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 excel类 phpExcel使用方法介绍
Aug 21 PHP
php ci框架中加载css和js文件失败的解决方法
Mar 03 PHP
浅谈php扩展imagick
Jun 02 PHP
php实现的一个简单json rpc框架实例
Mar 30 PHP
php ucwords() 函数将字符串中每个单词的首字符转换为大写(实现代码)
May 12 PHP
Yii2 GridView实现列表页直接修改数据的方法
May 16 PHP
php pdo oracle中文乱码的快速解决方法
May 16 PHP
浅谈php中的访问修饰符private、protected、public的作用范围
Nov 20 PHP
PHP isset()与empty()的使用区别详解
Feb 10 PHP
PHP定义字符串的四种方式详解
Feb 06 PHP
ThinkPHP框架结合Ajax实现用户名校验功能示例
Jul 03 PHP
关于php开启错误提示的总结
Sep 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中file_exists与is_file,is_dir的区别介绍
2012/09/12 PHP
PHP数据库链接类(PDO+Access)实例分享
2013/12/05 PHP
php获取URL中带#号等特殊符号参数的解决方法
2014/09/02 PHP
php实现统计目录文件大小的函数
2015/12/25 PHP
js取滚动条的尺寸的函数代码
2011/11/30 Javascript
jquery ready(fn)事件使用介绍
2013/08/21 Javascript
zTree插件之单选下拉菜单实例代码
2013/11/07 Javascript
js中reverse函数的用法详解
2013/12/26 Javascript
js判断变量初始化的三种形式及推荐用的形式
2014/07/22 Javascript
纯JavaScript实现的兼容各浏览器的添加和移除事件封装
2015/03/28 Javascript
JQuery select(下拉框)操作方法汇总
2015/04/15 Javascript
逐一介绍Jquery data()、Jquery stop()、jquery delay()函数(详)
2015/11/04 Javascript
JavaScript的removeChild()函数用法详解
2015/12/27 Javascript
解析javascript瀑布流原理实现图片滚动加载
2016/03/10 Javascript
微信小程序 textarea 详解及简单使用方法
2016/12/05 Javascript
使用contextMenu插件实现Bootstrap table弹出右键菜单
2017/02/20 Javascript
BootStrap daterangepicker 双日历控件
2017/06/02 Javascript
bootstrap table方法之expandRow-collapseRow展开或关闭当前行数据
2020/08/09 Javascript
java和js实现的洗牌小程序
2019/09/30 Javascript
vue实现图片上传到后台
2020/06/29 Javascript
Vue实现简单的拖拽效果
2020/08/25 Javascript
JavaScript 事件代理需要注意的地方
2020/09/08 Javascript
对Django的restful用法详解(自带的增删改查)
2019/08/28 Python
彪马美国官网:PUMA美国
2017/03/09 全球购物
理肤泉加拿大官网:La Roche-Posay加拿大
2018/07/06 全球购物
一道Delphi上机题
2012/06/04 面试题
计算机专业毕业生的自我评价
2013/11/18 职场文书
高中生家长会演讲稿
2014/01/14 职场文书
万年牢教学反思
2014/02/15 职场文书
《地震中的父与子》教学反思
2014/04/10 职场文书
装修协议书范本
2014/04/21 职场文书
工程材料采购方案
2014/05/18 职场文书
宇宙与人观后感
2015/06/05 职场文书
python3美化表格数据输出结果的实现代码
2021/04/14 Python
git中cherry-pick命令的使用教程
2022/06/25 Servers
windows server2016安装oracle 11g的图文教程
2022/07/15 Servers