在上一篇文章中我们讲到了 ThinkPHP 如何实现自动加载,如果想看的话可以看
ThinkPHP5.1 源码浅析(二)自动加载机制
在阅读本篇文章 之前,我希望你掌握了 IOC、DI、Facade 的基本知识,如果不了解,请先查看着几篇文章。
深入理解控制反转(IoC)和依赖注入(DI)
那么步入正题。
服务调用
基于分析框架的 入口脚本文件index.php
// 加载基础文件
require __DIR__ . '/../thinkphp/base.php';
// 支持事先使用静态方法设置 Request 对象和 Config 对象
// 执行应用并响应
Container::get('app')->run()->send();
上面 base.php
中的作用是载入自动加载机制,和异常处理,以及开启日志功能。
// 执行应用并响应
Container::get('app')->run()->send();
在这里才是使用了 IOC 容器功能,获取 app 这个容器1
进入 Container 中之后我们先介绍他的类属性
protected static $instance; // 定义我们的容器类实例,采用单例模式,只实例化一次
protected $instances = []; // 容器中的对象实例
protected $bind = []; // 容器绑定标识
protected $name = []; // 容器标识别名
$instances
是实现了 注册树模式2,存储值
array (
'think\\App' => App 实例,
'think\\Env' => Env 实例,
'think\\Config' => Config 实例,
...
)
bind
在初始化时,会载入一堆初始数据,记录一堆类别名和 类名的映射关系。
protected $bind = [
'app' => 'think\\App',
'build' => 'think\\Build',
'cache' => 'think\\Cache',
'config' => 'think\\Config',
'cookie' => 'think\\Cookie',
...
]
name
和 bind
属性记录的值都是很类似的,都是 类别名和 类名的映射关系。区别是,name
记录的是 已经实例化后的 映射关系。
进入 get 方法
public static function get($abstract, $vars = [], $newInstance = false)
{return static::getInstance()->make($abstract, $vars, $newInstance);
}
这一段代码没什么好讲的,就是先获取当前容器的实例(单例),并实例化。
进入 make 方法
public function make($abstract, $vars = [], $newInstance = false)
{if (true === $vars) {
// 总是创建新的实例化对象
$newInstance = true;
$vars = [];}
// 如果已经存在并且实例化的类,就用别名拿到他的类
$abstract = isset($this->name[$abstract]) ? $this->name[$abstract] : $abstract;
// 如果已经实例化,并且不用每次创建新的实例的话,就直接返回注册树上的实例
if (isset($this->instances[$abstract]) && !$newInstance) {return $this->instances[$abstract];
}
// 如果我们绑定过这个类,例如 'app' => 'think\\App',
if (isset($this->bind[$abstract])) {$concrete = $this->bind[$abstract];
// 因为 ThinkPHP 实现可以绑定一个闭包或者匿名函数进入,这里是对闭包的处理
if ($concrete instanceof Closure) {$object = $this->invokeFunction($concrete, $vars);
} else {
// 记录 映射关系,并按照 类名来实例化,如 think\\App
$this->name[$abstract] = $concrete;
return $this->make($concrete, $vars, $newInstance);
}
} else {
// 按照类名调用该类
$object = $this->invokeClass($abstract, $vars);
}
if (!$newInstance) {$this->instances[$abstract] = $object;
}
// 返回制作出来的该类
return $object;
}
我们拆分一下,
if (true === $vars) {
// 总是创建新的实例化对象
$newInstance = true;
$vars = [];}
这段代码是 让我们函数可以 使用 make($abstract, true)
的方式调用此函数,使我们每次得到的都是新的实例。(我觉得这种方式不是很好,每个变量的造成含义不明确)
// 如果已经存在并且实例化的类,就用别名拿到他的类
$abstract = isset($this->name[$abstract]) ? $this->name[$abstract] : $abstract;
// 如果已经实例化,并且不用每次创建新的实例的话,就直接返回注册树上的实例
if (isset($this->instances[$abstract]) && !$newInstance) {return $this->instances[$abstract];
}
前面说过,name
中存放的是已经实例化的 别名 => 类名 的映射关系,我们在这里尝试取出 类名,如果该类实例化,就直接返回。
// 如果我们绑定过这个类,例如 'app' => 'think\\App',
if (isset($this->bind[$abstract])) {$concrete = $this->bind[$abstract];
// 因为 ThinkPHP 实现可以绑定一个闭包或者匿名函数进入,这里是对闭包的处理
if ($concrete instanceof Closure) {$object = $this->invokeFunction($concrete, $vars);
} else {
// 记录 映射关系,并按照 类名来实例化,如 think\\App
$this->name[$abstract] = $concrete;
return $this->make($concrete, $vars, $newInstance);
}
} else {
// 按照类名调用该类
$object = $this->invokeClass($abstract, $vars);
}
这里是看我们需要容器加载的类是否以前绑定过别名(我们也可以直接 bind('classNickName')
来设置一个)
- 如果绑定过,那么就来实例化它。
- 如果没有,那么就认定他是一个类名,直接调用。3
门面模式
在上面的 IOC 容器中,我们需要
$ioc->get('test');
才能拿到 test 类,才能使用我们的$user->hello()
方法进行打招呼,有了门面之后,我们可以直接 用Test::hello()
进行静态调用,下面我们就来介绍一下这个
在我们编写代码时经常会用到 facade
包下的类来接口的静态调用,我们在这里举一下官网的例子
假如我们定义了一个 app\common\Test
类,里面有一个 hello
动态方法。
<?php
namespace app\common;
class Test
{public function hello($name)
{return 'hello,' . $name;}
}
调用 hello 方法的代码应该类似于:
$test = new \app\common\Test;
echo $test->hello('thinkphp'); // 输出 hello,thinkphp
接下来,我们给这个类定义一个静态代理类 app\facade\Test
(这个类名不一定要和Test
类一致,但通常为了便于管理,建议保持名称统一)。
<?php
namespace app\facade;
use think\Facade;
class Test extends Facade
{protected static function getFacadeClass()
{return 'app\common\Test';}
}
只要这个类库继承 think\Facade
,就可以使用静态方式调用动态类app\common\Test
的动态方法,例如上面的代码就可以改成:
// 无需进行实例化 直接以静态方法方式调用 hello
echo \app\facade\Test::hello('thinkphp');
结果也会输出 hello,thinkphp
。
说的直白一点,Facade 功能可以让类无需实例化而直接进行静态方式调用。
Facade 工作原理
- Facede 核心实现原理就是在 Facade 提前注入 IoC 容器。
- 定义一个服务提供者的外观类,在该类定义一个类的变量,跟 ioc 容器绑定的 key 一样,
- 通过静态魔术方法__callStatic 可以得到当前想要调用的 hello 方法
- 使用 static::$ioc->make(‘Test’);
为什么要使用 Facade
使用 Facades 其实最主要的就是它提供了简单,易记的语法,从而无需手动注入或配置长长的类名。此外,由于他们对 PHP 静态方法的独特调用,使得测试起来非常容易。
- 在这里系统找不到 Container 类的位置,所以会执行自动加载机制去寻找 Container 的位置,并加载它 ↩
- 把一堆实例挂在树上,需要的时候在拿来用。↩
- 直接调用是使用了反射后的结果,关于反射的知识点在自行查看 ↩