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 URL验证正则表达式
Jul 19 PHP
PHP笔记之:基于面向对象设计的详解
May 14 PHP
php中apc缓存使用示例
Dec 25 PHP
PHP彩蛋信息介绍和阻止泄漏的方法(隐藏功能)
Aug 06 PHP
PHP处理Json字符串解码返回NULL的解决方法
Sep 01 PHP
CI框架整合widget(页面格局)的方法
May 17 PHP
Zend Framework基于Command命令行建立ZF项目的方法
Feb 18 PHP
PHP后端银联支付及退款实例代码
Jun 23 PHP
PHP Socket网络操作类定义与用法示例
Aug 30 PHP
如何通过View::first使用Laravel Blade的动态模板详解
Sep 21 PHP
php 多继承的几种常见实现方法示例
Nov 18 PHP
通过实例解析PHP数据类型转换方法
Jul 11 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
Search File Contents PHP 搜索目录文本内容的代码
2010/02/21 PHP
PHP无敌近乎加密方式!
2010/07/17 PHP
PHP命名空间简单用法示例
2018/12/28 PHP
使javascript也能包含文件
2006/10/26 Javascript
JavaScript null和undefined区别分析
2009/10/14 Javascript
用AJAX返回HTML片段中的JavaScript脚本
2010/01/04 Javascript
js getBoundingClientRect() 来获取页面元素的位置
2010/11/25 Javascript
NodeJS创建基础应用并应用模板引擎
2016/04/12 NodeJs
jQuery实用小技巧_输入框文字获取和失去焦点的简单实例
2016/08/25 Javascript
JS返回只包含数字类型的数组实例分析
2016/12/16 Javascript
详解js中Number()、parseInt()和parseFloat()的区别
2016/12/20 Javascript
微信小程序 支付功能(前端)的实现
2017/05/24 Javascript
Ionic + Angular.js实现验证码倒计时功能的方法
2017/06/12 Javascript
用jQuery将JavaScript对象转换为querystring查询字符串的方法
2018/11/12 jQuery
Vue.js样式动态绑定实现小结
2019/01/24 Javascript
微信小程序实现的绘制table表格功能示例
2019/04/26 Javascript
小程序实现简单语音聊天的示例代码
2020/07/24 Javascript
在vue中实现给每个页面顶部设置title
2020/07/29 Javascript
python requests 使用快速入门
2017/08/31 Python
tensorflow输出权重值和偏差的方法
2018/02/10 Python
解决Pytorch训练过程中loss不下降的问题
2020/01/02 Python
使用python+poco+夜神模拟器进行自动化测试实例
2020/04/23 Python
Django如何批量创建Model
2020/09/01 Python
一款纯css3实现的响应式导航
2014/10/31 HTML / CSS
HTML5 中新的全局属性(整理)
2013/07/31 HTML / CSS
HTML5本地存储之Web Storage详解
2016/07/04 HTML / CSS
struct与class的区别
2014/02/03 面试题
一份软件工程师的面试试题
2016/02/01 面试题
奶茶店创业计划书范文
2014/01/17 职场文书
公司联欢晚会主持词
2014/03/22 职场文书
第一批党的群众路线教育实践活动总结报告
2014/07/03 职场文书
班子四风对照检查材料
2014/08/21 职场文书
交通事故委托书范本(2篇)
2014/09/21 职场文书
2016学习依法治国心得体会
2016/01/15 职场文书
Python 恐龙跑跑小游戏实现流程
2022/02/15 Python
源码分析Redis中 set 和 sorted set 的使用方法
2022/03/22 Redis