📜 ⬆️ ⬇️

How to start using DI

Many times I came across the opinion that DI is something complicated, cumbersome, slow, suitable only for “big” projects, and therefore its use specifically on the current task (500+ model classes, 300+ controller classes) is unjustified. This is partly due to the fact that DI is definitely associated with packages like the Symfony “The Dependency Injection Component”, which obviously more than covers all possible dependency injection options.
Here I want to give a certain functional minimum, which will give an understanding of the concept itself, in order to show that dependency inversion itself can be quite simple and concise.

Content


The implementation is 2 classes of 500 lines of code:
SimpleDi \ ClassManager - provides information about classes. For full-fledged work, it needs a cacher (we use Doctrine \ Common \ Cache \ ApcCache), this will allow not to create reflections every time the script is called. Parses annotations for subsequent injection. It is also possible to use it in the bootloader, since it stores the path to the class file.
SimpleDi \ ServiceLocator - creates and initializes the services requested from it. It is this class that produces injections.
1) In the simplest case, when no settings are specified for the class, SimpleDi \ ServiceLocator works in the same way as the multiton pattern (also known as the Object Pool).
$service_locator->get('HelperTime'); 

2) Embedding option through the field
 class A { /** * @Inject("HelperTime") * @var HelperTime */ protected $helper_time; } $service_locator->get('A'); 

This option should be used exclusively in controllers, since reflection will be created for the implementation, which affects the performance for the worse. One class will not affect the invocation of a script with several fields in any way at the time of loading the page, but if it is used everywhere, the loss of productivity will be quite noticeable.
Here you want to make a digression towards symfony. There such an introduction is permissible:

In our implementation, reflection is always created, the implementation will always end successfully.
3) Implementation through the method
 class B { /** * @var HelperTime */ protected $helper_time; /** * @Inject("HelperTime") * @param HelperTime $helper */ public function setHelperTime($helper) { $this->helper_time = $helper; } } $service_locator->get('B'); 

This option is the most acceptable and along with the implementation through the field should be used to set default dependencies.
4) Implementation via config
 $service_locator->setConfigs(array( 'class_b_service' => array( 'class' => 'B', 'calls' => array( array('setHelperTime', array('@CustomHelperTime')), ) ) )); $service_locator->get('class_b_service'); 

This is what dependency injection is used for. Now through the settings it is possible to replace the helper used in class B, while the class B itself will not change.
5) Create a new instance of the class. When it is necessary to have several objects of the same class, it is possible to use the ServiceLocator as a factory.
 $users_factory = $service_locator; $users_row = array( array('id' => 1, 'name' => 'admin'), array('id' => 2, 'name' => 'guest'), ); $users = array(); foreach ($users_rows as $row) { $user = $users_factory->createService('User'); $user->setData($row); } 


Example


Take an arbitrary useful library and try to embed it in our project. Suppose this is github.com/yiisoft/yii/blob/master/framework/utils/CPasswordHelper.php
It turns out that we cannot do this, because the class is rigidly tied to the absolutely unnecessary classes Yii and CException.
 class CPasswordHelper { … public static function generateSalt($cost=13) { if(!is_numeric($cost)) throw new CException(Yii::t('yii','{class}::$cost must be a number.',array('{class}'=>__CLASS__))); $cost=(int)$cost; if($cost<4 || $cost>31) throw new CException(Yii::t('yii','{class}::$cost must be between 4 and 31.',array('{class}'=>__CLASS__))); if(($random=Yii::app()->getSecurityManager()->generateRandomString(22,true))===false) if(($random=Yii::app()->getSecurityManager()->generateRandomString(22,false))===false) throw new CException(Yii::t('yii','Unable to generate random string.')); return sprintf('$2a$%02d$',$cost).strtr($random,array('_'=>'.','~'=>'/')); } } 

In order to make the class available for any project, it would be sufficient to describe the dependencies correctly:
 class CPasswordHelper { /** *      public ,        , *    . * @Inject * @var \Yii\SecurityManager */ public $securityManager; /** *   * @Inject * @var \YiiExceptor */ public $exceptor; … public function generateSalt($cost=13) { if(!is_numeric($cost)) $this->exceptor->create('yii','{class}::$cost must be a number.',array('{class}'=>__CLASS__)); $cost=(int)$cost; if($cost<4 || $cost>31) $this->exceptor->create('yii','{class}::$cost must be between 4 and 31.',array('{class}'=>__CLASS__)); if(($random=$this->securityManager->generateRandomString(22,true))===false) if(($random=$this->securityManager()->generateRandomString(22,false))===false) this->exceptor->create('yii','Unable to generate random string.'); return sprintf('$2a$%02d$',$cost).strtr($random,array('_'=>'.','~'=>'/')); } } 

And start a class - exception generator
 class YiiExceptor { public function create($a, $b, $c = null) { throw new CException(Yii:t($a, $b, $c)); } } 


Conclusion


Using DI allows you not to think about the context in which your module will be used. It makes it possible to transfer a separate class to another project without recruiting (often hierarchical) dependencies. When using annotations, you will not have to deal with the explicit creation of objects and the explicit transfer of parameters and services to the object. And, of course, such a class is much easier to test than tied to static methods or explicitly creating instances of a class, instead of using a factory.
')

Links


Example github.com/mthps/SimpleDi
Theory ru.wikipedia.org/wiki/Deavability Deployment
One of the best implementations of symfony.com/doc/current/components/dependency_injection/index.html

Source: https://habr.com/ru/post/191168/


All Articles