Pragmatism in the real world

Using abstract factories with Slim 3

In my Slim 3 skeleton, I chose to put each action into its own class so that its dependencies are injected into the constructor. We then register each action with the DI Container like this:

$container['App\Action\HomeAction'] = function ($c) {
    return new App\Action\HomeAction($c['view'], $c['logger']);
};

In this case, HomeAction requires a view and a logger in order to operate. This is quite clear and easy. However, it requires you manually register each action class with the DI Container.

Bruno Skvorc said this on Twitter:

In short, this seems ULTRA wasteful if I want ALL my controllers to get view and logger.

Slim 3’s default DI container is Pimple, which is one of the simpler DI Containers out there. To solve Bruno’s problem, my initial though was to use Zend\ServiceManager‘s abstract factories feature.

Fortunately, Slim 3 supports container-interop and I’ve already written RKA\ZsmSlimContainer which integrates Zend\ServiceManager with Slim 3. Once we have ZSM in place it’s all quite easy!

We need to register our own abstract factory with Zend\ServiceManager. An abstract factory requires two methods to be implemented: canCreateServiceWithName and createServiceWithName. The method names are fairly self-explantory.

In our case, canCreateServiceWithName needs to return true if the requested class name ends in “Action” and then createServiceWithName simply creates the class with the two required dependencies. It looks like this:

app/src/ActionAbstractFactory.php:

<?php
namespace App;

use Zend\ServiceManager\AbstractFactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;

class ActionAbstractFactory implements AbstractFactoryInterface
{
    public function canCreateServiceWithName(
        ServiceLocatorInterface $locator, $name, $requestedName)
    {
        if (substr($requestedName, -6) == 'Action') {
            // This abstract factory will create any class that
            // ends with the word "Action"
            return true;
        }
        return false;
    }
 
    public function createServiceWithName(
        ServiceLocatorInterface $locator, $name, $requestedName)
    {
        $className = $requestedName;

        // This factory creates Actions that have precisely two
        // constructor parameters: $view & $logger
        $view = $locator->get('view');
        $logger = $locator->get('logger');
        
        return new $className($view, $logger);
    }
}

We then register the abstract factory with the DI container:

$container->addAbstractFactory(new App\ActionAbstractFactory());

The end result is that we don’t need to register every Action with the DI Container individually as long as they all have the same constructor signature. This is obviously a simplistic example, but the principle applies to any situation where you need to create different classes in the same vein.

If you want to see this in action, I’ve created a sample project on GitHub.

2 thoughts on “Using abstract factories with Slim 3

  1. Hi Rob, nice tutorial, simple and easy.
    i tried to implement it but im getting know addAbstractFactory method .
    i loaded "zendframework/zend-servicemanager" and "akrabat/rka-slim-zfsm-container" inside composer and edited the required file…
    i there anything else im missing?
    thank you

    1. Hi Genius,
      I've just tried it myself and it worked fine. Have you made sure to call Rob's custom container class instead of the slim one? e.g. "$container = new RKA\ZsmSlimContainer\Container($settings);" and not "new \Slim\Container($settings)"

Comments are closed.