thinkphp 5.1框架解析(三):容器和依赖注入
在上一篇文章中我们讲到了 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 的位置,并加载它 ↩
- 把一堆实例挂在树上,需要的时候在拿来用。 ↩
- 直接调用是使用了反射后的结果,关于反射的知识点在自行查看 ↩