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? Then you’re going to want to know about a powerful new 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 the Zend\ServiceManager returns it.
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 spin on it:
Delegator factories allow you to add dynamically functionality 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 returns 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 DI container requests the Application service, 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 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
Join the discussion
comments powered by Disqus