Autowiring Vfs in a Horde App

The Horde Vfs is an abstraction around storing and retrieving files. Calling code does not care about where the Vfs is actually stored, be it a remote filesystem, a dav resource, a database or a path in the local filesystem. Autowiring means the Injector knows how to create a class using some other class without having it explicitly defined

For this article, I want to get an instance of Horde\MyApp\ReportGenerator which uses the VFS to store reports.

First let’s create the ReportGenerator.

lib/ReportGenerator.php

<php
namespace Horde\MyApp;
class ReportGenerator
{
    private $driver;
     public function __construct(\Horde_Vfs_Base $driver
     {
         $this--->driver = $driver;
     }
     ...
}

When a controller in the app wants a report generator, it would require it through the dependency injector like this:


$reportGenerator = $injector->getInstance('Horde\MyApp\ReportGenerator');

Autowiring allows the injector to return an instance even if it has not been explicitly bound to the injector. For this to work, all required dependencies must be interfaces which have either been registered with the injector or can themselves be created from registered interfaces or have no dependencies themselves.

Now, Horde_Vfs_Base is the common base class of all Horde Vfs drivers.

However, Horde does not provide a default binding for Horde_Vfs_Base even though Horde_Core has a default factory Horde_Core_Factory_Vfs.

Thus, we need to define one. In Application.php, look for a method _bootstrap(). We will use it to setup the injector binding. Normally, we would just want to use the existing factory through $injector->bindFactory($interface, $factory, $method);

We cannot do this in this case. The Vfs Factory’s create method has two string parameters to allow creating multiple Vfs instances. For our app, we just want to get the default configured in horde base. But this doesn’t work. bindFactory would automatically pass a child injector to the create method. This would override the default first parameter “horde”. In the end, we would receive the factory’s exception saying we should configure a backend first – even if we did.

To work around this, we could create an own factory class in our app which uses Horde_Core_Factory_Vfs as a dependency. Horde_Injector would just know what to do. But let’s not do this as it’s quite some boilerplate just for creating a Vfs with default configuration.

Instead, let’s use the closure binder.


protected function _bootstrap()
{
     $injector = $GLOBALS['injector'];
     $vfsClosure = function(\Horde_Injector $injector) {
     return $injector->getInstance('Horde_Core_Factory_Vfs')->create();
};
$injector->bindClosure('Horde_Vfs_Base', $vfsClosure);

Now injector knows how to provide a Horde_Vfs_Base implementation which the ReportGenerator requires. We can ask injector to provide an instance. Injector will look if it already has an instance from a previous call, otherwise runs the closure to create the dependency, then create the actual ReportGenerator instance.

Leave a Reply

Your email address will not be published. Required fields are marked *