在上一篇文章中咱们讲到了 ThinkPHP 如何实现自动加载,若是想看的话能够看
ThinkPHP5.1 源码浅析(二)自动加载机制php
在阅读本篇文章 以前,我但愿你掌握了 IOC 、DI 、Facade的基本知识,若是不了解,请先查看着几篇文章。thinkphp
深刻理解控制反转(IoC)和依赖注入(DI)segmentfault
那么步入正题。cookie
基于分析框架的 入口脚本文件index.php
闭包
// 加载基础文件 require __DIR__ . '/../thinkphp/base.php'; // 支持事先使用静态方法设置Request对象和Config对象 // 执行应用并响应 Container::get('app')->run()->send();
上面 base.php
中的做用是载入自动加载机制,和异常处理,以及开启日志功能。app
// 执行应用并响应 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
在初始化时,会载入一堆初始数据,记录一堆类别名和 类名的映射关系。ui
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')
来设置一个)
在上面的 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功能可让类无需实例化而直接进行静态方式调用。
使用Facades其实最主要的就是它提供了简单,易记的语法,从而无需手动注入或配置长长的类名。此外,因为他们对 PHP 静态方法的独特调用,使得测试起来很是容易。