Configuring the ServiceManager with Abstract Factories


One of the best features about Zend Framework 2 is undoubtedly the ServiceManager; because it makes it almost painless to configure and retrieve services in a consistent and predictable manner, anywhere in your application, at any time.

But the catch is, there’s quite a bit to learn if you want to use it properly. As well as that, there’s quite a number of ways to use it. For example, you can instantiate classes using the invokables, factories and abstract factories elements as well as being able to use closures.

Now each of these approaches have their place, as well as the pros and cons. For example, you can use closures when you creating a prototype and invokables for classes which require no constructor arguments.

You could then use factories for classes which have dependencies, managed via constructor injection. But it’s not all sweetness and light. Let’s have a quick look at the negative points.

You can use closures, but your configuration won’t be cacheable, resulting in a bottleneck in your application.

And if you create an invokable or factory configuration for each class, you may end up creating quite a lot of classes (and an accompanying maintenance headache).

Despite these points, throughout the manual you’ll see a lot of reference to these three approaches.

So I don’t blame you if you thought that these were the only options. But there’s a better option, one which I only started to appreciate recently.

So I’ve made it the subject of today’s post – abstract factories. Abstract Factories in Zend Framework 2 allow you to implement the pattern of the same name. If you’re not familiar with it,

The abstract factory pattern provides a way to encapsulate a group of individual factories that have a common theme without specifying their concrete classes. This pattern separates the details of implementation of a set of objects from their general usage and relies on object composition, as object creation is implemented in methods exposed in the factory interface.

What Are Abstract Factories?

In Zend Framework 2 specifically, abstract factories are a fallback instantiation method for the ServiceManager, allowing it to instantiate and return services if all of the other methods fail.

When a service is requested and the ServiceManager can’t find it, because you’ve not setup an explicit configuration for it, it will iterate through the configured abstract factories and see if one of them can provide a matching service.

Similar to the factories classes, abstract factories classes implements two interfaces:

  • Zend\ServiceManager\AbstractFactoryInterface
  • Zend\ServiceManager\ServiceLocatorInterface

As a result of these interfaces, they need to implement two methods:

  • canCreateServiceWithName
  • createServiceWithName

canCreateServiceWithName returns a boolean value, confirming whether the class can return the service requested or not. If true, then createServiceWithName is called which carries out the instantiation of the service, returning the object requested.

Both of these methods take three parameters, a ServiceLocatorInterface object and two strings, commonly set as $name and $requestedName.

The ServiceLocatorInterface object, provides access to the ServiceLocator. So if you need other services, then you’re able to access them through it.

The $requestedName parameter is the service name being requested which, for today’s post, will be YourModule\Table\YourFormName.

A Working Example

Right, now that we’ve talked about what they are and why they’re good to use, let’s work through an example. Firstly we’ll create the class, then we’ll cover the Module.php configuration required.

namespace Application\ServiceManager\AbstractFactory;

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

The first thing we need to do is to bring in the required use statements, specifically AbstractFactoryInterface and ServiceLocatorInterface. The other two are there as I’ve based this example on an existing codebase.

class TableAbstractFactory implements AbstractFactoryInterface
{

Next, we need to ensure that the class always implements AbstractFactoryInterface.

    public function canCreateServiceWithName(
        ServiceLocatorInterface $serviceLocator, $name, $requestedName
    ) {
        return (fnmatch('*Table', $requestedName)) ? true : false;
    } 

Now we create the canCreateServiceWithName function. First, we’re going to inspect the $requestedName argument. For this example, I’m attempting to retrieve a service called “YourModule\Table\MyUserTable”.

As this is a TableAbstractGateway file, responsible for instantiating table classes, we first check if the service name ends in Table. If it does, we then check if the class exists. If it does, we return true, otherwise false. For the rest of the example, we’ll assume that it does.

    public function createServiceWithName(
        ServiceLocatorInterface $serviceLocator, $name, $requestedName
    ) {
        if (class_exists($requestedName)) {
            $tableGateway = $requestedName . "Gateway";
            return new $requestedName($tableGateway);
        }

        return false;
    }
}

Next we implement the createServiceWithName method. This one handles instantiation and return of the service object. As the canCreateService method checks if we can do it, I’ve not checked the file name pattern again.

Instead I’ve checked if the class exists and if so, instantiated and returned it. The net result is that one class can now handle instantiation of nearly any object of a specific type; just like the abstract factory pattern is meant to do.

How Do You Configure Them

Now that we’ve setup the class to instantiate all of our table objects, we need to configure the ServiceManager in Module.php to make use of it. Honestly, I would have thought it would be more complicated than it is; it only requires one line. Have a look at the example below:

public function getServiceConfig()
{
    return array(
        'abstract_factories' => array(
            'Application\ServiceManager\AbstractFactory\TableAbstractFactory',
        ),
    ),
}

In the associative array returned from the getServiceConfig method, we just add the namespaced path to the new class we’ve just created. I’ve been rather explicit about mine for the sakes of clarity. That’s it.

Save Module.php and now if you when your tables are attempted to be retrieved from the ServiceManager, if they’re not explicitly defined, the new abstract factory class will attempt to instantiate them for you.

This will result in a more cacheable configuration, less development and maintenance effort.

Wrapping Up

So, have you tried out abstract factories yet? Are you tempted to do so now? Would you do this differently? Let me know in the comments.

ServiceManager
  • Baghayi

    Actually we don’t need to implement ServiceLocatorInterface in abstract factory classes and in fact we are only implementing AbstractFactoryInterface in it and that’s it.
    In fact we are using ServiceLocatorInterface ‘s object in there (inside methods we have to implement for abstract factories) and ServiceLocatorInterface is ONLY used there as one of parameter types and not any more than that.

    Another things which bugs me is the fact that you are checking to see if you can create services in createServiceWithName method which I believe is wrong, canCreateServiceWithName is specifically for that matter.

    • http://www.matthewsetter.com/ Matthew Setter

      Hi baghayi,

      I think I was a bit lax with my terminology. I should have said something like “made use of the ServiceLocator interface” instead of implementing it and left implementing strictly to the AbstractFactoryInterface. If that was confusing, I do apologise. But the statements there to keep the code more concise and easier to read.

      Perhaps the checking should have all been done in canCreateServiceWithName, but as this is a simple example, explaining why things were done as they were, as opposed to implicitly stating that that’s what should be done, I don’t see the harm in how it was approached.

      Thanks for sharing your feedback.