Sphicy helps you to create your objects in a reasonable way. It makes use of the dependency injection pattern. Its architecture is loosly based on Google's Guice framework.
In an application each object has dependencies with other objects. Dependency injection (DI) simply states that these dependencies shall be made explicit. If class A depends on class B, then the constructor of A should require an object ob B to be created. In a perfect world of DI all dependencies are given through the constructor. As alternative setters methods are possible which are enforced to be called after object creation.
Sphicy has 4 terms that describe its functionality:
To use Sphicy you would create your own concrete module implementation and have the injector and binder tie it all together to instantiate the objects you want to have.
Imagine we have remote and it always has a device it depends on. We have to interfaces describing this world:
interface Remote {
public function __construct(Device $device);
public function pressButtonOn();
public function pressButtonOff();
public function getDevice();
}
interface Device {
public function switchOn();
public function switchOff();
}
Now if you have to concrete implementations TVRemote and TV you would always have to instantiate a TV and put it into the TVRemote constructor to get your remote. Sphicy extracts this dependency graph into a module, that can be reused throughout your application to build TVs:
class MyTelevisionModule implements spModule {
public function configure(spBinder $b)
{
$b->bind("Remote")->to("TVRemote");
$b->bind("Device")->to("TV");
}
}
Retrieving your Remote object would then look like:
$injector = new spDefaultInjector(new MyTelevisionModule());
$remote = $injector->getInstance('Remote');
There are different possibilites to handle multiple concrete implementations:
You can bind certain interfaces to annotations rather than the interface name itself. When you try to create an object and explicitly state that you want to use an annotation, this annotation is given deep down to all dependencies.
You could also use the specific
doc-block syntax and put @annotate $paramName annotationName into it.
You can bind a provider to a specific interface. The provider has
to implement the interface spProvider and requires one
method get($name, array $args, $annotation) which you can implement.
This is the way to create specialized factories within Sphicy.
Say you have a module which binds an interface to a provider:
class MyDeviceProviderModule implements spModule {
public function configure(spBinder $b)
{
$b->bind("Device")->toProvider(new MyDeviceProvider());
$b->bind("Remote")->to("UniversalRemote");
}
}
class MyDeviceProvider implements spProvider
{
public function get($name, $args)
{
if($args['type'] == "TV") {
return 'MyTV';
} elseif($args['type'] == "DvdPlayer") {
return 'MyDvdPlayer';
} else {
throw new spProviderException;
}
}
}
Now we can decide upon which device is instantiated by calling:
$injector = new spDefaultInjector(new MyDeviceProviderModule());
$tv = $injector->getInstance('Device', array('type' => 'TV'));
$dvd = $injector->getInstance('Device', array('type' => 'DvdPlayer'));
The MyDeviceModule implementation looks tedious but some
complex cases of dependency injection might need such a setup.
Additionally you could use Providers as wrappers around existing factories or assembling stratgies of your own code basis or frameworks that you are using. Additional uses cases would be configuring persistent objects.
Objects that use setter injection can also be created by providers, like this example shows:
class myPersistentObjectProvider implements Provider {
public function get($name, $args) {
$object = new MySetterObject();
$object->setStuff($args["stuff"]);
$object->setFoo($args["bar"]);
return $object;
}
}
As you can see, Providers can return either strings of class names or objects as their target implementation.
Sphicy allows to have single instances across the object tree without the need for static, global singletons.
You can make use of the binding to an instance, so that this instance is used as placeholder for all "Interface" implementations.
class MySingletonInstanceModule implements spModule {
public function configure(spBinder $b) {
$b->bind("Interface")->toInstance(new Instance());
}
}
This case is only useful for singletons that are leafes in the object graph. When the singleton should create more objects deep down that might even be singletons on their own in other parts of the graph you have to use the following nice solution:
class MyEagerSingletonModule implements spModule {
public function configure(spBinder $b) {
$b->bind("ArrayAccess")->to('ArrayObject').asEagerSingleton();
}
}
Now the ArrayObject is instantiated upon first request and
buffered in a layer inside the injector. If you use this injector to
request more implementations of ArrayAccess it will always return
the single instance that was created earlier.
This is by far the most beautiful way to use a singleton instance without having to use the Singleton pattern with global static accessors. You can use exactly the same object in two very different positions of the object graph without having to pass it around through all its intermediates.
First a note: Sphicy works best with constructors that require only objects as arguments.
The second argument of the getInstance() injector method
allows to pass arguments as key value pairs of "argument variable name" and
"argument value".
$injector->getInstance("Interface", array("argName1" => "argValue1"));
This argument list is passed down to nested getInstance() calls.
You can write a custom module that accepts the configuration parameters and implement binding decisions based on this information.
If an object allows to be passed in as "null" and no concrete interface binding is found Sphicy will automatically detect this and insert null as parameter.
If the framework is engineered with good OO-practices in mind you are probably possible to use Sphicy. Sample framework modules for Sphicy will be implemented sooner or later.
If you have build a module for Sphicy that helps bootstrap framework dependencies you can of course contribute this back to be included in Sphicy by default as a TieIn component.