thinkphp5.1框架中容器(Container)和门面(Facade)的实现方法分析


Posted in PHP onAugust 05, 2019

本文实例讲述了thinkphp5.1框架中容器(Container)和门面(Facade)的实现方法。分享给大家供大家参考,具体如下:

tp5.1中引入了容器(Container)和门面(Facade)这两个新的类

官方文档已经给出了定义:

容器(Container)实现类的统一管理,确保对象实例的唯一性。

门面(Facade)为容器(Container)中的类提供了一个静态调用接口,相比于传统的静态方法调用, 带来了更好的可测试性和扩展性,你可以为任何的非静态类库定义一个facade类。

深入源码,我们来看看它到底是如何实现的:

// 在框架目录下的base.php文件
// 注册核心类到容器
Container::getInstance()->bind([
  'app'          => App::class,
  'build'         => Build::class,
  'cache'         => Cache::class,
  'config'        => Config::class,
  ...
]);
// 注册核心类的静态代理
Facade::bind([
  facade\App::class   => App::class,
  facade\Build::class  => Build::class,
  facade\Cache::class  => Cache::class,
  facade\Config::class  => Config::class,
  ...
]);
// 注册类库别名
Loader::addClassAlias([
  'App'   => facade\App::class,
  'Build'  => facade\Build::class,
  'Cache'  => facade\Cache::class,
  'Config'  => facade\Config::class,
  ...
]);

容器实现:

这里,框架已经帮我们绑定了系统常用类到容器中,在之后使用时,只需要调用助手函数 app()进行容器中的类解析调用,对于已经绑定的类标识,会自动快速实例化。

// 实例化缓存类
app('cache');
// app('cache', ['file']); 参数化调用
// 相当于执行了
Container::get('cache');
// 查看源码,Container调用的其实是make方法,在该方法里调用反射等实现类的实例化,过程如下:
public function make($abstract, $vars = [], $newInstance = false)
{
  if (true === $vars) {
    // 总是创建新的实例化对象
    $newInstance = true;
    $vars    = [];
  }
  if (isset($this->instances[$abstract]) && !$newInstance) {
    $object = $this->instances[$abstract];
  } else {
    if (isset($this->bind[$abstract])) {
      $concrete = $this->bind[$abstract];
 // 闭包实现
      if ($concrete instanceof \Closure) {
        $object = $this->invokeFunction($concrete, $vars);
      } else {
        $object = $this->make($concrete, $vars, $newInstance);
      }
    } else {
 // 反射实现
      $object = $this->invokeClass($abstract, $vars);
    }
    if (!$newInstance) {
      $this->instances[$abstract] = $object;
    }
  }
  return $object;
}
/**
 * 调用反射执行类的实例化 支持依赖注入
 * @access public
 * @param string  $class 类名
 * @param array   $vars 变量
 * @return mixed
 */
public function invokeClass($class, $vars = [])
{
  $reflect   = new \ReflectionClass($class);
  $constructor = $reflect->getConstructor();
  if ($constructor) {
    $args = $this->bindParams($constructor, $vars);
  } else {
    $args = [];
  }
  return $reflect->newInstanceArgs($args);
}
/**
 * 执行函数或者闭包方法 支持参数调用
 * @access public
 * @param string|array|\Closure $function 函数或者闭包
 * @param array         $vars   变量
 * @return mixed
 */
public function invokeFunction($function, $vars = [])
{
  $reflect = new \ReflectionFunction($function);
  $args  = $this->bindParams($reflect, $vars);
  return $reflect->invokeArgs($args);
}

简而言之,容器内部是通过反射类或闭包等来实现类的实例化。

门面实现:

以一个例子来分析:

facade\Config::get('app_debug');

我们来分析一下它的实现方式:

// thinkphp\library\facade\Config 类
namespace think\facade;
use think\Facade;
class Config extends Facade
{
}
// 从源代码上看 Config本身没有任何方法,它继承了Facade的方法,但Facade并没有get这个静态方法
// 此时,系统自动触发了魔术方法:__callStatic(),Facade重写了此方法:
public static function __callStatic($method, $params)
{
  return call_user_func_array([static::createFacade(), $method], $params);
}
// 可见,最后调用的是用户自定义函数:call_user_func_array([实例, 方法], 参数),为了获得Config实例,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);
}
// 其内部是通过容器来实例化对象
// 因为在base.php中已经将 think\Config 类绑定到 config 这个标识
Container::getInstance()->bind([
'config' => Config::class
])
// 在 createFacade 方法中,获取类的名称:$class = $class ?: static::class; 即得到 config 这个标识
// 在容器的make方法中,根据config标识,找到绑定的 think\Config 类,并调用其动态方法 get。
facade\Config::get('app_debug');
// 最后调用的是:
(new think\Config())->get('app_debug');

简而言之,门面的实现是通过PHP的魔术方法 __callStatic,再配合容器来实现动态类的静态化调用。

希望本文所述对大家基于ThinkPHP框架的PHP程序设计有所帮助。

PHP 相关文章推荐
php 数据库字段复用的基本原理与示例
Jul 22 PHP
采用PHP函数memory_get_usage获取PHP内存清耗量的方法
Dec 06 PHP
PHP 登录记住密码实现思路
May 07 PHP
解析PHP缓存函数的使用说明
May 10 PHP
php eval函数一句话木马代码
May 21 PHP
在Linux系统的服务器上隐藏PHP版本号的方法
Jun 06 PHP
详解php比较操作符的安全问题
Dec 03 PHP
PHP实现针对日期,月数,天数,周数,小时,分,秒等的加减运算示例【基于strtotime】
Apr 19 PHP
php使用curl伪造来源ip和refer的方法示例
May 08 PHP
ThinkPHP 3使用OSS的方法
Jul 19 PHP
解决laravel 表单提交-POST 异常的问题
Oct 15 PHP
基于PHP的微信公众号的开发流程详解
Aug 07 PHP
RSA实现JS前端加密与PHP后端解密功能示例
Aug 05 #PHP
thinkPHP5框架接口写法简单示例
Aug 05 #PHP
ThinkPHP5+UEditor图片上传到阿里云对象存储OSS功能示例
Aug 05 #PHP
PHP各种常见经典算法总结【排序、查找、翻转等】
Aug 05 #PHP
php时间戳转换代码详解
Aug 04 #PHP
ThinkPHP5.1框架数据库链接和增删改查操作示例
Aug 03 #PHP
ThinkPHP5&5.1框架关联模型分页操作示例
Aug 03 #PHP
You might like
PHP输出英文时间日期的安全方法(RFC 1123格式)
2014/06/13 PHP
深入浅析Yii admin的权限控制
2016/08/31 PHP
Yii框架分页实现方法详解
2017/05/20 PHP
PHP实现生成推广海报的方法详解
2018/03/14 PHP
PHP7新特性
2021/03/09 PHP
javascript之可拖动的iframe效果代码
2008/08/01 Javascript
让IE8支持DOM 2(不用框架!)
2009/12/31 Javascript
JavaScript创建对象的写法
2013/08/29 Javascript
nodeType属性返回被选节点的节点类型介绍
2013/11/22 Javascript
javascritp添加url参数将参数加入到url中
2014/09/25 Javascript
调试JavaScript中正则表达式中遇到的问题
2015/01/27 Javascript
原生js和jquery实现图片轮播淡入淡出效果
2015/04/23 Javascript
js获取页面description的方法
2015/05/21 Javascript
浅谈Node.js中的定时器
2015/06/18 Javascript
js下拉选择框与输入框联动实现添加选中值到输入框的方法
2015/08/17 Javascript
AngularJs Dependency Injection(DI,依赖注入)
2016/09/02 Javascript
jQuery替换节点用法示例(使用replaceWith方法)
2016/09/08 Javascript
jquery对Json的各种遍历方法总结(必看篇)
2016/09/29 Javascript
Bootstrap列表组学习使用
2017/02/09 Javascript
微信小程序实现上传图片功能
2018/05/28 Javascript
微信小程序BindTap快速连续点击目标页面跳转多次问题处理
2019/04/08 Javascript
Vue CLI项目 axios模块前后端交互的使用(类似ajax提交)
2019/09/01 Javascript
vue中v-for循环选中点击的元素并对该元素添加样式操作
2020/07/17 Javascript
[02:07]2018DOTA2亚洲邀请赛主赛事第三日五佳镜头 fy极限反杀
2018/04/06 DOTA
python opencv旋转图像(保持图像不被裁减)
2018/07/26 Python
Python实现的调用C语言函数功能简单实例
2019/03/13 Python
python GUI库图形界面开发之PyQt5动态加载QSS样式文件
2020/02/25 Python
Python Celery异步任务队列使用方法解析
2020/08/10 Python
德国鞋子网上商店:Omoda.de
2017/03/31 全球购物
助人为乐模范事迹材料
2014/06/02 职场文书
党员个人对照检查材料思想汇报
2014/09/16 职场文书
Nginx中break与last的区别详析
2021/03/31 Servers
常用的Python代码调试工具总结
2021/06/23 Python
Win11怎么进入安全模式?Windows 11进入安全模式的方法
2021/11/21 数码科技
python机器学习实现oneR算法(以鸢尾data为例)
2022/03/03 Python
Go归并排序算法的实现方法
2022/04/06 Golang