谈谈thinkphp5.1中容器(Container)和门⾯(Facade)的实现
tp5.1中引⼊了容器(Container)和门⾯(Facade)这两个新的类
官⽅⽂档已经给出了定义:
容器(Container)实现类的统⼀管理,确保对象实例的唯⼀性。
门⾯(Facade)为容器(Container)中的类提供了⼀个静态调⽤接⼝,相⽐于传统的静态⽅法调⽤,带来了更好的可测试性和扩展性,你可以为任何的⾮静态类库定义⼀个facade类。
深⼊源码,我们来看看它到底是如何实现的:
// 在框架⽬录下的base.php⽂件
// 注册核⼼类到容器
Container::getInstance()->bind([
'app'                  => App::class,
'build'                => Build::class,
'cache'                => Cache::class,
'config'                => Config::class,
...
]);
// 注册核⼼类的静态代理
Facade::bind([
facade\App::class      => App::class,
facade\Build::class    => Build::class,
facade\Cache::class    => Cache::class,
facade\Config::class  => Config::class,
...
]
);
// 注册类库别名
Loader::addClassAlias([
'App'      => facade\App::class,
'Build'    => facade\Build::class,
'Cache'    => facade\Cache::class,
'Config'  => facade\Config::class,
...
]);
容器实现:
这⾥,框架已经帮我们绑定了系统常⽤类到容器中,在之后使⽤时,只需要调⽤助⼿函数 app()进⾏容器中的类解析调⽤,对于已经绑定的类标识,会⾃动快速实例化。// 实例化缓存类
app('cache');
// app('cache', ['file']); 参数化调⽤
// 相当于执⾏了
Container::get('cache');
// 查看源码,Container调⽤的其实是make⽅法,在该⽅法⾥调⽤反射等实现类的实例化,过程如下:
public function make($abstract, $vars = [], $newInstance = false) {
if (true === $vars) {
// 总是创建新的实例化对象
$newInstance = true;
$vars        = [];
}
if (isset($this->instances[$abstract]) && !$newInstance) {
$object = $this->instances[$abstract];
} else {
if (isset($this->bind[$abstract])) {
$concrete = $this->bind[$abstract];
       // 闭包实现
if ($concrete instanceof \Closure) {
$object = $this->invokeFunction($concrete, $vars);
} else {
$object = $this->make($concrete, $vars, $newInstance);            }
} else {
       // 反射实现
$object = $this->invokeClass($abstract, $vars);
}
if (!$newInstance) {
$this->instances[$abstract] = $object;
}
}
return $object;
}
/**
* 调⽤反射执⾏类的实例化⽀持依赖注⼊
* @access public
* @param  string    $class 类名
* @param  array    $vars  变量
* @return mixed
*/
public function invokeClass($class, $vars = [])
{
$reflect    = new \ReflectionClass($class);
$constructor = $reflect->getConstructor();
if ($constructor) {
$args = $this->bindParams($constructor, $vars);
} else {
$args = [];
}
return $reflect->newInstanceArgs($args);
}
/**
* 执⾏函数或者闭包⽅法⽀持参数调⽤
* @access public
* @param  string|array|\Closure $function 函数或者闭包
* @param  array                $vars    变量
* @return mixed
*/
public function invokeFunction($function, $vars = [])
{
$reflect = new \ReflectionFunction($function);
$args    = $this->bindParams($reflect, $vars);
return $reflect->invokeArgs($args);
}
简⽽⾔之,容器内部是通过反射类或闭包等来实现类的实例化。
门⾯实现:
以⼀个例⼦来分析:
facade\Config::get('app_debug');container容器用法
我们来分析⼀下它的实现⽅式:
// thinkphp\library\facade\Config 类
namespace think\facade;
use think\Facade;
class Config extends Facade
{
}
// 从源代码上看 Config本⾝没有任何⽅法,它继承了Facade的⽅法,但Facade并没有get这个静态⽅法
// 此时,系统⾃动触发了魔术⽅法:__callStatic(),Facade重写了此⽅法:
public static function __callStatic($method, $params)
{
return call_user_func_array([static::createFacade(), $method], $params);
}
// 可见,最后调⽤的是⽤户⾃定义函数:call_user_func_array([实例, ⽅法], 参数),为了获得Config实例,Facade⼜定义了⼀个获取对象的⽅法:/**
* 创建Facade实例
* @static
* @access protected
* @param  string    $class          类名或标识
* @param  array    $args          变量
* @param  bool      $newInstance    是否每次创建新的实例
* @return object
*/
protected static function createFacade($class = '', $args = [], $newInstance = false)
{
$class      = $class ?: static::class;
$facadeClass = static::getFacadeClass();
if ($facadeClass) {
$class = $facadeClass;
} elseif (isset(self::$bind[$class])) {
$class = self::$bind[$class];
}
if (static::$alwaysNewInstance) {
$newInstance = true;
}
return Container::getInstance()->make($class, $args, $newInstance);
}
// 其内部是通过容器来实例化对象
// 因为在base.php中已经将 think\Config 类绑定到 config 这个标识
Container::getInstance()->bind([
  'config'  => Config::class
])
// 在createFacade ⽅法中,获取类的名称:$class = $class ?: static::class; 即得到 config 这个标识
// 在容器的make⽅法中,根据config标识,到绑定的 think\Config 类,并调⽤其动态⽅法 get。
facade\Config::get('app_debug');
/
/ 最后调⽤的是:
(new think\Config())->get('app_debug');
简⽽⾔之,门⾯的实现是通过PHP的魔术⽅法 __callStatic,再配合容器来实现动态类的静态化调⽤。