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编程开发“虚拟域名”系统
Oct 09 PHP
php+mysqli实现批量替换数据库表前缀的方法
Dec 29 PHP
在WordPress中获取数据库字段内容和添加主题设置菜单
Jan 11 PHP
ThinkPHP中Common/common.php文件常用函数功能分析
May 20 PHP
PHP运行模式汇总
Nov 06 PHP
php查找字符串中第一个非0的位置截取
Feb 27 PHP
在PHP 7下安装Swoole与Yar,Yaf的方法教程
Jun 02 PHP
Windows平台实现PHP连接SQL Server2008的方法
Jul 26 PHP
购物车实现的几种方式优缺点对比
May 02 PHP
PHP添加文字水印或图片水印的水印类完整源代码与使用示例
Mar 18 PHP
微信公众号之主动给用户发送消息功能
Jun 22 PHP
PHP引擎php.ini参数优化深入讲解
Mar 24 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五种设计模式小结
2011/03/23 PHP
php 冒泡排序 交换排序法
2011/05/10 PHP
PHP-CGI进程CPU 100% 与 file_get_contents 函数的关系分析
2011/08/15 PHP
学习使用curl采集curl使用方法
2012/01/11 PHP
php的GD库imagettftext函数解决中文乱码问题
2015/01/24 PHP
PHP将session信息存储到数据库的类实例
2015/03/04 PHP
php递归遍历多维数组的方法
2015/04/18 PHP
LNMP部署laravel以及xhprof安装使用教程
2017/09/14 PHP
总结一些js自定义的函数
2006/08/05 Javascript
escape、encodeURI、encodeURIComponent等方法的区别比较
2006/12/27 Javascript
IE6不能修改NAME问题的解决方法
2010/09/03 Javascript
wap手机图片滑动切换特效无css3元素js脚本编写
2014/07/28 Javascript
JavaScript的各种常见函数定义方法
2014/09/16 Javascript
常用的JavaScript WEB操作方法分享
2015/02/28 Javascript
深入浅析JavaScript的API设计原则
2016/06/14 Javascript
js前端面试题及答案整理(一)
2016/08/26 Javascript
浅析JavaScriptSerializer类的序列化与反序列化
2016/11/22 Javascript
react开发教程之React 组件之间的通信方式
2017/08/12 Javascript
vue-cli 默认路由再子路由选中下的选中状态问题及解决代码
2018/09/06 Javascript
angularJs利用$scope处理升降序的方法
2018/10/08 Javascript
javascript中undefined的本质解析
2019/07/31 Javascript
Vue实现点击按钮复制文本内容的例子
2019/11/09 Javascript
vue全局使用axios的操作
2020/09/08 Javascript
js实现鼠标切换图片(无定时器)
2021/01/27 Javascript
[02:35]DOTA2英雄基础教程 狙击手
2014/01/14 DOTA
[50:38]DOTA2-DPC中国联赛 正赛 Phoenix vs CDEC BO3 第二场 3月7日
2021/03/11 DOTA
Python实现简单的文件传输与MySQL备份的脚本分享
2016/01/03 Python
Python + selenium自动化环境搭建的完整步骤
2018/05/19 Python
python 自定义装饰器实例详解
2019/07/20 Python
一波HTML5 Canvas基础绘图实例代码集合
2016/02/28 HTML / CSS
城野医生官方海外旗舰店:风靡亚洲毛孔收敛水
2018/04/26 全球购物
蔻驰意大利官网:COACH意大利
2019/01/16 全球购物
俄罗斯三星品牌商店:GalaxyStore
2020/11/04 全球购物
庆祝教师节主题班会
2015/08/17 职场文书
nginx proxy_cache 缓存配置详解
2021/03/31 Servers
如何用python反转图片,视频
2021/04/24 Python