Tp5.1 源码窥探之配置篇

通过之前了解到container实现了ArrayAccess,IteratorAggregate,Conuntable,三个接口类,并且注意到它使用了单列模式和注册树模式,并且也使用PHP的反射机制。在了解配置文件是怎么加载之后,你会了解到另外的常见的设计模式:工厂模式,并实现简单的扩展。

配置加载流程

之前了解了类的自动加载,那么再重新查看项目的入口文件最后一行Container::get('app')->run()->send(),Container容器类自动加载,它没有构造函数,使用单列模式进行初始化,首先访问get静态方法,它通过后期静态绑定通过上下文确定为自身实例化,并且调用make方法。

thinkphp/library/think/Container.php

/**
 * 获取容器中的对象实例
 * @access public
 * @param  string        $abstract       类名或者标识
 * @param  array|true    $vars           变量
 * @param  bool          $newInstance    是否每次创建新的实例
 * @return object
 */
public static function get($abstract, $vars = [], $newInstance = false)
{
    return static::getInstance()->make($abstract, $vars, $newInstance);
}

make方法中,首先查看容器标识别名name中是否有该标识对应的类名解析获取的完全限定名,然后再查看该容器内的注册树instances中是否有对应类的实例,如果有就返回该实例,如果没有就查看容器绑定标识bind中是否有该标识,如果有,则将此标识做为键名,此标识对应的完全限定名做为键值,添加到容器标识别名name中,对应如果没有的话就利用反射机制进行类的实例化,并将该实例添加到注册树中,最后返回该实例。

thinkphp/library/think/Container.php

/**
 * 创建类的实例
 * @access public
 * @param  string        $abstract       类名或者标识
 * @param  array|true    $vars           变量
 * @param  bool          $newInstance    是否每次创建新的实例
 * @return object
 */
public function make($abstract, $vars = [], $newInstance = false)
{
    if (true === $vars) {
        $newInstance = true;
        $vars        = [];
    }
    $abstract = isset($this->name[$abstract]) ? $this->name[$abstract] : $abstract; //app

    if (isset($this->instances[$abstract]) && !$newInstance) {
        return $this->instances[$abstract];
    }

    if (isset($this->bind[$abstract])) { // true
        $concrete = $this->bind[$abstract]; // think\App

        if ($concrete instanceof Closure) {
            $object = $this->invokeFunction($concrete, $vars);
        } else {
            $this->name[$abstract] = $concrete; // ['app'=>'think\App']

            return $this->make($concrete, $vars, $newInstance);
        }
    } else {
        $object = $this->invokeClass($abstract, $vars); // App实例对象
    }

    if (!$newInstance) {
        $this->instances[$abstract] = $object;//['think\App' => 'App实例对象']
    }

    return $object;
}

实例化app对象后,调用run方法,然后就是初始化应用initialize,首先加载环境变量配置env文件,然后加载惯例配置文件convention,然后进入init方法,初始化应用或者模块,加载初始化文件,行为扩展文件tags,加载公共文件件common,加载系统助手函数helper,加载中间件middleware,注册服务的容器对象实例provider,然后是自动加载配置文件

thinkphp/library/think/App.php

// 自动读取配置文件
if (is_dir($path . 'config')) {
    $dir = $path . 'config' . DIRECTORY_SEPARATOR;
} elseif (is_dir($this->configPath . $module)) {
    $dir = $this->configPath . $module;
}

$files = isset($dir) ? scandir($dir) : [];

foreach ($files as $file) {
    if ('.' . pathinfo($file, PATHINFO_EXTENSION) === $this->configExt) {
        $this->config->load($dir . $file, pathinfo($file, PATHINFO_FILENAME));
    }
}

$this->config实际上是使用了魔术方法__get,当访问对象不存在的属性的时候,系统自动调用该对象的__get方法,而App类是继承了Container类的,所以它执行make方法,返回Config对象实例,然后再执行load方法。

thinkphp/library/think/Config.php

/**
 * 加载配置文件(多种格式)
 * @access public
 * @param  string    $file 配置文件名
 * @param  string    $name 一级配置名
 * @return mixed
 */
public function load($file, $name = '')
{
    if (is_file($file)) {
        $filename = $file;
    } elseif (is_file($this->path . $file . $this->ext)) {
        $filename = $this->path . $file . $this->ext;
    }

    if (isset($filename)) {
        return $this->loadFile($filename, $name);
    } elseif ($this->yaconf && Yaconf::has($file)) {
        return $this->set(Yaconf::get($file), $name);
    }

    return $this->config;
}

Yaconf是一个高性能的配置管理扩展,Yaml是专门用来写配置文件的语言,非常简洁和强大,远比 JSON 格式方便。具体怎么安装扩展和配置,之前的文章有所讲解。此处就不再叙述,然后再看loadFile方法,进行文件类型判断,根据不同的文件类型进行解析(此处属于冗余,完全可以使用工厂模式的便利性),然后再调用parse方法,此处用到了工厂模式,返回实例。最后调用parse方法进行解析配置文件。

thinkphp/library/think/Config.php

protected function loadFile($file, $name)
{
    $name = strtolower($name);
    $type = pathinfo($file, PATHINFO_EXTENSION);

    // TODO 此处的两个php 和 yamlk可以以扩展的方式
    if ('php' == $type) {
        return $this->set(include $file, $name);
    } elseif ('yaml' == $type && function_exists('yaml_parse_file')) {
        return $this->set(yaml_parse_file($file), $name);
    }

    return $this->parse($file, $type, $name);
}

public function parse($config, $type = '', $name = '')
{
    if (empty($type)) {
        $type = pathinfo($config, PATHINFO_EXTENSION);
    }

    $object = Loader::factory($type, '\\think\\config\\driver\\', $config);

    return $this->set($object->parse(), $name);
}

thinkphp/library/think/Loader.php

public static function factory($name, $namespace = '', ...$args)
{
    $class = false !== strpos($name, '\\') ? $name : $namespace . ucwords($name);

    if (class_exists($class)) {
        return Container::getInstance()->invokeClass($class, $args);
    } else {
        throw new ClassNotFoundException('class not exists:' . $class, $class);
    }
}

经过上述分析,你知道为什么框架的配置权重了吧,惯例配置 < 应用配置 < 模块配置 < 动态配置

扩展

下面针对Config工厂模式进行扩展

thinkphp/library/think/config/driver/Php.php

namespace think\config\driver;

class Php
{
    protected $config;

    public function __construct($config)
    {
        if (is_file($config)) {
            $config = include $config;
        }

        $this->config = $config;
    }

    public function parse()
    {
        return  $this->config;
    }
}

thinkphp/library/think/config/driver/Yaml.php

namespace think\config\driver;


use think\Exception;

class Yaml
{
    protected $config;

    public function __construct($config)
    {
        if( ! function_exists('yaml_parse_file') ){
            throw new Exception('不存在yaml扩展');
        }

        if( is_file($config) ){
            $config = yaml_parse_file($config);
        }

        $this->config = $config;
    }

    public function parse()
    {
        return $this->config;
    }
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
今年不学习,明天惨唧唧。
zs4336
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!