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
桌面中心(一)创建数据库
Oct 09 PHP
浅析PHP水印技术
Feb 14 PHP
PHP 翻页 实例代码
Aug 07 PHP
php Calender(日历)代码分享
Jan 03 PHP
Laravel 4 初级教程之Pages、表单验证
Oct 30 PHP
ucenter通信原理分析
Jan 09 PHP
php实现统计网站在线人数的方法
May 12 PHP
PHP判断上传文件类型的解决办法
Oct 20 PHP
php使用PDO执行SQL语句的方法分析
Feb 16 PHP
Laravel框架实现简单的学生信息管理平台案例
May 07 PHP
php 利用socket发送GET,POST请求的实例代码
Jul 04 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上传文件的增强函数
2010/07/21 PHP
php 修改上传文件大小限制实例详解
2016/10/23 PHP
Jquery知识点一 Jquery的ready和Dom的onload的区别
2011/01/15 Javascript
jQuery中setTimeout的几种使用方法小结
2013/04/07 Javascript
javascript使用定时函数实现跳转到某个页面
2013/12/25 Javascript
jquery制作居中遮罩层效果分享
2014/02/21 Javascript
jQuery 获取、设置HTML或TEXT内容的两种方法
2014/05/23 Javascript
用console.table()调试javascript
2014/09/04 Javascript
JavaScript之Object类型介绍
2015/04/01 Javascript
基于nodejs+express(4.x+)实现文件上传功能
2015/11/23 NodeJs
jQuery隐藏和显示效果实现
2016/04/06 Javascript
JS实现的打字机效果完整实例
2016/06/20 Javascript
内容滑动切换效果jquery.hwSlide.js插件封装
2016/07/07 Javascript
Boostrap实现的登录界面实例代码
2016/10/09 Javascript
判断div滑动到底部的scroll实例代码
2017/11/15 Javascript
Node.js使用Koa搭建 基础项目
2018/01/08 Javascript
微信小程序scroll-view锚点链接滚动跳转功能
2019/12/12 Javascript
Node.js实现批量下载图片简单操作示例
2020/01/18 Javascript
Vue.js 中制作自定义选择组件的代码附演示demo
2020/02/28 Javascript
ES6箭头函数和扩展实例分析
2020/05/23 Javascript
[00:35]DOTA2上海特级锦标赛 Newbee战队宣传片
2016/03/03 DOTA
Python pickle模块用法实例分析
2015/05/27 Python
多个应用共存的Django配置方法
2018/05/30 Python
Python实现的远程登录windows系统功能示例
2018/06/21 Python
解决python执行不输出系统命令弹框的问题
2019/06/24 Python
Python处理时间日期坐标轴过程详解
2019/06/25 Python
Ubuntu+python将nii图像保存成png格式
2019/07/18 Python
PyCharm搭建Spark开发环境的实现步骤
2019/09/05 Python
Django REST Framework之频率限制的使用
2019/09/29 Python
jupyter notebook中新建cell的方法与快捷键操作
2020/04/22 Python
Groupon西班牙官方网站:在线优惠券和交易,节省高达70%
2021/03/13 全球购物
数学国培研修感言
2014/02/13 职场文书
2014年初三班主任工作总结
2014/12/05 职场文书
如何在centos上使用yum安装rabbitmq-server
2021/03/31 Servers
使用Apache Camel表达REST服务的方法
2022/06/10 Servers
MySQL新手入门进阶语句汇总
2022/09/23 MySQL