📜 ⬆️ ⬇️

Phemto and the Pattern Dependency Injection. Part 1

I have not seen a good description of the Dependency Injection pattern applied to PHP.

Recently, the guys from symfony released their container DI , providing it with a detailed and good book on how to work with this pattern.

I remembered another library for DI, Phemto . Its author, Markus Baker, creator of SimpleTest . Unfortunately, the site contains a brief and vague help. Nevertheless, the project is developing, and inside the distribution is an article with a very good explanation about DI, and of course the manual. Phemto, is a very tiny project consisting of three not very large files.
')
It seemed to me useful to translate the article into Russian and put it here. The article is not very big, but informative. I can not give a link to the original, the original is inside the distribution kit :)


In programmer jargon, Phemto is a lightweight, automated dependency injection container. Simply put, the task of Phemto is to create an instance of an object, obtaining a minimum of information, thus significantly weakening dependencies within an application or framework.

Why do you need it?

The easiest way to understand the DI pattern is to imagine a scale with “Use DI” at one end and “Use hard coding (i.e. hard-coded connections)” at the other. We are now going to arrange a little trip from hardcoding through the patterns Factory , Registry , Service Locator to DI. If you already know what DI is, go directly to
Phemto installation .

The ordinary creation of objects using the new operator looks simple and straightforward, but we will most likely encounter difficulties when we want to change something later. Let's look at the code ...


class MyController {
function __construct ( ) {
...
$ connection = new MysqlConnection ( ) ;
}
}


Here MyController depends on MysqlConnection .

The new operator is clear and understandable, but MyController will only be able to use the MySQL database. A little remake of the class so that it can be inherited can hardly help, since then we will have in the successor along with the logic of the child controller and the logic of getting the database driver. In any case, multiple dependencies are not solved by inheritance, leading to cluttering up the class. Generally speaking, you can play an inheritance card only once.

The next step is to use the Factory ...


class MyController {
function __construct ( $ connection_pool ) {
...
$ connection = $ connection_pool -> getConnection ( ) ;
}
}


Very effective solution. The factory can be configured to the desired type of driver using the configuration file or explicitly. Factories can often create objects from different families of objects, and then they are called Abstract Factory (Abstract Fabroika) or Repository (Repository). However, there are limitations.

Factories bring a lot of extra code. If you need to test classes using mock-objects, you will have to simulate not only the objects returned by the factory, but also the factory itself. Get a little extra fuss.

Yes, and in live code, if you need to return an object about which the author of the factory did not think, you will have to inherit or rewrite the factory itself, which can be a noticeable problem for frameworks.

The next move in our fight against dependencies is to generally take out the creation of a Registry object from the main object to the outside ...


class MyController {
function __construct ( $ registry ) {
...
$ connection = $ registry -> connection ;
}
}
...
$ registry = new Registry ( ) ;
$ registry -> connection = new MysqlConnection ( ) ;
...
$ controller = new MyController ( $ registry ) ;


Registry is completely passive, but in the main code we create and reload many objects. We can even accidentally build up objects that will never be needed and leave this place.

In addition, using this approach, we will not be able to use lazy object creation (lazy loading). Failure is waiting for us, and if we want to return not the same adapter object to the database, but different objects.

Life will immediately deteriorate if in our example there are dependencies that need to be taken into account. Those. if, for example, to create an adapter object, it is not enough to make new , but you need to add some other object to the constructor. In general, pre-tuning threatens to become very confusing.

We can make the Registry pattern more sophisticated if we allow the Registry object to independently create instances of the necessary objects. Our facility has become a Service Locator ...


class MyController {
function __construct ( $ services ) {
...
$ connection = $ services -> connection ;
}
}
...
$ services = new ServiceLocator ( ) ;
$ services -> connection ( 'MysqlConnection' ) ;
...
$ controller = new MyController ( $ services ) ;


The settings can now be in any order, but the ServiceLocator needs to know how to create a MysqlConnection . The problem is solved with the help of factories or with the help of reflection tricks, although the transfer of parameters can be a very painstaking job. The life cycle of objects (for example, to return the same object, or create different ones) is now under the control of a programmer who can either program everything in factory methods or put everything into settings or plug-ins.

Unfortunately, this almost silver bullet has the same problem as the Registry . Any class that will use such an interface will inevitably depend on the Service Locator . If you try to mix the two systems with different service locators, you will feel that this is “not lucky.”

Dependency Injection comes a little on the other side. Let's look at our very first example ...


class MyController {
function __construct ( ) {
...
$ connection = new MysqlConnection ( ) ;
}
}


... and make an external dependency ...

class MyController {
function __construct ( Connection $ connection ) {
...
}
}


At first glance, it's just terrible. Now, after all, every time in the script you have to touch all these dependencies with your hands. Change the adapter to the database will have to make changes in hundreds of places. It would have been like that if we used new ...


$ injector = new Phemto ( ) ;
$ controller = $ injector -> create ( 'MyController' ) ;


Believe it or not, that's all we need.

The task of Phemto is to identify how to create an object, which makes it surprisingly cool to automate development. Only by the type of the parameter in the interface, it will show that MysqlConnection is the only candidate that satisfies the necessary type of Connection .

More complex situations may require additional information, which is usually contained in a chained file. Here is an example of such a real-life file so that you can feel the power of the pattern ...


require_once ( 'phemto / phemto.php' ) ;

$ injector = new Phemto ( ) ;
$ injector -> whenCreating ( 'Page' ) -> forVariable ( 'session' ) -> willUse ( new Reused ( 'Session' ) ) ;
$ injector -> whenCreating ( 'Page' ) -> forVariable ( 'continuation' ) -> willUse ( 'Continuation' ) ;
$ injector -> whenCreating ( 'Page' ) -> forVariable ( 'alerts' ) -> willUse ( 'Alert' ) ;
$ injector -> whenCreating ( 'Page' ) -> forVariable ( 'accounts' ) -> willUse ( 'Accounts' ) ;
$ injector -> whenCreating ( 'Page' ) -> forVariable ( 'mailer' ) -> willUse ( 'Mailer' ) ;
$ injector -> whenCreating ( 'Page' ) -> forVariable ( 'clock' ) -> willUse ( 'Clock' ) ;
$ injector -> whenCreating ( 'Page' ) -> forVariable ( 'request' ) -> willUse ( 'Request' ) ;
return $ injector ;


Such a number of settings is typical for a medium sized project.

Now the controller sets only the interface, and the work on creating objects is performed by an intermediary.
MyController should no longer know about MysqlConnection at all.
But $ injector knows about the one and the other. This is called inversion of control.


Continued in part 2

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


All Articles