参考tp5,实现一个简单的容器

    技术2022-07-11  82

    本文目录

    注册树模式后期静态绑定自动加载反射实现对类的方法依赖注入和构造函数依赖注入 今天翻看tp5的源码时,看到了入口文件 index.php 中的 container 类,初看有些不解 通过查看代码与文档,得知是使用了容器来动态的实现类的加载,在开始前,是我们需要了解的几个知识点:

    注册树模式后期静态绑定自动加载依赖注入控制反转(Ioc)反射机制单例模式闭包函数

    我们先定义第一个文件,这里很重要,主要是实现自动加载,当实例化一些不存在的类时根据命名空间去引入

    <?php /** * Created by PhpStorm. * User: Administrator * Date: 2020-07-03 * Time: 09:59 */ //定义根目录路径 define('ROOT_PATH', str_replace('\\','/',dirname(dirname(__FILE__))) ); //自动加载文件 spl_autoload_register('autoLoad',true,true); function autoLoad($class_name){ $arr = explode('\\', $class_name); if (count($arr) > 0) { $path = ''; foreach($arr as $k=> $v){ $path .= '/'.$v; } $file = ROOT_PATH."$path.php"; @include_once $file; } }

    定义好之后,我们新建一个文件夹,并创建一些demo类 其他的 Config.php、demo1.php、demo2.php、demo3.php、Env.php、Log.php 结构与上方的App.php一致

    接下来定义我们的容器类 Container.php 吧

    <?php /** * Created by PhpStorm. * User: Administrator * Date: 2020-07-01 * Time: 16:47 */ namespace Container\basics; require_once('../Register.php'); class Container { /** * 容器对象实例 * @var Container */ protected static $instance; /** * 容器中的对象池 * @var array */ protected $instances = []; /** * 容器绑定标识 * @var array */ protected $bind = [ 'app' => App::class, 'config' => Config::class, 'env' => Env::class, 'log' => Log::class, ]; /** * 获取容器实例(单例模式) * 后期静态绑定 * @return Container */ public static function getInstance(){ if (is_null(static::$instance)){ static::$instance = new static; } return static::$instance; } /** * 设置当前容器的实例 * 后期静态绑定 * @param $instance */ public static function setInstance($instance){ static::$instance = $instance; } /** * 获取当前容器中的实例 * @param $instance */ public static function get($instance){ return static::getInstance()->make($instance); } /** * 设置当前容器中的实例 * @param $abstract * @param null $concrete */ public static function set($key,$value){ return static::getInstance()->bindTo($key,$value); } /** * 利用反射机制,往容器中挂载对象 * @param $className * @param string $key * @return object * @throws \ReflectionException */ public function make($className){ if (!empty($this->instances[$className])){ $className = $this->instances[$className]; }else if(!empty($this->bind[$className])){ return new $this->bind[$className]; } $reflect = new \ReflectionClass($className); $get_construct = $reflect->getConstructor(); if (!$get_construct){ return new $className(); } $param = $get_construct->getParameters(); if (!$param){ return new $className; } foreach($param as $value){ $class = $value->getClass(); if ($class){ $args[] = $this->make($class->name); } } return $reflect->newInstanceArgs($args); } /** * 判断传入参数是对象、命名空间、闭包(暂时只支持三种) * @param $key * @param null $value * @return $this */ public function bindTo($key,$value= null){ if (is_object($value)){ //判断值是否为对象 if (!empty($this->bind[$key])){ $key = $this->bind[$key]; } $this->instances[$key] = $value; }else if ($value instanceof \Closure){ //判断值是否为闭包 $this->instances[$key] = $value; }else if(is_string($value)){ //判断是命名空间 if (empty($this->bind[$key])){ $this->instances[$key] = $value; }else{ $this->instances[$key] = new $this->bind[$key](); } } else{ //其他判断 throw new \Exception('该类型不支持挂载,只支持对象或闭包函数'); } return $this; } } echo '<pre>'; Container::set('app','Container\basics\App'); $res = Container::get('app'); var_dump($res::run());

    上面的这个容器,是笔者模仿tp5.1的服务容器,实现的一个简单的容器,下面我们开始解读代码

    注册树模式

    首先,服务容器的结构,类似于注册树模式,下面是一个简单的注册树模式

    class Objpool{ /** * 存储对象的变量 * @var */ private static $pool; /** * 往树上挂载对象 * @param $key * @param $value */ public static function set($key,$value){ self::$pool[$key] = $value; } /** * 从树上摘取对象 * @param $key * @return mixed */ public static function get($key){ if (empty(self::$pool[$key])){ self::$pool[$key] = new $key; } return self::$pool[$key]; } /** * 销毁对象 * @param $key * @return Exception */ public static function _unset($key){ if (empty(self::$pool[$key])){ return new Exception("对象池中不存在该对象"); } unset(self::$pool[$key]); } }

    从代码上看,我们的容器是否与注册树模式结构相似? 其实容器就是采用了注册树模式,将对象挂载到树上备用,需要用时再那些来使用,就不用每用到一些类就去new。

    后期静态绑定

    而Container.php中的 $bind 这里采用的 类名::class,使用 ClassName::class 可以获取一个字符串,包含了类 ClassName 的完全限定名称。 什么意思呢,这里的 APP::class 中的App代表的是App这个类,而App::class 的作用就是拿到这个类下面的命名空间。关系可看下图

    继续往下面走,我们可以看到代码中有一些 static::$instance 或者 new static 的语法

    static:: 这个关键字能够让你在继承中,父类与子类调用 方法时引用的类是 B 而不是 A同理 new static new self 都是实例化当前类, 但是new static只有代码所在的类,就是子类如果没有重写的话那么实例化的就是父类。 而new self就是严格的当前类

    这里可以参考笔者的另外一篇文章会有更细的讲解 php new static与new self 的区别

    自动加载

    在项目中,我们类与类文件都分开的,但是文中我们的代码又是直接去new 类名 ,这样子不会报错吗?如下图

    我们的容器牵扯到一个自动加载的问题 ,可以利用 spl_autoload_register() 内置函数去解决 ,在容器中,我们是需要根据命名空间去引入文件的,每次当我们实例化不存在的类时,应该都去寻找这个类,相关的资料,大家可以看笔者的这篇文章 spl_autoload_register 实现自动加载 以及多次调用

    反射实现对类的方法依赖注入和构造函数依赖注入

    笔者在没有看源码前,还以为php的语法上面支持对类的方法依赖注入(解决了像类中的方法传对象的问题),查看后源码才得知,原来是:

    先通过自动加载,利用反射机制去获取其构造函数的参数再递归调用最后实例化出对象

    这里的话,有两个点需要了解,一个是依赖注入,另外一个是控制反转。关于这块知识,大家可以看看 IOC基础,另外笔者在写这篇文章之前,也有写过一篇文章是关于 反射利用反射机制完成动态用户权限(RBAC) 的文章,大家有兴趣的话,也可以看看。

    总结:       服务容器是用来管理类依赖于运行依赖注入的工具,它是整个tp的核心,提供整个系统功能及服务的配置,容器字面上的意思就是装东西的物品。       而上面的demo只是服务容器的冰山一角,虽然了解到原理,但要实现完整的容器远远不止这么简单,总而言之,服务容器最大的好处将创建对象的步骤交给容器管理,配合服务提供者使用,能大大降低个个模块的耦合度。

    Processed: 0.012, SQL: 9