Dependency injection and detailed explanation of Ioc container for advanced PHP learning

background

In the development of many programming languages ​​(such as java), programmers need to rely on methods of other classes in a certain class. Usually new is a method that relies on the class and then calls the class instance. The problem with this kind of development is the new class instance It is not easy to manage in a unified way. Once there is a modification, there will be many classes involved.

The idea of ​​dependency injection was first proposed in the spring of java, that is, dependent classes are not instantiated by programmers, but through the spring container to help us specify new instances and inject the instances into the classes that need the object. At present, many mainstream PHP frameworks also use dependency injection containers, such as ThinkPHP, Laravel, etc.

1. Concept

1. Container: literally, it means something to hold things. Common variables, object properties, etc. can all be regarded as containers. What a container can hold depends on your definition of the container. Of course, what we are discussing now is such a container, which stores not text, values, but objects, object descriptions (classes, interfaces), or callbacks (closures) that provide objects. Through this container, we can Achieve many advanced functions, among which the most frequently mentioned are "decoupling" and "dependency injection".

2. IoC-Inversion of Control

Inversion of control is described from the perspective of the container, that is, the container controls the application, and the container injects the external resources required by the application into the application in the reverse direction.

3. DI-Dependency Injection

Dependency injection is described from the perspective of the application. Dependency injection can be used, that is, the application depends on the container to create and inject the external resources it needs.

Remarks: Dependency injection and inversion of control talk about the same thing. They are a design pattern. This design pattern is used to reduce the coupling between programs. In a way, they describe different perspectives.

Second, the principle of dependency injection

Under normal circumstances, when there is a dependency between a class and a class, we call it through direct instantiation. Once multi-layer dependencies appear, the degree of coupling in this way is very high. When one of the classes needs to be modified, it will involve a lot of modifications to the classes that depend on it, so the code changes will be relatively large.

Here is a brief example of the A->B->C three-layer dependency relationship to explain how to use dependency injection to decouple and improve development efficiency.
Insert picture description here

The dependency injection method is as follows:
Insert picture description here
Resolution:

In the conventional way of writing, once the class C needs to be changed, or the call of the class B needs to be changed to the class D, it is also necessary to consider the dependency on the class B, that is, the class B needs to be modified.

The idea of ​​dependency injection is to use it as an instance, invert the control relationship between the class and the class, and realize that the subsequent dependency relationship is controlled by the calling class A, so that the B class can change the required dependencies and instantiated classes at will ( C or D), to achieve the purpose of decoupling.

Insert picture description here

3. Commonly used dependency injection methods:

1. Constructor injection; 2. Set attribute injection; 3. Static factory method injection;

The above example uses the construction method injection method, passing the object as a parameter to the construction method; the same set attribute injection is also a similar method, the only difference is that the object parameter is passed when the attribute of a member of a class is set. , I will not give examples one by one here.

In addition, there is a static factory method injection method, which is similar to the static factory method.

We know that the static factory method is to manage multiple similar classes that need to be instantiated through a class. This class will define a method to obtain the object that needs to be instantiated, and the specific object to be instantiated depends on the object passed in. Name parameter.

For static factory injection, the difference from the general static factory method is that the parameter passed in is an object that has already been instantiated.

<?php
class IoC
{
    
    
  protected static $registry = [];
  public static function bind($name, Callable $resolver) //传入类名和类对象实例
  {
    
    
    static::$registry[$name] = $resolver;
  }
  public static function make($name) //静态工厂方法
  {
    
    
    if (isset(static::$registry[$name])) {
    
    
      $resolver = static::$registry[$name];
      return $resolver(); //实例化
    }
    throw new Exception('Alias does not exist in the IoC registry.');
  }
}

All in all, the three methods are all instantiated objects, but the difference is that the location of the transfer is the construction method, set property, and static factory method.

Four, dependency injection container (Ioc container)

Most of the time, when using dependency injection to decouple components, containers are not needed.
When a program needs to instantiate too many classes or too many dependencies, it is more cumbersome to repeat the dependency injection code, such as the following situation:
Insert picture description here
When the above relationship occurs, the dependency injection code will be messy and there will be duplication. , It is more likely that when a general method is called, an unnecessary class will be created, resulting in redundancy.

At this time, you need to use the container. The idea after using the dependency injection container is that the application needs to reach the A class, and then obtain the A class from the container. Specifically, the container creates class C, then creates class B and injects C, then creates class A, and injects class B, the application calls class A methods, class A calls class B methods, and then does some other work. In short, the container is responsible for Instantiate, inject dependencies, handle dependencies, etc.
Insert picture description here

For the complex and changeable code environment in actual development, we do not fully know what the current class will expand into in the future, so we need to implement the method of instantiating the class through the container when a new dependent class is added. . Therefore, when instantiating an unknown class, the best way to explore the internal structure and instantiation of a class is to use reflection. It can be seen that reflection is the core of container management for each dependent class. We can understand the internal implementation of the container through examples:

Three dependent classes: file testClass.php

<?php //依赖关系:Company->Department->Group
class Group
{
    
    
  public function doSomething()
  {
    
    
    echo __CLASS__.":".'hello', '|';
  }
}
class Department
{
    
    
  private $group;
  public function __construct(Group $group)
  {
    
    
    $this->group = $group;
  }
  public function doSomething()
  {
    
    
    $this->group->doSomething();
    echo __CLASS__.":".'hello', '|';
  }
}
class Company
{
    
    
  private $department;
  public function __construct(Department $department)
  {
    
    
    $this->department = $department;
  }
  public function doSomething()
  {
    
    
    $this->department->doSomething();
    echo __CLASS__.":".'hello', '|';
  }
}

The internal implementation of the Ioc container:

<?php
class Container
{
    
    
  private $s = array();
  public function __set($k, $c)
  {
    
    
    $this->s[$k] = $c;
  }
  public function __get($k)
  {
    
    
    return $this->build($this->s[$k]);
  }
  /**
   * 自动绑定(Autowiring)自动解析(Automatic Resolution)
   *
   * @param string $className
   * @return object
   * @throws Exception
   */
  public function build($className)
  {
    
    
    // 如果是匿名函数(Anonymous functions),也叫闭包函数(closures)
    if ($className instanceof Closure) {
    
    
      // 执行闭包函数,并将结果
      return $className($this);
    }
    /*通过反射获取类的内部结构,实例化类*/
    $reflector = new ReflectionClass($className);
    // 检查类是否可实例化, 排除抽象类abstract和对象接口interface
    if (!$reflector->isInstantiable()) {
    
    
      throw new Exception("Can't instantiate this.");
    }
    /** @var ReflectionMethod $constructor 获取类的构造函数 */
    $constructor = $reflector->getConstructor();
    // 若无构造函数,直接实例化并返回
    if (is_null($constructor)) {
    
    
      return new $className;
    }
    // 取构造函数参数,通过 ReflectionParameter 数组返回参数列表
    $parameters = $constructor->getParameters();
    // 递归解析构造函数的参数
    $dependencies = $this->getDependencies($parameters);
    // 创建一个类的新实例,给出的参数将传递到类的构造函数。
    return $reflector->newInstanceArgs($dependencies);
  }
  /**
   * @param array $parameters
   * @return array
   * @throws Exception
   */
  public function getDependencies($parameters)
  {
    
    
    $dependencies = [];
    /** @var ReflectionParameter $parameter */
    foreach ($parameters as $parameter) {
    
    
      /** @var ReflectionClass $dependency */
      $dependency = $parameter->getClass();
      if (is_null($dependency)) {
    
    
        // 是变量,有默认值则设置默认值
        $dependencies[] = $this->resolveNonClass($parameter);
      } else {
    
    
        // 是一个类,递归解析
        $dependencies[] = $this->build($dependency->name);
      }
    }
    return $dependencies;
  }
  /**
   * @param ReflectionParameter $parameter
   * @return mixed
   * @throws Exception
   */
  public function resolveNonClass($parameter)
  {
    
    
    // 有默认值则返回默认值
    if ($parameter->isDefaultValueAvailable()) {
    
    
      return $parameter->getDefaultValue();
    }
    throw new Exception('I have no idea what to do here.');
  }
}
require_once "./testclass.php"; //开始测试,先测试已知依赖关系的情况
$c = new Container();
$c->department = 'Department';
$c->company = function ($c) {
  return new Company($c->department);
};
// 从容器中取得company
$company = $c->company;
$company->doSomething(); //输出: Group:hello|Department:hello|Company:hello|
// 测试未知依赖关系,直接使用的方法
$di = new Container();
$di->company = 'Company';
$company = $di->company;
$company->doSomething();//输出: Group:hello|Department:hello|Company:hello|

Five, summary

The basic concept of IOC is: not to create objects, but to describe the way to create them. It is not directly connected with objects and services in the code, but which component needs which service is described in the configuration file. The Spring container is responsible for linking these together. In other words, Spring's IOC is responsible for managing the creation and removal of various objects and the connections between them.

Guess you like

Origin blog.csdn.net/kevlin_V/article/details/103336914