Home  >  Article  >  Backend Development  >  Understanding PHP Dependency Injection | LaravelIoC Container

Understanding PHP Dependency Injection | LaravelIoC Container

黄舟
黄舟Original
2016-12-16 09:31:241008browse

The dependency injection of the Laravel framework is indeed very powerful, and dependency injection implemented through the container can selectively load the required services and reduce the overhead of initializing the framework. The following is a post I saw on the Internet. It is very well written and can be used with Everyone, this article shows the power of dependency injection from the beginning of designing database connections according to traditional classes to the highly decoupled design of loading services through containers, which is worthy of our reference and learning.

------------------------------------------------ ----------Below the dividing line is Daniel's original text---------------------------------- -----------------------------

First, let's assume that we want to develop a component named SomeComponent. A database connection will now be injected into this component. In this example, the database connection is created in the component. This method is impractical. If we do this, we will not be able to change some parameters such as database connection parameters and database type.

1 <?php 
2  
3 class SomeComponent 
4 { 
5  
6     PRotected $_connection; 
7  
8     /** 
9      * Sets the connection externally
10      */
11     public function setConnection($connection)
12     {
13         $this->_connection = $connection;
14     }
15 
16     public function someDbTask()
17     {
18         $connection = $this->_connection;
19 
20         // ...
21     }
22
23 }
24 
25 $some = new SomeComponent();
26 
27 //Create the connection
28 $connection = new Connection(array(
29     "host" => "localhost",
30     "username" => "root",
31     "password" => "secret",
32     "dbname" => "invo"
33 ));
34 
35 //Inject the connection in the component
36 $some->setConnection($connection);
37 
38 $some->someDbTask();

Now let’s consider a problem, we use this component in different places in the application and the database connection will be created multiple times. Use a method similar to the global registry to obtain a database connection instance from here instead of creating it once you use it.

1 <?php 
2  
3 class Registry 
4 { 
5  
6     /** 
7      * Returns the connection 
8      */ 
9     public static function getConnection()
10     {
11        return new Connection(array(
12             "host" => "localhost",
13             "username" => "root",
14             "password" => "secret",
15             "dbname" => "invo"
16         ));
17     }
18 
19 }
20 
21 class SomeComponent
22 {
23 
24     protected $_connection;
25 
26     /**
27      * Sets the connection externally
28      */
29     public function setConnection($connection){
30         $this->_connection = $connection;
31     }
32 
33     public function someDbTask()
34     {
35         $connection = $this->_connection;
36 
37         // ...
38     }
39 
40 }
41 
42 $some = new SomeComponent();
43 
44 //Pass the connection defined in the registry
45 $some->setConnection(Registry::getConnection());
46 
47 $some->someDbTask();

Now, let's imagine that we have to implement two methods in the component, first of which we need to create a new database connection, and the second one always gets a shared connection:

1 <?php 
2  
3 class Registry 
4 { 
5  
6     protected static $_connection; 
7  
8     /** 
9      * Creates a connection
10      */
11     protected static function _createConnection()
12     {
13         return new Connection(array(
14             "host" => "localhost",
15             "username" => "root",
16             "password" => "secret",
17             "dbname" => "invo"
18         ));
19     }
20 
21     /**
22      * Creates a connection only once and returns it
23      */
24     public static function getSharedConnection()
25     {
26         if (self::$_connection===null){
27             $connection = self::_createConnection();
28             self::$_connection = $connection;
29         }
30         return self::$_connection;
31     }
32 
33     /**
34      * Always returns a new connection
35      */
36     public static function getNewConnection()
37     {
38         return self::_createConnection();
39     }
40 
41 }
42 
43 class SomeComponent
44 {
45 
46     protected $_connection;
47 
48     /**
49      * Sets the connection externally
50      */
51     public function setConnection($connection){
52         $this->_connection = $connection;
53     }
54 
55     /**
56      * This method always needs the shared connection
57      */
58     public function someDbTask()
59     {
60         $connection = $this->_connection;
61 
62         // ...
63     }
64
65     /**
66      * This method always needs a new connection
67      */
68     public function someOtherDbTask($connection)
69     {
70 
71     }
72 
73 }
74 
75 $some = new SomeComponent();
76 
77 //This injects the shared connection
78 $some->setConnection(Registry::getSharedConnection());
79 
80 $some->someDbTask();
81 
82 //Here, we always pass a new connection as parameter
83 $some->someOtherDbTask(Registry::getConnection());

So far, we have seen Here's how to use dependency injection to solve our problem. Instead of creating a dependency within the code, we pass it as a parameter, which makes our program easier to maintain, reduces the coupling of the program code, and achieves a kind of loose coupling. But in the long run, this form of dependency injection also has some disadvantages.

 For example, if there are many dependencies in the component, we need to create multiple setter methods to pass, or create a constructor to pass. In addition, every time you use a component, you need to create dependent components, making the code maintenance not easy. The code we write may look like this:

1 <?php 
2  
3 //Create the dependencies or retrieve them from the registry 
4 $connection = new Connection(); 
5 $session = new Session(); 
6 $fileSystem = new FileSystem(); 
7 $filter = new Filter(); 
8 $selector = new Selector(); 
9 
10 //Pass them as constructor parameters
11 $some = new SomeComponent($connection, $session, $fileSystem, $filter, $selector);
12 
13 // ... or using setters
14 
15 $some->setConnection($connection);
16 $some->setSession($session);
17 $some->setFileSystem($fileSystem);
18 $some->setFilter($filter);
19 $some->setSelector($selector);

I think, we have to create this object in many places in the application. If you don't need the dependent components, we have to go to the code injection part to remove the parameters in the constructor or the setter method. To solve this problem, we once again go back to using a global registry to create the component. However, before creating the object, it adds a new layer of abstraction:

 1 <?php 
 2  
 3 class SomeComponent 
 4 { 
 5  
 6     // ... 
 7  
 8     /** 
 9      * Define a factory method to create SomeComponent instances injecting its dependencies
 10      */
 11     public static function factory()
 12     {
 13 
 14         $connection = new Connection();
 15         $session = new Session();
 16         $fileSystem = new FileSystem();
 17         $filter = new Filter();
 18         $selector = new Selector();
 19 
 20         return new self($connection, $session, $fileSystem, $filter, $selector);
 21     }
 22 
 23 }

At this moment, we seem to be back to the beginning of the problem, we are creating dependencies inside the component, and we are modifying and finding a way to solve the problem every time methods, but these are not good practices.

A practical and elegant way to solve these problems is to use container dependency injection. As we saw earlier, the container acts as a global registry. Using container dependency injection as a bridge to solve dependencies allows us to The code coupling is lower, which greatly reduces the complexity of the component:

 1 <?php 
 2  
 3 class SomeComponent 
 4 { 
 5  
 6     protected $_di; 
 7  
 8     public function __construct($di) 
 9     {
 10         $this->_di = $di;
 11     }
 12 
 13     public function someDbTask()
 14     {
 15 
 16         // Get the connection service
 17         // Always returns a new connection
 18         $connection = $this->_di->get(&#39;db&#39;);
 19 
 20     }
 21 
 22     public function someOtherDbTask()
 23     {
 24 
 25         // Get a shared connection service,
 26         // this will return the same connection everytime
 27         $connection = $this->_di->getShared(&#39;db&#39;);
 28 
 29         //This method also requires a input filtering service
 30         $filter = $this->_db->get(&#39;filter&#39;);
 31 
 32     }
 33 
 34 }
 35 
 36 $di = new Phalcon\DI();
 37 
 38 //Register a "db" service in the container
 39 $di->set(&#39;db&#39;, function(){
 40     return new Connection(array(
 41         "host" => "localhost",
 42         "username" => "root",
 43         "password" => "secret",
 44         "dbname" => "invo"
 45     ));
 46 });
 47 
 48 //Register a "filter" service in the container
 49 $di->set(&#39;filter&#39;, function(){
 50     return new Filter();
 51 });
 52 
 53 //Register a "session" service in the container
 54 $di->set(&#39;session&#39;, function(){
 55     return new Session();
 56 });
 57 
 58 //Pass the service container as unique parameter59 $some = new SomeComponent($di);
 60 
 61 $some->someTask();

Now, the component only needs it when it accesses a certain service. If it does not need it, it will not even be initialized to save resources. The component is highly decoupled. Their behavior, or any other aspect of them, does not affect the components themselves.
Our implementation method¶

PhalconDI is a component that implements the dependency injection function of the service, and it is also a container itself.

Due to the high degree of decoupling of Phalcon, PhalconDI is an essential part of the framework used to integrate other components. Developers can also use this component to dependency inject and manage instances of different class files in the application.

Basically, this component implements the Inversion of Control pattern. Based on this, the object no longer implements injection by receiving parameters in the constructor or using setters, but directly requests dependency injection of the service. This greatly reduces the overall program complexity because there is only one way to obtain the required dependencies for a component.

Additionally, this pattern enhances the testability of your code, making it less error-prone.
Register services in containers¶

Either the framework itself or developers can register services. When a component A requests a call to component B (or an instance of its class), it can request a call to component B from the container instead of creating an instance of component B.

This way of working provides us with many advantages:

We can replace a component, either from themselves or created easily by a third party.
Before the component is released, we can fully control the initialization of the object and make various settings for the object.
We can use a unified way to get a structured global instance from the component

Services can be injected into the container in the following ways:

 1 <?php 
 2  
 3 //Create the Dependency Injector Container 
 4 $di = new Phalcon\DI(); 
 5  
 6 //By its class name 
 7 $di->set("request", &#39;Phalcon\Http\Request&#39;); 
 8  
 9 //Using an anonymous function, the instance will lazy loaded
 10 $di->set("request", function(){
 11     return new Phalcon\Http\Request();
 12 });
 13 
 14 //Registering directly an instance
 15 $di->set("request", new Phalcon\Http\Request());
 16 
 17 //Using an array definition
 18 $di->set("request", array(
 19     "className" => &#39;Phalcon\Http\Request&#39;
 20 ));

In the above example, when requesting access to a request data from the framework, it will First determine whether the service with this "reqeust" name exists in the container.

The container will return an instance of the requested data, and developers finally get the component they want.

Each method in the above example has advantages and disadvantages. Which one to use depends on the specific scenario during the development process.

用一个字符串来设定一个服务非常简单,但缺少灵活性。设置服务时,使用数组则提供了更多的灵活性,而且可以使用较复杂的代码。lambda函数是两者之间一个很好的平衡,但也可能导致更多的维护管理成本。

Phalcon\DI 提供服务的延迟加载。除非开发人员在注入服务的时候直接实例化一个对象,然后存存储到容器中。在容器中,通过数组,字符串等方式存储的服务都将被延迟加载,即只有在请求对象的时候才被初始化。

 1 <?php 
 2  
 3 //Register a service "db" with a class name and its parameters 
 4 $di->set("db", array( 
 5     "className" => "Phalcon\Db\Adapter\Pdo\MySQL", 
 6     "parameters" => array( 
 7           "parameter" => array( 
 8                "host" => "localhost", 
 9                "username" => "root",
 10                "password" => "secret",
 11                "dbname" => "blog"
 12           )
 13     )
 14 ));
 15 
 16 //Using an anonymous function
 17 $di->set("db", function(){
 18     return new Phalcon\Db\Adapter\Pdo\Mysql(array(
 19          "host" => "localhost",
 20          "username" => "root",
 21          "password" => "secret",
 22          "dbname" => "blog"
 23     ));
 24 });

以上这两种服务的注册方式产生相同的结果。然后,通过数组定义的,在后面需要的时候,你可以修改服务参数:

1 <?php
2 
3 $di->setParameter("db", 0, array(
4     "host" => "localhost",
5     "username" => "root",
6     "password" => "secret"
7 ));

从容器中获得服务的最简单方式就是使用”get”方法,它将从容器中返回一个新的实例:

1 <?php
2      $request = $di->get("request")

或者通过下面这种魔术方法的形式调用:

1 <?php
2 
3 $request = $di->getRequest();
4 
5 Phalcon\DI 同时允许服务重用,为了得到一个已经实例化过的服务,可以使用 getShared() 方法的形式;

具体的 Phalcon\Http\Request 请求示例:

1 <?php
2 
3 $request = $di->getShared("request");

参数还可以在请求的时候通过将一个数组参数传递给构造函数的方式:

1 <?php
2 
3 $component = $di->get("MyComponent", array("some-parameter", "other"))

理解PHP依赖注入|LaravelIoC容器

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn