TP5.1 源码窥探之门面代理
之前有过疑问在某个控制器或者模型中
use
引入一个全局的类,但使用编辑器的快捷跳转功能,又跳不过去,说是找不到该类,但实际上项目又可以使用,现在了解了自动加载和门面代理模式就知道为什么可以正常使用了。
自动加载类库别名
之前有介绍了框架是怎么自动加载类的,系统在注册自动加载后,有一个操作,那就是给Loader
类添加类库别名。
thinkphp/base.php
// 注册类库别名
Loader::addClassAlias([
'App' => facade\App::class,
'Build' => facade\Build::class,
'Cache' => facade\Cache::class,
'Config' => facade\Config::class,
'Cookie' => facade\Cookie::class,
'Db' => Db::class,
'Debug' => facade\Debug::class,
'Env' => facade\Env::class,
'Facade' => Facade::class,
'Hook' => facade\Hook::class,
'Lang' => facade\Lang::class,
'Log' => facade\Log::class,
'Request' => facade\Request::class,
'Response' => facade\Response::class,
'Route' => facade\Route::class,
'Session' => facade\Session::class,
'Url' => facade\Url::class,
'Validate' => facade\Validate::class,
'View' => facade\View::class,
]);
而在框架进行Loader::autoload
自动加载的步骤的时候,有一段逻辑是判断是否存在类库别名,如果存在就自动加载该类。
thinkphp/library/think/Loader.php
// 自动加载
public static function autoload($class)
{
//class_alias 为一个类创建别名,并自动加载
if (isset(self::$classAlias[$class])) {
return class_alias(self::$classAlias[$class], $class);
}
//var_dump($class);exit; => think\Error
if ($file = self::findFile($class)) {
// Win环境严格区分大小写
if (strpos(PHP_OS, 'WIN') !== false && pathinfo($file, PATHINFO_FILENAME) != pathinfo(realpath($file), PATHINFO_FILENAME)) {
return false;
}
__include_file($file);
return true;
}
}
截止目前,终于知道它是怎么实现自动加载的了。
门面代理
门面为容器中的类提供了一个静态调用接口,相比于传统的静态方法调用, 带来了更好的可测试性和扩展性,说的直白一点,Facade
功能可以让类无需实例化而直接进行静态方式调用。你可以为任何的非静态类库定义一个facade
类。下面就看一下是怎么实现的,就以Config
类别名为例吧。
当使用Config
类别名的时候,
$middleware_config = \Config::get('middleware.');
系统实际上调用的是thinkphp/library/think/facade/Config.php
namespace think\facade;
use think\Facade;
class Config extends Facade
{
/**
* 获取当前Facade对应类名(或者已经绑定的容器对象标识)
* @access protected
* @return string
*/
protected static function getFacadeClass()
{
return 'config';
}
}
可见它继承了Facade类,当调用静态方法get
的时候,此类并不存在该方法,则会自动调用魔术方法__callStatic
thinkphp/library/think/Facade.php
// 调用实际类的方法
public static function __callStatic($method, $params)
{
return call_user_func_array([static::createFacade(), $method], $params);
}
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);
}
然后再调用Container
容器类的make
方法,然后再加载响应的绑定标识类。
thinkphp/library/think/Container.php
public function make($abstract, $vars = [], $newInstance = false)
{
if (true === $vars) {
// 总是创建新的实例化对象
$newInstance = true;
$vars = [];
}
$abstract = isset($this->name[$abstract]) ? $this->name[$abstract] : $abstract;
//var_dump($abstract,$this->name,$this->instances);exit;
if (isset($this->instances[$abstract]) && !$newInstance) {
return $this->instances[$abstract];
}
if (isset($this->bind[$abstract])) {
$concrete = $this->bind[$abstract];
//var_dump($concrete);exit;
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);
}
if (!$newInstance) {
$this->instances[$abstract] = $object;//['think\App' => 'App实例对象']
}
return $object;
}
源码显示实际上调用的是createFacade
返回的代理类的$method
相关方法,例如think\Config
的get
方法。现在明白它是怎么运行的了吧。
本作品采用《CC 协议》,转载必须注明作者和本文链接