Continued, beginning in
Part 1Phemto 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