浅谈thinkphp的服务容器

发布于 2020-06-13 | 最后更新 2020-11-24 | 浏览量 118


服务容器是用来管理类依赖与运行依赖注入的工具。它是整个 thinkphp 的核心,提供了整个系统功能及服务的配置。容器字面上的意思就是装东西的物品,比如罐子:

image

当框架启动的时候,我们把需要的一些服务放进容器里面,等到需要的时候取出来就可以了。

thinkphp里实现容器的主要概念是单例模式注册数模式

单例模式

单例模式在内存中只有一个实例,特别是一个对象需要频繁地创建、销毁时,单例模式的优势就非常明显。实际项目中像数据库查询,日志输出等模块。这些模块功能单一,但需要多次访问,如果能够全局唯一,多次复用会大大提升性能。这也就是单例存在的必要性。

<?php

class Singleton
{

    private static $instance = null;

    public static function getInstance()
    {
        //第一次访问,实例化
        if (is_null(self::$instance)) {
            echo 'new';
            self::$instance = new self();
        }
        return self::$instance;
    }

}

Singleton::getInstance();
Singleton::getInstance();
Singleton::getInstance();
//输出:new

上面代码简单的实现了单例模式。因为类静态对象是全局变量,在php-fpm下,只有每次请求结束后才会被释放。单次请求内,第二次调用Singleton::getInstance()之后会直接读取全局的Singleton::$instance,所以代码结果只会输出一次new

注册树模式

注册树模式也叫注册器模式,和单例模式基本思想类似,注册模式为应用中经常使用的对象创建一个中央存储器来存放这些对象。可以理解为,注册模式将对象实例注册到一颗全局的对象树上,需要用到的时候从对象树上摘下来的设计方法。

我们来看看thinkphp6.0的 \think\Container 容器类:

class Container implements ContainerInterface, ArrayAccess, IteratorAggregate, Countable
{
    /**
     * 容器中的对象实例
     * @var array
     */
    protected $instances = [];
}

里面定义了一个$instances变量,可以理解为对象树的存储器,用来存放各种实例。

实例是怎样注册到树上的呢,我们接着找:

    /**
     * 绑定一个类实例到容器
     * @access public
     * @param string $abstract 类名或者标识
     * @param object $instance 类的实例
     * @return $this
     */
    public function instance(string $abstract, $instance)
    {
        //获取真实类名,用作实例的标识
        $abstract = $this->getAlias($abstract);
        //把实例注册到树上
        $this->instances[$abstract] = $instance;

        return $this;
    }

很简单是不是,接着找摘取的方法。

    /**
     * 创建类的实例 已经存在则直接获取
     * @access public
     * @param string $abstract    类名或者标识
     * @param array  $vars        变量
     * @param bool   $newInstance 是否每次创建新的实例
     * @return mixed
     */
    public function make(string $abstract, array $vars = [], bool $newInstance = false)
    {
        $abstract = $this->getAlias($abstract);
        //已经注册过了,直接返回
        if (isset($this->instances[$abstract]) && !$newInstance) {
            return $this->instances[$abstract];
        }
        //没注册过,根据参数生成实例,并注册到对象树上
        if (isset($this->bind[$abstract]) && $this->bind[$abstract] instanceof Closure) {
            $object = $this->invokeFunction($this->bind[$abstract], $vars);
        } else {
            $object = $this->invokeClass($abstract, $vars);
        }

        if (!$newInstance) {
            $this->instances[$abstract] = $object;
        }

        return $object;
    }

这些都不难理解,可以总结出来这里注册树的三个点:

  • 定义存储器 protected $instances = []
  • 绑定实例
  • 取出实例

自己动手实现一个简单的容器

<?php

class Container
{
    protected $instances = [];

    public function instance($class, $instance)
    {
        $this->instances[$class] = $instance;
    }

    public function make($class)
    {
        if (!isset($this->instances[$class])) {
            $this->instances[$class] = new $class;
        }
        return $this->instances[$class];
    }
}
class Http {}
class Log {}

$container = new Container();

$container->instance('Http', new Http());

$http = $container->make('Http');
var_dump($http);//因为绑定过Http实例,所以直接返回Http类
$log = $container->make('Log');
var_dump($log);//Log首先被绑定,再返回

image

有没有人发现,这代码很眼熟。没错,注册器模式是单例模式的升级版。单例模式只是针对一个单独的实例进行操作,注册树更像一个盒子,将要用或者正在调用的实例放在盒子中。

这只是服务容器的冰山一角,虽然了解到了原理,但要实现完整的容器远远不止这么简单。总而言之,服务容器最大的好处将创建对象的步骤交给容器管理,配合服务提供者使用,能大大降低个个模块的耦合度。
公告

本博客基于 Laravel 搭建,点击 Github 了解更多,如果你喜欢本博客,欢迎 Star, 感谢!