Dependency Injection with PHP: Introducing Sphicy

I have written on dependency injection before christmas and how Guice, googles DI framework, offers a simple solution. I copied the functionality for a PHP clone of Guice and named it Sphicy. You saw an early prototype of it in the blogpost mentioned above.

Sphicy configures object dependencies with modules: You explicitly state which implementation should be bound to which interface. An injector then creates instances of these objects via reflection: All constructor dependencies are resolved by looking at the given type hints and initializes those according to the specified bindings.

Two examples included in the source code of Sphicy are Zend Framework and ezComponents MVC bootstrapping modules. Sadly both frameworks default front controllers are engineered in such a way that useful dependency injection needs some workarounds.

As an example I will now discuss the Sphicy Module for Zend Framework MVC applications. To circumvent the singleton and protected Constructor of Zend_Controller_Front, we have to build a new front controller that wraps around it and requires all the dependencies:

class Sphicy_Controller_Front
{
    protected $front;

    /**
     * Create Front Controller for Zend Framework using explicitly dependencies created by Sphicy.
     *
     * @param Zend_Controller_Request_Abstract      $request
     * @param Zend_Controller_Response_Abstract     $response
     * @param Zend_Controller_Router_Interface      $router
     * @param Zend_Controller_Dispatcher_Interface  $dispatcher
     */
    public function __construct(
        Zend_Controller_Request_Abstract $request,
        Zend_Controller_Response_Abstract $response,
        Zend_Controller_Router_Interface $router,
        Zend_Controller_Dispatcher_Interface $dispatcher=null
    )
    {
        $front = Zend_Controller_Front::getInstance();
        $front->setRequest($request);
        $front->setResponse($response);
        $front->setRouter($router);

        if($dispatcher === null) {
            $dispatcher = $front->getDispatcher();
        }
        $front->setDispatcher($dispatcher);
    }

    public function dispatch()
    {
        $this->front->dispatch();
    }
}

You can see that the Sphicy_Controller_Front class requires dependencies in its constructor that are then forward injected into Zend_Controller_Front. You can now create a module that binds all the required dependencies to concrete implementations, for example a module for a Zend MVC Http Application might look like:

class Sphicy_ZendMvc_ExampleModule implements spModule {
     public function configure(spBinder $binder) {
         // Sphicy_Controller_Front does not extend Zend_Controller_Front, because of Singletonitis
         // It offers the dispatch method to proxy against Zend_Controller_Front::dispatch.
         $binder->bind("Sphicy_Controller_Front")->to("Sphicy_Controller_Front");
         $binder->bind("Zend_Controller_Request_Abstract")->to("Zend_Controller_Request_Http");
         $binder->bind("Zend_Controller_Response_Abstract")->to("Zend_Controller_Response_Http");
         // loads all routes
         $binder->bind("Zend_Controller_Router_Interface")->to("MyApplication_Router");
     }
 }

The class MyApplication_Router might look up all the routing information of the application via a hardcoded configuration mechanism. You may say this is a hard dependency, but actually you can just switch modules or implementations at this position to replace the router with another implementation. You can also see that no implementation for the dispatcher is bound. But this dependency is optional and will be created automatically as can be seen in the Sphicy_Controller_Front class.

The front controllers is now created by calling:

$injector = new spDefaultInjector(new Sphicy_ZendMvc_ExampleModule());
$front = $injector->getInstance("Sphicy_Controller_Front");
$front->dispatch();

What happens in the $injector->getInstance() line? Sphicy looks at Sphicy_Controller_Front‘s constructor and finds that four dependencies are needed: Request, Response, Router and Dispatcher implementations. It looks up the bindings and searches for them, creating Zend_Controller_Request_Http, Zend_Controller_Response_Http and MyApplication_Router objects. A dispatcher implementation is not found, but Sphicy recognizes that null is a valid paramater and injects it. The 3 concrete implementations and one null are instantiated and used to construct a valid Sphicy_Controller_Front instance.

You have now stated the dependencies of the Zend Controller Front explicitly and can switch them instantaneously by switching the bindings of interface to implementations in the configuration module.

Have a look at the Sphicy Website and FAQ to see more examples and information about the possibilites of this dependency injection framework.