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 协议》,转载必须注明作者和本文链接
推荐文章: