One Abstract Action Factory For All
Use one abstract action factory for all PSR-7 actions.
Yesterday I wrote about using one ActionFactory for all your PSR-7 actions. I used zend-servicemanager for it, together with some voodoo to detect the dependencies and inject it. I was pretty happy with the solution and then I got this:
@geerteltink question: why not use abstract_factories? while it may take slower, it will reduce repetitive reg with same factory
After some more research I decided to try it out and it’s actually pretty brilliant. Zend ServiceManger 3 is needed for this.
<?php
namespace App\Action;
use Interop\Container\ContainerInterface;
use ReflectionClass;
use Zend\ServiceManager\Factory\AbstractFactoryInterface;
class AbstractActionFactory implements AbstractFactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
// Construct a new ReflectionClass object for the requested action
$reflection = new ReflectionClass($requestedName);
// Get the constructor
$constructor = $reflection->getConstructor();
if (is_null($constructor)) {
// There is no constructor, just return a new class
return new $requestedName;
}
// Get the parameters
$parameters = $constructor->getParameters();
$dependencies = [];
foreach ($parameters as $parameter) {
// Get the parameter class
$class = $parameter->getClass();
// Get the class from the container
$dependencies[] = $container->get($class->getName());
}
// Return the requested class and inject its dependencies
return $reflection->newInstanceArgs($dependencies);
}
public function canCreate(ContainerInterface $container, $requestedName)
{
// Only accept Action classes
if (substr($requestedName, -6) == 'Action') {
return true;
}
return false;
}
}
As you can see, the code is almost the same as what I did before with the ActionFactory. However it
now extends an AbstractFactoryInterface
and it has this canCreate
method.
To register the factory you need to add this line:
'dependencies' => [
'invokables' => [
],
'factories' => [
],
'abstract_factories' => [
App\Action\AbstractActionFactory::class,
],
],
And now the most brilliant part… Remove all action factories from dependencies -> factories / invokables. Yes you read it correctly, you can remove them. The abstract factory will automatically handle all actions from now on.
In case you used the expressive-skeleton, remove these two lines from routes.global.php
:
'dependencies' => [
'invokables' => [
App\Action\PingAction::class => App\Action\PingAction::class,
],
'factories' => [
App\Action\HomePageAction::class => App\Action\HomePageFactory::class,
],
],
While trying to get homepage, under the hood the container (still zend-servicemanager) is looking
for the HomePageAction
class at the usual locations. But since it’s not registered with the
container, it falls back to this abstract factory. In its canCreate
method it tells the container
it can handle all classes ending with Action
. After that the abstract factory returns the Action
class with the right dependencies.
There might be a downside though. Since the servicemanager checks if unregistered classes can be handled by a specific abstract factory, it causes some overhead. As long as you limit the number of abstract factories you still have a good performance.
At the time of writing this solution is used for this site and it’s open source.