The dependency injection of the Laravel framework is indeed very powerful, and the dependency injection implemented through the container can selectively load the required services. To reduce the overhead of initializing the framework, the following is a post I saw on the Internet. It is very well written and I would like to share it with you. The article starts from designing the database connection according to traditional classes to the highly decoupled design display of loading services through containers. The power of dependency injection is worth learning from.
------------------------------------------------- ------------Below the dividing line is Daniel's original text--------------------------------- ----------------------------
Original link (http://www.yuansir-web.com/2014/03/20)
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.
<span> 1</span> <?<span>php </span><span> 2</span> <span> 3</span> <span>class</span><span> SomeComponent </span><span> 4</span> <span>{ </span><span> 5</span> <span> 6</span> <span>/*</span><span>* </span><span> 7</span> <span> * The instantiation of the connection is hardcoded inside </span><span> 8</span> <span> * the component so is difficult to replace it externally </span><span> 9</span> <span> * or change its behavior </span><span>10</span> <span>*/</span> <span>11</span> <span>public</span> <span>function</span><span> someDbTask() </span><span>12</span> <span> { </span><span>13</span> <span>$connection</span> = <span>new</span> Connection(<span>array</span><span>( </span><span>14</span> "host" => "localhost", <span>15</span> "username" => "root", <span>16</span> "password" => "secret", <span>17</span> "dbname" => "invo" <span>18</span> <span> )); </span><span>19</span> <span>20</span> <span>//</span><span> ...</span> <span>21</span> <span> } </span><span>22</span> <span>23</span> <span>} </span><span>24</span> <span>25</span> <span>$some</span> = <span>new</span><span> SomeComponent(); </span><span>26</span> <span>$some</span>->someDbTask();
In order to solve the above problem, we need to create an external connection before use and inject it into the container. For now, this looks like a good solution:
<span> 1</span> <?<span>php </span><span> 2</span> <span> 3</span> <span>class</span><span> SomeComponent </span><span> 4</span> <span>{ </span><span> 5</span> <span> 6</span> <span>protected</span> <span>$_connection</span><span>; </span><span> 7</span> <span> 8</span> <span>/*</span><span>* </span><span> 9</span> <span> * Sets the connection externally </span><span>10</span> <span>*/</span> <span>11</span> <span>public</span> <span>function</span> setConnection(<span>$connection</span><span>) </span><span>12</span> <span> { </span><span>13</span> <span>$this</span>->_connection = <span>$connection</span><span>; </span><span>14</span> <span> } </span><span>15</span> <span>16</span> <span>public</span> <span>function</span><span> someDbTask() </span><span>17</span> <span> { </span><span>18</span> <span>$connection</span> = <span>$this</span>-><span>_connection; </span><span>19</span> <span>20</span> <span>//</span><span> ...</span> <span>21</span> <span> } </span><span>22</span> <span>23</span> <span>} </span><span>24</span> <span>25</span> <span>$some</span> = <span>new</span><span> SomeComponent(); </span><span>26</span> <span>27</span> <span>//</span><span>Create the connection</span> <span>28</span> <span>$connection</span> = <span>new</span> Connection(<span>array</span><span>( </span><span>29</span> "host" => "localhost", <span>30</span> "username" => "root", <span>31</span> "password" => "secret", <span>32</span> "dbname" => "invo" <span>33</span> <span>)); </span><span>34</span> <span>35</span> <span>//</span><span>Inject the connection in the component</span> <span>36</span> <span>$some</span>->setConnection(<span>$connection</span><span>); </span><span>37</span> <span>38</span> <span>$some</span>->someDbTask();
Now let’s consider a problem. We use this component in different places in the application to create database connections 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.
<span> 1</span> <?<span>php </span><span> 2</span> <span> 3</span> <span>class</span><span> Registry </span><span> 4</span> <span>{ </span><span> 5</span> <span> 6</span> <span>/*</span><span>* </span><span> 7</span> <span> * Returns the connection </span><span> 8</span> <span>*/</span> <span> 9</span> <span>public</span> <span>static</span> <span>function</span><span> getConnection() </span><span>10</span> <span> { </span><span>11</span> <span>return</span> <span>new</span> Connection(<span>array</span><span>( </span><span>12</span> "host" => "localhost", <span>13</span> "username" => "root", <span>14</span> "password" => "secret", <span>15</span> "dbname" => "invo" <span>16</span> <span> )); </span><span>17</span> <span> } </span><span>18</span> <span>19</span> <span>} </span><span>20</span> <span>21</span> <span>class</span><span> SomeComponent </span><span>22</span> <span>{ </span><span>23</span> <span>24</span> <span>protected</span> <span>$_connection</span><span>; </span><span>25</span> <span>26</span> <span>/*</span><span>* </span><span>27</span> <span> * Sets the connection externally </span><span>28</span> <span>*/</span> <span>29</span> <span>public</span> <span>function</span> setConnection(<span>$connection</span><span>){ </span><span>30</span> <span>$this</span>->_connection = <span>$connection</span><span>; </span><span>31</span> <span> } </span><span>32</span> <span>33</span> <span>public</span> <span>function</span><span> someDbTask() </span><span>34</span> <span> { </span><span>35</span> <span>$connection</span> = <span>$this</span>-><span>_connection; </span><span>36</span> <span>37</span> <span>//</span><span> ...</span> <span>38</span> <span> } </span><span>39</span> <span>40</span> <span>} </span><span>41</span> <span>42</span> <span>$some</span> = <span>new</span><span> SomeComponent(); </span><span>43</span> <span>44</span> <span>//</span><span>Pass the connection defined in the registry</span> <span>45</span> <span>$some</span>->setConnection(Registry::<span>getConnection()); </span><span>46</span> <span>47</span> <span>$some</span>->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:
<span> 1</span> <?<span>php </span><span> 2</span> <span> 3</span> <span>class</span><span> Registry </span><span> 4</span> <span>{ </span><span> 5</span> <span> 6</span> <span>protected</span> <span>static</span> <span>$_connection</span><span>; </span><span> 7</span> <span> 8</span> <span>/*</span><span>* </span><span> 9</span> <span> * Creates a connection </span><span>10</span> <span>*/</span> <span>11</span> <span>protected</span> <span>static</span> <span>function</span><span> _createConnection() </span><span>12</span> <span> { </span><span>13</span> <span>return</span> <span>new</span> Connection(<span>array</span><span>( </span><span>14</span> "host" => "localhost", <span>15</span> "username" => "root", <span>16</span> "password" => "secret", <span>17</span> "dbname" => "invo" <span>18</span> <span> )); </span><span>19</span> <span> } </span><span>20</span> <span>21</span> <span>/*</span><span>* </span><span>22</span> <span> * Creates a connection only once and returns it </span><span>23</span> <span>*/</span> <span>24</span> <span>public</span> <span>static</span> <span>function</span><span> getSharedConnection() </span><span>25</span> <span> { </span><span>26</span> <span>if</span> (self::<span>$_connection</span>===<span>null</span><span>){ </span><span>27</span> <span>$connection</span> = self::<span>_createConnection(); </span><span>28</span> self::<span>$_connection</span> = <span>$connection</span><span>; </span><span>29</span> <span> } </span><span>30</span> <span>return</span> self::<span>$_connection</span><span>; </span><span>31</span> <span> } </span><span>32</span> <span>33</span> <span>/*</span><span>* </span><span>34</span> <span> * Always returns a new connection </span><span>35</span> <span>*/</span> <span>36</span> <span>public</span> <span>static</span> <span>function</span><span> getNewConnection() </span><span>37</span> <span> { </span><span>38</span> <span>return</span> self::<span>_createConnection(); </span><span>39</span> <span> } </span><span>40</span> <span>41</span> <span>} </span><span>42</span> <span>43</span> <span>class</span><span> SomeComponent </span><span>44</span> <span>{ </span><span>45</span> <span>46</span> <span>protected</span> <span>$_connection</span><span>; </span><span>47</span> <span>48</span> <span>/*</span><span>* </span><span>49</span> <span> * Sets the connection externally </span><span>50</span> <span>*/</span> <span>51</span> <span>public</span> <span>function</span> setConnection(<span>$connection</span><span>){ </span><span>52</span> <span>$this</span>->_connection = <span>$connection</span><span>; </span><span>53</span> <span> } </span><span>54</span> <span>55</span> <span>/*</span><span>* </span><span>56</span> <span> * This method always needs the shared connection </span><span>57</span> <span>*/</span> <span>58</span> <span>public</span> <span>function</span><span> someDbTask() </span><span>59</span> <span> { </span><span>60</span> <span>$connection</span> = <span>$this</span>-><span>_connection; </span><span>61</span> <span>62</span> <span>//</span><span> ...</span> <span>63</span> <span> } </span><span>64</span> <span>65</span> <span>/*</span><span>* </span><span>66</span> <span> * This method always needs a new connection </span><span>67</span> <span>*/</span> <span>68</span> <span>public</span> <span>function</span> someOtherDbTask(<span>$connection</span><span>) </span><span>69</span> <span> { </span><span>70</span> <span>71</span> <span> } </span><span>72</span> <span>73</span> <span>} </span><span>74</span> <span>75</span> <span>$some</span> = <span>new</span><span> SomeComponent(); </span><span>76</span> <span>77</span> <span>//</span><span>This injects the shared connection</span> <span>78</span> <span>$some</span>->setConnection(Registry::<span>getSharedConnection()); </span><span>79</span> <span>80</span> <span>$some</span>-><span>someDbTask(); </span><span>81</span> <span>82</span> <span>//</span><span>Here, we always pass a new connection as parameter</span> <span>83</span> <span>$some</span>->someOtherDbTask(Registry::getConnection());
So far, we have seen 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 a dependent component, making the code maintenance difficult. The code we write may look like this:
<span> 1</span> <?<span>php </span><span> 2</span> <span> 3</span> <span>//</span><span>Create the dependencies or retrieve them from the registry</span> <span> 4</span> <span>$connection</span> = <span>new</span><span> Connection(); </span><span> 5</span> <span>$session</span> = <span>new</span><span> Session(); </span><span> 6</span> <span>$fileSystem</span> = <span>new</span><span> FileSystem(); </span><span> 7</span> <span>$filter</span> = <span>new</span><span> Filter(); </span><span> 8</span> <span>$selector</span> = <span>new</span><span> Selector(); </span><span> 9</span> <span>10</span> <span>//</span><span>Pass them as constructor parameters</span> <span>11</span> <span>$some</span> = <span>new</span> SomeComponent(<span>$connection</span>, <span>$session</span>, <span>$fileSystem</span>, <span>$filter</span>, <span>$selector</span><span>); </span><span>12</span> <span>13</span> <span>//</span><span> ... or using setters</span> <span>14</span> <span>15</span> <span>$some</span>->setConnection(<span>$connection</span><span>); </span><span>16</span> <span>$some</span>->setSession(<span>$session</span><span>); </span><span>17</span> <span>$some</span>->setFileSystem(<span>$fileSystem</span><span>); </span><span>18</span> <span>$some</span>->setFilter(<span>$filter</span><span>); </span><span>19</span> <span>$some</span>->setSelector(<span>$selector</span>);
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, it adds a new layer of abstraction before creating the object:
<span> 1</span> <?<span>php </span><span> 2</span> <span> 3</span> <span>class</span><span> SomeComponent </span><span> 4</span> <span>{ </span><span> 5</span> <span> 6</span> <span>//</span><span> ...</span> <span> 7</span> <span> 8</span> <span>/*</span><span>* </span><span> 9</span> <span> * Define a factory method to create SomeComponent instances injecting its dependencies </span><span>10</span> <span>*/</span> <span>11</span> <span>public</span> <span>static</span> <span>function</span><span> factory() </span><span>12</span> <span> { </span><span>13</span> <span>14</span> <span>$connection</span> = <span>new</span><span> Connection(); </span><span>15</span> <span>$session</span> = <span>new</span><span> Session(); </span><span>16</span> <span>$fileSystem</span> = <span>new</span><span> FileSystem(); </span><span>17</span> <span>$filter</span> = <span>new</span><span> Filter(); </span><span>18</span> <span>$selector</span> = <span>new</span><span> Selector(); </span><span>19</span> <span>20</span> <span>return</span> <span>new</span> self(<span>$connection</span>, <span>$session</span>, <span>$fileSystem</span>, <span>$filter</span>, <span>$selector</span><span>); </span><span>21</span> <span> } </span><span>22</span> <span>23</span> }
At this moment, we seem to be back to the beginning of the problem. We are creating dependencies inside the component. We are modifying and looking for a solution to the problem every time, but this is not a good approach.
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 can make Our code is less coupled, which greatly reduces the complexity of components:
<span> 1</span> <?<span>php </span><span> 2</span> <span> 3</span> <span>class</span><span> SomeComponent </span><span> 4</span> <span>{ </span><span> 5</span> <span> 6</span> <span>protected</span> <span>$_di</span><span>; </span><span> 7</span> <span> 8</span> <span>public</span> <span>function</span> __construct(<span>$di</span><span>) </span><span> 9</span> <span> { </span><span>10</span> <span>$this</span>->_di = <span>$di</span><span>; </span><span>11</span> <span> } </span><span>12</span> <span>13</span> <span>public</span> <span>function</span><span> someDbTask() </span><span>14</span> <span> { </span><span>15</span> <span>16</span> <span>//</span><span> Get the connection service </span><span>17</span> <span> // Always returns a new connection</span> <span>18</span> <span>$connection</span> = <span>$this</span>->_di->get('db'<span>); </span><span>19</span> <span>20</span> <span> } </span><span>21</span> <span>22</span> <span>public</span> <span>function</span><span> someOtherDbTask() </span><span>23</span> <span> { </span><span>24</span> <span>25</span> <span>//</span><span> Get a shared connection service, </span><span>26</span> <span> // this will return the same connection everytime</span> <span>27</span> <span>$connection</span> = <span>$this</span>->_di->getShared('db'<span>); </span><span>28</span> <span>29</span> <span>//</span><span>This method also requires a input filtering service</span> <span>30</span> <span>$filter</span> = <span>$this</span>->_db->get('filter'<span>); </span><span>31</span> <span>32</span> <span> } </span><span>33</span> <span>34</span> <span>} </span><span>35</span> <span>36</span> <span>$di</span> = <span>new</span><span> Phalcon\DI(); </span><span>37</span> <span>38</span> <span>//</span><span>Register a "db" service in the container</span> <span>39</span> <span>$di</span>->set('db', <span>function</span><span>(){ </span><span>40</span> <span>return</span> <span>new</span> Connection(<span>array</span><span>( </span><span>41</span> "host" => "localhost", <span>42</span> "username" => "root", <span>43</span> "password" => "secret", <span>44</span> "dbname" => "invo" <span>45</span> <span> )); </span><span>46</span> <span>}); </span><span>47</span> <span>48</span> <span>//</span><span>Register a "filter" service in the container</span> <span>49</span> <span>$di</span>->set('filter', <span>function</span><span>(){ </span><span>50</span> <span>return</span> <span>new</span><span> Filter(); </span><span>51</span> <span>}); </span><span>52</span> <span>53</span> <span>//</span><span>Register a "session" service in the container</span> <span>54</span> <span>$di</span>->set('session', <span>function</span><span>(){ </span><span>55</span> <span>return</span> <span>new</span><span> Session(); </span><span>56</span> <span>}); </span><span>57</span> <span>58</span> <span>//</span><span>Pass the service container as unique parameter</span> <span>59</span> <span>$some</span> = <span>new</span> SomeComponent(<span>$di</span><span>); </span><span>60</span> <span>61</span> <span>$some</span>->someTask();
Now, the component only needs it when accessing 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.
How we implement it¶
PhalconDI is a component that implements the dependency injection function of services. It is also a container itself.
Since Phalcon is highly decoupled, 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 of a component.
Additionally, this pattern enhances the testability of your code, making it less error-prone.
Registering services in containers¶
Services can be registered either by the framework itself or by developers. 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 get a structured global instance from the component in a unified way
Services can be injected into containers in the following ways:
<span> 1</span> <?<span>php </span><span> 2</span> <span> 3</span> <span>//</span><span>Create the Dependency Injector Container</span> <span> 4</span> <span>$di</span> = <span>new</span><span> Phalcon\DI(); </span><span> 5</span> <span> 6</span> <span>//</span><span>By its class name</span> <span> 7</span> <span>$di</span>->set("request", 'Phalcon\Http\Request'<span>); </span><span> 8</span> <span> 9</span> <span>//</span><span>Using an anonymous function, the instance will lazy loaded</span> <span>10</span> <span>$di</span>->set("request", <span>function</span><span>(){ </span><span>11</span> <span>return</span> <span>new</span><span> Phalcon\Http\Request(); </span><span>12</span> <span>}); </span><span>13</span> <span>14</span> <span>//</span><span>Registering directly an instance</span> <span>15</span> <span>$di</span>->set("request", <span>new</span><span> Phalcon\Http\Request()); </span><span>16</span> <span>17</span> <span>//</span><span>Using an array definition</span> <span>18</span> <span>$di</span>->set("request", <span>array</span><span>( </span><span>19</span> "className" => 'Phalcon\Http\Request' <span>20</span> ));
在上面的例子中,当向框架请求访问一个请求数据时,它将首先确定容器中是否存在这个”reqeust”名称的服务。
容器会反回一个请求数据的实例,开发人员最终得到他们想要的组件。
在上面示例中的每一种方法都有优缺点,具体使用哪一种,由开发过程中的特定场景来决定的。
用一个字符串来设定一个服务非常简单,但缺少灵活性。设置服务时,使用数组则提供了更多的灵活性,而且可以使用较复杂的代码。lambda函数是两者之间一个很好的平衡,但也可能导致更多的维护管理成本。
Phalcon\DI 提供服务的延迟加载。除非开发人员在注入服务的时候直接实例化一个对象,然后存存储到容器中。在容器中,通过数组,字符串等方式存储的服务都将被延迟加载,即只有在请求对象的时候才被初始化。
<span> 1</span> <?<span>php </span><span> 2</span> <span> 3</span> <span>//</span><span>Register a service "db" with a class name and its parameters</span> <span> 4</span> <span>$di</span>->set("db", <span>array</span><span>( </span><span> 5</span> "className" => "Phalcon\Db\Adapter\Pdo\Mysql", <span> 6</span> "parameters" => <span>array</span><span>( </span><span> 7</span> "parameter" => <span>array</span><span>( </span><span> 8</span> "host" => "localhost", <span> 9</span> "username" => "root", <span>10</span> "password" => "secret", <span>11</span> "dbname" => "blog" <span>12</span> <span> ) </span><span>13</span> <span> ) </span><span>14</span> <span>)); </span><span>15</span> <span>16</span> <span>//</span><span>Using an anonymous function</span> <span>17</span> <span>$di</span>->set("db", <span>function</span><span>(){ </span><span>18</span> <span>return</span> <span>new</span> Phalcon\Db\Adapter\Pdo\<span>Mysql</span>(<span>array</span><span>( </span><span>19</span> "host" => "localhost", <span>20</span> "username" => "root", <span>21</span> "password" => "secret", <span>22</span> "dbname" => "blog" <span>23</span> <span> )); </span><span>24</span> });
以上这两种服务的注册方式产生相同的结果。然后,通过数组定义的,在后面需要的时候,你可以修改服务参数:
<span>1</span> <?<span>php </span><span>2</span> <span>3</span> <span>$di</span>->setParameter("db", 0, <span>array</span><span>( </span><span>4</span> "host" => "localhost", <span>5</span> "username" => "root", <span>6</span> "password" => "secret" <span>7</span> ));
从容器中获得服务的最简单方式就是使用”get”方法,它将从容器中返回一个新的实例:
<span>1</span> <?<span>php </span><span>2</span> <span>$request</span> = <span>$di</span>->get("request");
或者通过下面这种魔术方法的形式调用:
<span>1</span> <?<span>php </span><span>2</span> <span>3</span> <span>$request</span> = <span>$di</span>-><span>getRequest(); </span><span>4</span> <span>5</span> Phalcon\DI 同时允许服务重用,为了得到一个已经实例化过的服务,可以使用 getShared() 方法的形式来获得服务。
具体的 Phalcon\Http\Request 请求示例:
<span>1</span> <?<span>php </span><span>2</span> <span>3</span> <span>$request</span> = <span>$di</span>->getShared("request");
参数还可以在请求的时候通过将一个数组参数传递给构造函数的方式:
<span>1</span> <?<span>php </span><span>2</span> <span>3</span> <span>$component</span> = <span>$di</span>->get("MyComponent", <span>array</span>("some-parameter", "other"))