container

Foreword

Swoft designed its own container based on the PSR-11 specification and enhanced its functionality based on 注解 . The container is the most important design of Swoft. It can be said that it is the core essence of Svoft and the basis for the implementation of various modules of Swoft. This chapter will introduce the basic knowledge of the container so that novice friends can better understand the container.

What is a container

Let's take a look at the more official definition.

Inversion of Control (abbreviated as IoC) is a design principle in object-oriented programming that can be used to reduce the degree of coupling between computer code. The most common way is called Dependency Injection (DI), and there is another way called Dependency Lookup. By controlling inversion, when an object is created, it is passed (injected) to a reference to the object it depends on by an external entity that regulates all objects in the system. --"Wikipedia"

I didn't talk about the container here, but just said a design principle called IOC . There is also a noun called DI dependency injection. It seems that there is nothing in the container. Why? To figure out the problem first, we still need to explain some technical terms first.

DIP - Dependency Inversion Principle

Dependence Inversion Principle (DIP) is an abstract software design principle. It mainly tells us some specifications. Let us first look at the official definition of this principle.

High-level modules should not rely on low-level modules, and both should rely on abstractions.

Abstraction should not depend on implementation, implementation should rely on abstraction.

What is the specific meaning? We can explain this principle through an example. Everyone should be familiar with the USB socket on the computer. Through it, we can expand various peripheral capabilities, such as U disk, gaming mouse, etc. The interfaces are the same, and the device can work normally when plugged in.

In this example, our computer is equivalent to a high-level module, and a USB flash drive, mouse, etc. is equivalent to the underlying module. Our computer defines a socket (interface) that can be plugged into other devices, but our computer does not depend on the specific device to be plugged in. It just defines an interface specification as long as it conforms to the interface specification. Can be plugged into this computer to use. It corresponds to the software development point of view refers to the Dependency Inversion Principle converted dependency, requiring high-level modules should not depend on the underlying implementation of the module, the module depends on the underlying layer module interface defined.

The above example only clarifies the definition of the 依赖倒置原则(DIP) . Let's take a look at a scenario of software development to see why we need to design this principle.

Scenario: For example, we have a business that needs to perform data storage operations on the data. The target may be MySql, MongoDb, etc.

Implementation one: no dependency inversion, that is, the underlying definition interface, high-level module implementation.

No dependency inversion UML

As shown in the figure, in the case of non-practical dependency inversion, we have to complete the function of this scenario very much, our business class (high-level module) to implement all DB class (underlying module) interface, if there is a new DB class (underlying module) intervention, then need to modify the business class (high-level module) to implement the new DB class (underlying module) which breaks the open and closed principle of the class.

Implementation two: use dependency inversion, that is, high-level definition interface, the implementation of the underlying module.

Dependently inverting UML

As shown in the figure, after we use dependency inversion, our business class (high-level module) will no longer depend on the DB class (the underlying module) but the DB class (the underlying module) is responsible for implementing the interface of the business class (high-level module), so even We also do not need to modify the business module when the new underlying module is added to the business.

This shows the benefits of using DIP:

  • The abstraction of each class or module can be made independent of each other without affecting each other, and loose coupling (also essential) between modules can be realized;

  • You can avoid problems caused by non-technical factors (such as the probability of a change in demand when the project is large, and constrain the implementation class by using an interface or abstract class designed by the dependency inversion principle, which can reduce the surge in workload caused by the change in demand. At the same time, personnel changes, as long as the documentation is perfect, can also be easily extended and maintained by maintenance personnel);

  • Can promote parallel development (for example, there are dependencies between the two classes, as long as the interface between the two (or abstract classes) can be developed independently, and unit tests between projects can also run independently, and The TDD development model is the most advanced application of DIP (especially suitable for use when the overall level of the project staff is low).

IOC - Control Inversion

Through the introduction of the previous section, we understand the DIP-dependent inversion principle, but it is just a 软件设置原则 It just provides us with a standard so that we can follow and avoid bad design, but it does not tell us these standards. How to achieve.

So here is the introduction of IOC, a 软件设计模式 that provides a detailed solution for how we implement DIP. It is defined as: providing abstraction for interdependent components, and passing the access to the third party for control, ie the dependent object is not obtained by the dependent module . in

In the DIP - Dependency Inversion Principles section, we cite the example of a USB socket. The general meaning of IOC is that our computer does not have the ability to plug in specific devices, but is controlled by human (third party) insertion. Let's take a closer look at the IOC with examples of previous software development scenarios in DIP.

For example, our business is an order-inbound operation. At the beginning we just store the data in Mysql. Usually we will encapsulate a MysqlDb class for database operations.

/**
 * Class MysqlDb
 *
 * @since 2.0
 */
class MysqlDb
{
    public function insert()
    {
        //TODO::插入一些数据
    }
}

紧接着我们看一下我们的业务类

/**
* Class Order
*
* @since 2.0
*/
class Order
{
    public function add()
    {
        //TODO::订单业务
        $db = new MysqlDb();//建立依赖
        $db->insert();//执行入库操作
    }
}

至此我们看似是完成类我们的需求,那这时如果让你改用 MongoDb 怎么办?那我们要先去写一个 MongoDb 的操作类,然后再去我们的 Order 类中修改 DB 的依赖,例如: 定义 Mongo Db 类

/**
 * Class MongoDb
 *
 * @since 2.0
 */
class MongoDb{
    public function insert()
    {
        //TODO::插入一些数据
    }
}

然后我们继续修改我们的业务类

/**
* Class Order
*
* @since 2.0
*/
class Order
{
    public function add()
    {
        //TODO::订单业务
//        $db = new MysqlDb();//将MysqlDb更改为MongoDb
        $db = new MongoDb();//建立依赖
        $db->insert();//执行入库操作
    }
}

显然这是一个非常糟糕的设计,组件之间还是高度耦合的,同时也破坏了开放封闭原则,也违背了DIP原则。高层的业务模块不应该维护依赖关系,两者应该将依赖抽象出来。那么之前说过 IOC 的出现好像就是为了解决这个问题的,那么它提供了什么方法呢?

IOC 是一个很大的概念,基于这个模式可以有很多种实现方式,但是其主流的有两种:依赖查找(Dependency Lookup 简称DL),依赖注入(Dependency Injection 简称 DI) ,Swoft 使用的是 依赖注入(DI) 技术。我们也主要对 依赖注入DI 进行一个说明介绍,对 依赖查找(DL)感兴趣的朋友可自行 Google。

DI - 依赖注入

DI 是实现 IOC 的一种重要方式,如之前的例子所述,我们将依赖关系在业务中进行创建和绑定是非常糟糕的做法,依赖注入DI 就是解决这种问题,它提供一种实现方式,将需要依赖的底层模块(MysqlDb,MongoDb等)的对象的引用传递给被依赖对象(业务模块)去使用,那么它是如何实现的呢?

DI 主要是由两种方式来实现依赖注入:构造函数注入属性注入

构造函数注入顾名思义,它就是通过对象的构造函数将所需的依赖模块传递给对象使用。下面我们来看一下例子

由于DIP 的原则,我们不应该再模块内部来创建依赖关系即高层模块不应该依赖于底层模块,两者应该依赖于抽象,所以我们先来定义一个接口。

/**
 * Interface DbDrive
 *
 * @since 2.0
 */
interface DbDrive
{
    public function insert();
}

然后我们将我们的数据库类都实现这个接口

/**
 * Class MysqlDb
 *
 * @since 2.0
 */
class MysqlDb implements DbDrive
{
    public function insert()
    {
        //TODO::插入一些数据
    }
}

/**
 * Class MongoDb
 *
 * @since 2.0
 */
class MongoDb implements DbDrive
{
    public function insert()
    {
        //TODO::插入一些数据
    }
}

然后改写一下我们的业务类。

/**
 * Class Order
 *
 * @since 2.0
 */
class Order
{
    /**
     * @var DbDrive
     */
    private $db;

    /**
     * Order constructor.
     *
     * @param DbDrive $driver
     */
    public function __construct(DbDrive $driver)
    {
        $this->db = $driver;
    }

    public function add()
    {
        //TODO::订单业务
        $this->db->insert();//执行入库操作
    }
}

至此我们就已经完成了构造函数注入的实现方式,这样我们就不需要在业务中(高层模块)关心我该依赖于谁来做那些事情,而是通过第三方(还记得之前举例子的人插u盘的行为)来完成依赖关系的创建,体现为代码就是这样。

$db = new MysqlDb();//创建一个依赖,这就好比是一个u盘
$order = new Order($db);//将需要依赖的对象通过构造函数传递进去,这就好比插入u盘
$order->add();//正常的去调用业务。

这样,我们就将我们的依赖关系从内部转移到了外部,其实这也就是IOC的核心思想,就是高层做接口,比如我们这时候需要更换Db驱动为 redis,则我们只需要编写 redis 类并实现 DbDeive 接口即可然后在业务调用的地方直接将 redis 类通过 构造函数注入 或者 属性注入 的方式注入需要的依赖就可以了,而无需修改我们的 业务类 。属性注入 的方式也是类似的操作只是注入的方式不同,是通过属性注入的这里就不做过多赘述了。

通过以上的介绍我们已经其实已经实现了 DIP,IOC,DI,也都清楚了它们都是什么,那么它们和容器的关系是什么呢?容器又是谁的容器?做了什么呢?下面我们对这些问题进行一些讲解。

IOC Container - IOC容器

容器又叫 IOC 容器,通过之前章节的案例我们通过 DI 实现了 IOC 控制反转,但是我们发现我们要手动的去创建依赖对象,然后再传递给高层模块去使用,显然这样的方式还是有缺陷的,并且效率很低,甚至会出现难以掌控的问题出现。假设我们的业务有十几上百个依赖,并且还存在依赖嵌套等问题,实际工作中这种情况便会很难处理,我们用伪代码来描述下这种情况(试着从最后一行向上阅读):

$validator = new Validator();//最后一层的依赖
$check = new Check($validator);//我们的业务检查类,同时它又依赖于一个验证器。
$db = new Mysql();//Db类
$user = new User($db,$check);//我们的用户类业务,同时它又依赖于一个db类,和一个检查类业务
$order = new Order($user);//我们的订单业务,它依赖于一个用户类业务

此时就报漏出来了很多问题,包括但不限于之前的问题,例如还有对象生命周期等问题。

这时出现了一个新的技术那就是 IOC 容器,就是用来解决上述的问题,他的主要功能就是:

  • 自动的管理依赖关系,避免手工管理的缺陷。
  • 再需要使用依赖的时候自动的为我们注入所需依赖。
  • 管理对象的声明周期

为了更好的理解容器,我们来实现一个简单的通过 构造函数注入 的容器,(注意 这只是为了科普教学,Swoft已经为大家准备了更加完善强大且易用的 IOC 容器)。

<?php

/**
 * Class Container
 */
class Container
{
    /**
     * 容器内所管理的所有实例
     * @var array
     */
    protected $instances = [];

    /**
     * @param $class
     * @param null $concrete
     */
    public function set($class, $concrete = null)
    {
        if ($concrete === null) {
            $concrete = $class;
        }
        $this->instances[$class] = $concrete;
    }

    /**
     * 获取目标实例
     *
     * @param $class
     * @param array $param
     *
     * @return mixed|null|object
     * @throws Exception
     */
    public function get($class, ...$param)
    {
        // 如果容器中不存在则注册到容器
        if (!isset($this->instances[$class])) {
            $this->set($class);
        }
        //解决依赖并返回实例
        return $this->resolve($this->instances[$class], $param);
    }

    /**
     * 解决依赖
     *
     * @param $class
     * @param $param
     *
     * @return mixed|object
     * @throws ReflectionException
     * @throws Exception
     */
    public function resolve($class, $param)
    {
        if ($class instanceof Closure) {
            return $class($this, $param);
        }
        $reflector = new ReflectionClass($class);
        // 检查类是否可以实例化
        if (!$reflector->isInstantiable()) {
            throw new Exception("{$class} 不能被实例化");
        }
        // 通过反射获取到目标类的构造函数
        $constructor = $reflector->getConstructor();
        if (is_null($constructor)) {
            // 如果目标没有构造函数则直接返回实例化对象
            return $reflector->newInstance();
        }

        // 获取构造函数参数
        $parameters = $constructor->getParameters();
        //获取到构造函数中的依赖
        $dependencies = $this->getDependencies($parameters);
        // 解决掉所有依赖问题并返回实例
        return $reflector->newInstanceArgs($dependencies);
    }

    /**
     * 解决依赖关系
     *
     * @param $parameters
     *
     * @return array
     * @throws Exception
     */
    public function getDependencies($parameters)
    {
        $dependencies = [];
        foreach ($parameters as $parameter) {
            $dependency = $parameter->getClass();
            if ($dependency === null) {
                // 检查是否有默认值
                if ($parameter->isDefaultValueAvailable()) {
                    // 获取参数默认值
                    $dependencies[] = $parameter->getDefaultValue();
                } else {
                    throw new Exception("无法解析依赖关系 {$parameter->name}");
                }
            } else {
                // 重新调用get() 方法获取需要依赖的类到容器中。
                $dependencies[] = $this->get($dependency->name);
            }
        }

        return $dependencies;
    }
}

class MysqlDb
{
    public function insert()
    {
        echo 'mysql';
    }
}

class Order
{
    private $db;

    public function __construct(MysqlDb $db)
    {
        $this->db = $db;
    }

    public function add()
    {
        $this->db->insert();
    }

}

$container = new Container();//使用容器
$order = $container->get('Order');//通过容器拿到我们的Order类
$order->add();//正常的使用业务

We mainly use the reflection class to complete the automatic injection of the container. In other words, the container is actually like a factory pattern. Using the container is similar to using the factory. It will help us solve the dependencies and then return to our object example. The top is just a simple demo, and there are many places that are not considered, such as the handling of the loop mechanism, the caching of objects, lifecycle management, and so on.

However, the Swoft framework has provided us with a very complete and easy-to-use IOC container, and we'll cover how to use it in a later chapter.

What is a Bean

At the end of the introduction, there is a small concept, that is, Bean a previous knowledge, we can quickly describe what is a Bean .

Let us first look at the definition:

  • A bean is an instance managed by an IOC container.

In other words, Bean is an example of a class object, but it is an object instantiated, assembled, and managed by the IOC 容器 .

IOC container can be thought of as a collection of Beans relationships. Our application consists of a number of Bean .

BeanFactory provides an advanced configuration mechanism to manage any kind of bean.

The definition of the Bean must have a BeanDefinition description: when the 配置文件 / 注解 is parsed, it will be internally converted into a BeanDefinition object. Subsequent operations are done on this object.

What are Beans?

Beans are not just equal to `@Bean`, although in most cases they refer to the same thing.

As follows, all class annotation tag classes can be called Bean objects in the container.

Class annotations, for example:

  • @Bean most commonly used bean annotations
  • @Listener
  • @Controller
  • @Command
  • @WsModule
  • @WsController
  • and many more...
/docs/2.x/en/bean/index.html
progress-bar