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::插入一些数据
    }
} 

Then let's take a look at our business class.

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

At this point we seem to be completing our class needs, then what if you want to switch to MongoDb? Then we have to write a MongoDb operation class, and then go to our Order class to modify the DB's dependencies, for example: Define the Mongo Db class

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

Then we continue to modify our business class

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

Obviously this is a very bad design, the components are highly coupled, and it also undermines the open and closed principle and violates the DIP principle. High-level business modules should not maintain dependencies, and the two should abstract the dependencies. So before I said that the emergence of IOC seems to solve this problem, then what method does it provide?

IOC is a big concept. There are many implementations based on this model, but there are two mainstream types: Dependency Lookup (DL), Dependency Injection (DI) , and Swoft uses 依赖注入(DI) technology. We also mainly introduce a description of 依赖注入DI , and friends who are interested in 依赖查找(DL) can make their own Google.

DI - dependency injection

DI is an important way to implement IOC. As mentioned in the previous example, it is very bad practice to create and bind dependencies in the business. 依赖注入DI is to solve this problem, it provides an implementation. How to implement a reference to an object that depends on the underlying module (MysqlDb, MongoDb, etc.) to be used by the dependent object (business module), so how is it implemented?

DI mainly implements dependency injection in two ways: 构造函数注入 and 属性注入 .

Constructor injection , as its name implies, is the use of the object's constructor to pass the required dependency module to the object. Let's take a look at the example.

Due to the principles of DIP, we should not create dependencies inside the module. That is, high-level modules should not depend on the underlying module. The two should rely on abstraction , so let's define an interface first.

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

Then we will implement this interface for our database classes.

 /**
 * 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::插入一些数据
    }
} 

Then rewrite our business class.

 /**
 * 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();//执行入库操作
    }
} 

So far we have completed the implementation of 构造函数注入 , so that we do not need to care about who I should rely on in the business (high-level module), but through a third party (remember the example of the previous plug The behavior of the u disk) to complete the creation of the dependency, as reflected in the code.

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

In this way, we will transfer our dependencies from the inside to the outside. In fact, this is the core idea of IOC. It is the high-level interface. For example, when we need to replace the Db driver with redis, we only need to write the redis class and implement DbDrive. The interface can then directly 构造函数注入 redis class into the required dependencies by 构造函数注入 or 属性注入 in the place where the business is called, without modifying our business class. The way of 属性注入 is similar. The operation is just different in the way of injection. It is not explained too much here through attribute injection.

Through the above introduction, we have actually implemented DIP, IOC, DI, and we all know what they are, so what is the relationship between them and the container? Whose container is the container? What did you do? Let's take a look at some of these issues below.

IOC Container - IOC Container

The container is also called the IOC container. Through the case of the previous chapter, we implemented the IOC control inversion through DI, but we found that we have to manually create the dependent object and then pass it to the high-level module to use it. Obviously, this way is still flawed. And the efficiency is very low, and even problems that are difficult to control can occur. Suppose that our business has more than a dozen or so dependencies, and there are still problems such as nesting dependencies. In actual work, this situation will be difficult to handle. We use pseudocode to describe this situation (try to go up from the last line) read):

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

At this point, many problems have been exposed, including but not limited to the previous problems, such as the object life cycle.

At this time, a new technology emerged, that is, the IOC container , which is used to solve the above problems. His main functions are:

  • Automatic management of dependencies to avoid manual management defects.
  • Automatically inject the required dependencies into us when we need to use dependencies.
  • Manage the life cycle of an object

To better understand the container, let's implement a simple container 构造函数注入 through the 构造函数注入 . ( Note that this is just for science teaching, and Svoft has prepared a more powerful and easy-to-use IOC container for everyone).

 <?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 actually an object instance of a class, except that 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 beans.

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