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 { 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 controllers for fields with any visibility (including protected, private), and this is due to a slight impact on performance, and besides this, the controller itself is a container of services (and has a get () method similar to our ServiceLocator :: get ());
- in any classes (services) for public fields, since in this case, no reflection will be created, and a simple assignment of $ service-> field = $ injected_service will be used, which will result in an exception for private / protected fields.
In our implementation, reflection is always created, the implementation will always end successfully.
3) Implementation through the method
class B { protected $helper_time; 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.phpIt 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 $securityManager; 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/SimpleDiTheory
ru.wikipedia.org/wiki/Deavability DeploymentOne of the best implementations of
symfony.com/doc/current/components/dependency_injection/index.html