1. 概述
在开始使用TP5的时候,发现,为啥子很多TP框架自带的类,在其他目录都有一个相同的类文件。比如: Cookie , Log , Request , Cache , … ,等。

因此,看看这是什么作用。以 Cache::set() 为例。
1.1 应用
- 类
Cache的应用:
平常运用一样,先 new 一个对象,然后通过 -> 调用方法:
<?use \think\Cache;$c = new Cache();$c->set($name, $value, $expire);
- 类
facade\Cache的应用:
静态的方式访问:
<?
use \think\facade\Cache;
Cache::set($name, $value, $expire);
2. 分析
2.1 查看第一层源码
- Cache源码就是具体实现的源码,调试代码也是走到该类。
查看
facade\Cache源码:<?php namespace think\facade; use think\Facade; /** * @see \think\Cache * @mixin \think\Cache * @method \think\cache\Driver connect(array $options = [], mixed $name = false) static 连接缓存 * @method \think\cache\Driver init(array $options = []) static 初始化缓存 * @method \think\cache\Driver store(string $name = '') static 切换缓存类型 * @method bool has(string $name) static 判断缓存是否存在 * @method mixed get(string $name, mixed $default = false) static 读取缓存 * @method mixed pull(string $name) static 读取缓存并删除 * @method mixed set(string $name, mixed $value, int $expire = null) static 设置缓存 * @method mixed remember(string $name, mixed $value, int $expire = null) static 如果不存在则写入缓存 * @method mixed inc(string $name, int $step = 1) static 自增缓存(针对数值缓存) * @method mixed dec(string $name, int $step = 1) static 自减缓存(针对数值缓存) * @method bool rm(string $name) static 删除缓存 * @method bool clear(string $tag = null) static 清除缓存 * @method mixed tag(string $name, mixed $keys = null, bool $overlay = false) static 缓存标签 * @method object handler() static 返回句柄对象,可执行其它高级方法 */ class Cache extends Facade { /** * 获取当前Facade对应类名(或者已经绑定的容器对象标识) * @access protected * @return string */ protected static function getFacadeClass() { return 'cache'; } }分析:
所有继承自Facade的类都可以静态的调用对象,但是如果这个类与Facade都没有该方法,那么按照PHP的特性,应该有个魔术方法__callStatic()来处理静态调用不到方法的情况,和生成一个目标类的对象的处理(这里衍生出来一个问题,目标类如何指定)。
查看第一层源码发现只有一个方法:<? protected static function getFacadeClass() { return 'cache'; }猜想:
这有可能就是指定目标类的语句,然后在父类进行实例化(根据依赖倒转原则,在父类中使用了子类必须重写的方法来获取指定的依赖名称)。
- 魔术方法
__callStatic()和类对象的处理这里没有实现的代码,那么应该可能在父类实现了,毕竟Facade是个通用的类。
2.2 查看Facade源码
<?php
namespace think;
class 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);
}
/**
* 获取当前Facade对应类名(或者已经绑定的容器对象标识)
* @access protected
* @return string
*/
protected static function getFacadeClass() {}
...
// 调用实际类的方法
public static function __callStatic($method, $params) {
return call_user_func_array([static::createFacade(), $method], $params);
}
}
验证:
- 果不其然有个
__callStatic()方法。 - 有个
getFacadeClass()方法,但是这与猜想不同,不是抽象方法(Facade类也不是抽象类)。
分析:
魔术方法
__callStatic()直接返回一个函数call_user_func_array的调用结果。那么按照 call_user_func_array 的功能来说,是对方法的调用具体实现了。<? public static function __callStatic($method, $params) { return call_user_func_array([static::createFacade(), $method], $params); }查看
static::createFacade()<? protected static function createFacade($class = '', $args = [], $newInstance = false) { ... $facadeClass = static::getFacadeClass(); ... // 主要生成逻辑 return Container::getInstance()->make($class, $args, $newInstance); }注意:这时已经跳转到了
Container类的处理了。查看
Container::getInstance()->make()<? public function make($abstract, $vars = [], $newInstance = false) { ... // 主要生成逻辑 $object = $this->invokeClass($abstract, $vars); ... return $object; }查看
$object = $this->invokeClass($abstract, $vars);<? public function invokeClass($class, $vars = []) { try { $reflect = new ReflectionClass($class); // 首先检查是否有 __make() 方法 if ($reflect->hasMethod('__make')) { $method = new ReflectionMethod($class, '__make'); if ($method->isPublic() && $method->isStatic()) { $args = $this->bindParams($method, $vars); return $method->invokeArgs(null, $args); } } // 通过构造函数生成 $constructor = $reflect->getConstructor(); $args = $constructor ? $this->bindParams($constructor, $vars) : []; return $reflect->newInstanceArgs($args); } catch (ReflectionException $e) { throw new ClassNotFoundException('class not exists: ' . $class, $class); } }这时,进入方法
invokeClass()方法的参数:$class = 'cache'和$vars = []。
反射首先检查类Cache是否有__make()方法,同时该方法是public static的,如果是,这返回该方法的生成结果。
分析:这个public static funtion __make()方法应该是框架通过容器类Container自动生成对象时的一个提供给用户使用的自定义初始化的入口:
这时流程回到了类Cache的__make()方法,因为缓存类有__make()方法。2.3 查看Cache源码
<? namespace think; class Cache { ... public function __construct(array $config = []) { $this->config = $config; $this->init($config); } /** * 连接缓存 * @access public * @param array $options 配置数组 * @param bool|string $name 缓存连接标识 true 强制重新连接 * @return Driver */ public function connect(array $options = [], $name = false) { if (false === $name) { $name = md5(serialize($options)); } if (true === $name || !isset($this->instance[$name])) { $type = !empty($options['type']) ? $options['type'] : 'File'; if (true === $name) { $name = md5(serialize($options)); } $this->instance[$name] = Loader::factory($type, '\\think\\cache\\driver\\', $options); } return $this->instance[$name]; } /** * 自动初始化缓存 * @access public * @param array $options 配置数组 * @param bool $force 强制更新 * @return Driver */ public function init(array $options = [], $force = false) { ... $this->handler = $this->connect($options); return $this->handler; } public static function __make(Config $config) { return new static($config->pull('cache')); } }分析:由于
__make()接管了容器初始化Cache对象的步骤,则这里必须要new出来一个 Cache。读取配置,初始化
Cache,连接缓存: ```php <? public function __construct(array $config = []) { $this->config = $config; $this->init($config); }
public function init(array $options = [], $force = false) { …
$this->handler = $this->connect($options);
return $this->handler;
}
public static function __make(Config $config) { return new static($config->pull(‘cache’)); }
2. 连接缓存 `connect()` :
```php
<?
public function connect(array $options = [], $name = false) {
if (false === $name) {
$name = md5(serialize($options));
}
if (true === $name || !isset($this->instance[$name])) {
$type = !empty($options['type']) ? $options['type'] : 'File';
if (true === $name) {
$name = md5(serialize($options));
}
// 加载依赖 $type
$this->instance[$name] = Loader::factory($type, '\\think\\cache\\driver\\', $options);
}
return $this->instance[$name];
}
分析:
- 连接缓存方法需要加载依赖
$type来使用,而且还是返回这个依赖,也就是说__make()方法初始化 Cache 的结果就是返回这个依赖,说明,Cache是对$type类的再封装,如果回到开始的Cache::set()调用,set()方法使用的是$type类的属性。 $options内容是由__make()方法加载到的配置,这个配置是:
也就是说<?php return [ // 驱动方式 'type' => 'File', // 缓存保存目录 'path' => '', // 缓存前缀 'prefix' => '', // 缓存有效期 0表示永久缓存 'expire' => 0, ];$type指向的是File的类。查看 File 类可以看到对应的 set() 方法等对应的在 facade\cache() 类的注释中出现的方法了。
至此,这个facade\cache()的初始化及调用过程就清楚了。3. 总结
TP5门面模式的设计流程图如下:

