📜 ⬆️ ⬇️

Phemto and the Pattern Dependency Injection. Part 2

Continued, beginning in Part 1

Phemto installation


Phemto is distributed with a simple tarball, so just unpack it ...
  tar -zxf phemto_0.1_alpha6.tar.gz 

It is enough to use require_once () to include the phemto.php file .

The only Phemto dependency is the mechanism of PHP reflection.

Phemto in your program


Phemto is best used in the main script or class application or framework.
')
First you write classes as usual ...


class Permissions { ... }

class Authentication {
function __construct ( $ permissions ) { ... }
}

class MyPage implements Page {
function __construct ( $ authentication ) { ... }
}


Conventional Page Controller Architecture We can easily make a unit test for the Page , since its dependency on Authentication is passed to the constructor. We can use test version simulation and concentrate on logic.

Now we will write a file with a chain configuration, let's call it “wiring.php”. It contains all the necessary settings for our application ...


require_once ( 'phemto / phemto.php' ) ;

$ injector = new Phemto ( ) ;
$ injector -> forVariable ( 'authentication' ) -> willUse ( 'Authentication' ) ;
$ injector -> whenCreating ( 'Authentication' )
-> forVariable ( 'permissions' ) -> willUse ( new Sessionable ( 'Permissions' ) ) ;
return $ injector ;


Here we tell our injector that if it sees the $ authentication argument, then we need to create an instance of Authentication . The object for the $ permissions argument has a different life cycle. Sessionable says that if possible, you need to take an object from the session, otherwise create it and save it in the session so that the object will be created only once.

Our main script instead of new is now using calls from the Phemto factories ...


require_once ( 'lib / my_page.php' ) ;

$ injector = include ( 'wiring.php' ) ;
$ page = $ injector -> create ( 'Page' ) ;
?>
< html > ... </ html >


Thus, our code is so isolated from the main script that we can add and remove dependencies between classes without interfering with the main script.

Framework authors usually have a different purpose. Most likely they already have a central point for creating pages, and the main work is to adapt the components of third-party developers.

Suppose we want to write an implementation of Authentication based on the framework interface ...


interface Authentication { ... }

class InternalFrontControllerActionChainThingy {
function __construct ( Authentication $ authentication , ... ) { ... }
}


Our component will use a common database connection with the framework, and we also want to take a third-party caching component.


require_once ( 'cache.php' ) ;

class OurAuthentication implements Authentication {
function __construct ( Database $ database , DatabaseCache $ cache ) { ... }
}


For a factory-based framework, this alignment is close to a nightmare, because the framework does not know how to create a cache component, and where to put it. To force us to transfer the framework and the factory is not an option, since the framework will still have to issue and put a caching component somewhere. If the framework uses Dependency Injection , then the task is reduced only to setting up the chain.

The chain can be changed directly using a custom file ...


$ injector = include ( 'framework / wiring.php' ) ;
$ injector -> willUse ( 'OurAuthenticator' ) ;
return $ injector ;


However, most likely, the framework will place the DI tool in its registration system ...


class FrameworkRegistration {
...
static function register ( $ class , $ dependencies = array ( ) ) {
$ this -> injector -> whenCreating ( 'Controller' ) -> willUse ( $ class ) ;
foreach ( dependencies as $ dependency ) {
$ this -> injector -> whenCreating ( 'Controller' )
-> whenCreating ( $ class )
-> willUse ( $ dependency ) ;
}
}
}



And then we can make such a call ...


FrameworkRegistration :: register ( 'OurAuthentication' , array ( 'DatabaseCache' ) ) ;


Phemto Chain Syntax


The simplest case of creating a Phemto object is through the class name ...


class Porsche911 { }

$ injector = new Phemto ( ) ;
$ car = $ injector -> create ( 'Porsche911' ) ;


Among the registered classes will be found suitable.

If only one class can satisfy the condition, then it is this class that will create the object. Phemto in this matter is smart enough and understands abstract classes and interfaces ...


abstract class car { }
class Porsche911 extends Car { }

$ injector = new Phemto ( ) ;
$ car = $ injector -> create ( 'Car' ) ;


Here $ car is an instance of the Porsche911 class. Also…


interface Transport { }
class Porsche911 implements Transport { }

$ injector = new Phemto ( ) ;
$ car = $ injector -> create ( 'Transport' ) ;


An object of the Porsche911 class will be created again , as the only possible option.

If there is ambiguity, then Phemto will throw an exception. The ambiguity can be resolved by adding more information to the chain ...


interface Transport { }
class Porsche911 implements Transport { }
class RouteMaster implements Transport { }

$ injector = new Phemto ( ) ;
$ injector -> willUse ( 'Porsche911' ) ;
$ car = $ injector -> create ( 'Transport' ) ;


This is convenient and when you need to override the standard implementation, while the standard class is registered in the system.

Phemto has two methods for automatically creating parameters. The first is with the help of the type ...


interface Engine { }

class Porsche911 {
function __construct ( Engine $ engine ) { }
}

class Flat6 implements Engine { }

$ injector = new Phemto ( ) ;
$ car = $ injector -> create ( 'Porsche911' ) ;


This is equivalent to the new Porsche911 (new Flat6 ()) . This method is convenient for framework authors, who only need to specify interface names.

Please note - we did not have to change the main code, even though we changed the constructor signature.

Another way - Phemto can create a parameter by the name of the argument ...


class Porsche911 {
function __construct ( $ engine ) { }
}

interface Engine { }
class Flat6 implements Engine { }

$ injector = new Phemto ( ) ;
$ injector -> forVariable ( 'engine' ) -> willUse ( 'Engine' ) ;
$ car = $ injector -> create ( 'Porsche911' ) ;


Again, for $ car we create an object of class new Porsche911 (new Flat6 ()) . Here we used the name of the $ engine argument to calculate the interface. And further Phemto could apply the rules of automation.

Sometimes it is still necessary to pass parameters to the constructor. The easiest way to do this is to add them to the create method ...

class Porsche911 {
function __construct ( $ fluffy_dice , $ nodding_dog ) { }
}

$ injector = new Phemto ( ) ;
$ car = $ injector -> create ( 'Porsche911' , true , false ) ;


These parameters will take their place in the constructor, in this case we get a new Porsche911 (true, false) .

Unnamed parameters can be the cause of errors when the code becomes more complicated, so that you can use the parameters with the names ...


class Porsche911 {
function __construct ( $ fluffy_dice , $ nodding_dog ) { }
}

$ injector = new Phemto ( ) ;
$ car = $ injector -> fill ( 'fluffy_dice' , 'nodding_dog' )
-> with ( true , false )
-> create ( 'Porsche911' , true ) ;


These parameters can also be used with dependencies.

Phemto can call and methods other than the constructor ...


interface Seat { }
interface SportsCar { }

class Porsche911 implements SportsCar {
function fitDriversSeat ( Seat $ seat ) { }
}

class BucketSeat implements Seat { }

$ injector = new Phemto ( ) ;
$ injector -> forType ( 'SportsCar' ) -> call ( 'fitDriversSeat' ) ;
$ car = $ injector -> create ( 'Porsche911' ) ;


This code is similar to ...

$ car = new Porsche911 ( ) ;
$ car -> fitDriversSeat ( new BucketSeat ( ) ) ;


Such a call to methods other than the constructor is called setter injection .

It is not always necessary to create the same object. Sometimes the choice has to be determined by the context ...


interface Seat { }

class Car {
function __construct ( Seat $ seat ) { }
}
class FordEscort extends Car ;
class Porsche911 extends Car ;

class StandardSeat implements Seat { }
class BucketSeat implements Seat { }

$ injector = new Phemto ( ) ;
$ injector -> willUse ( 'StandardSeat' ) ;
$ injector -> whenCreating ( 'Porsche911' ) -> willUse ( 'BucketSeat' ) ;
$ car = $ injector -> create ( 'Porsche911' ) ;


You can be sure that by default $ seat will be an object of the StandardSeat class, but BucketSeat will be used for the Porsche911 .

The whenCreating () method will create a new nested version of Phemto , so that the context affects all previous settings in the chain, i.e.


class Car {
function __construct ( $ seat ) { }
}
class FordEscort extends Car ;
class Porsche911 extends Car ;

class StandardSeat { }
class BucketSeat { }

$ injector = new Phemto ( ) ;
$ injector -> willUse ( 'StandardSeat' ) ;
$ injector -> whenCreating ( 'Porsche911' )
-> forVariable ( 'seat' ) -> willUse ( 'BucketSeat' ) ;
$ car = $ injector -> create ( 'Porsche911' ) ;


The life cycle of objects created using Phemto can be monitored.

Phemto has built-in classes: Factory (by default), which always creates a new instance of an object, Reused which gives references to the same instance, and Sessionable , which stores an instance of an object in the PHP $ _SESSION system variable. They all inherit from the base abstract class Lifecycle . Developers can extend these classes ...

Here we will create a single instance of the Porsche911 object and will distribute links.


class Porsche911 { }

$ injector = new Phemto ( ) ;
$ injector -> willUse ( new Reused ( 'Porsche911' ) ) ;
$ car = $ injector -> create ( 'Porsche911' ) ;
$ same_car = $ injector -> create ( 'Porsche911' ) ;


$ car and $ same_car will refer to the same object. In the end, Porsche cars are quite expensive.

Links and additional information

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


All Articles