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


今天翻看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 的语法
在这里插入图片描述

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

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

自动加载

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

在这里插入图片描述

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


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

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

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

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

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

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值