What Are Delegator Factories and Why You Should Use Them

Ever wanted to dynamically expand the functionality of an object which you retrieve from your dependency injection container, based on different needs, yet without creating messy, hard to maintain configurations? If so, then you're going to want to know about a powerful technique called Delegator Factories.

If you have not heard of Delegator Factories before, here's a quote from Marco Pivetta (@ocramius), who initially implemented them.

They are pretty much a wrapper around a real factory: it allows us to either replace the actual service with a "delegate" or interact with an object produced by a factory before it is returned by the Zend\ServiceManager.

According to the official documentation, delegator factories:

Allow decoration of services created by your dependency injection container, across all dependency injection containers supported by Expressive.

To put my own spin on it, they allow you to add functionality dynamically to existing services.

Let's Consider a Practical Example

In the latest version of Zend Expressive (version 3.x), the ability to configure routes from a module's ConfigProvider class was separated out from the Application object, to an Application delegator factory called Zend\Expressive\Container\ApplicationConfigInjectionDelegator.php.

public function getRouteConfig() : array
{
    return [
        [
            'path'            => '/',
            'middleware'      => RenderMoviesMiddleware::class,
            'allowed_methods' => ['GET'],
        ],
    ];
}

What this means is that if you have been configuring your routes using a getRouteConfig function, such as in the above example, if you migrate from version 2 to 3 it's no longer going to work. However, with a small code change, we can use the delegator factory to dynamically wrap the Application service, and continue using this configuration-driven approach, as before.

To enable it, as you can see in the code sample below, we've added a delegators element to config/autoload/dependencies.global.php, which will return an array. In that array, we've added Zend\Expressive\Application::class as a key, which references an array with one element, ApplicationConfigInjectionDelegator.

'delegators' => [
    Zend\Expressive\Application::class => [
        Zend\Expressive\Container\ApplicationConfigInjectionDelegator::class,
    ]
],

What will happen is when the Application service is requested from the DI container, it will have the functionality from ApplicationConfigInjectionDelegator dynamically added to it, specifically the injectRoutesFromConfig method, which is required to, as the name implies, use our ConfigProvider class' getRouteConfig method to build up the application's routing table.

As a result:

  • We don't need to extend the Application object and define a new service, resulting in extra maintenance overhead for use, for the lifetime of the project.
  • We don't need to create a one-off configuration that may get overlooked at sometime in the future, leading to hard-to-find bugs.

That's a Wrap

The delegator factory might seem like an advanced topic, and perhaps at a development level it is. However, at a usage level, it's quite straight-forward and well worth using for the advantages that it provides.

If you would like help getting started, with Zend Expressive, check out the Zend Expressive Essentials book as well as the course I created from PluralSight. There are loads more resources, but I recommend these two as excellent places to start.

CC Image Courtesy of Grodenaue on Flickr



About Matthew

Matthew Setter Matthew Setter is a PHP & Zend Framework specialist. If you're in need of a custom software application, need to migrate an existing legacy application, or want to know your current application's GPA - get in touch.

Want To Be A Zend Framework Guru?

Drop your email in the box below, and get awesome tutorialsjust like this one — straight to your inbox, PLUS exclusive content only available by email.