Make Module Configs Cacheable with the ZF2 Factory Interface

Zend Framework 2

For the longest time, I’ve been using closures in my Zend Framework 2 Modules Module class. I know they’re not always the best approach, but they’re not necessarily wrong either.

But after reviewing Gary Hockin’s recent talk at PHP Conference UK, I was reminded that outside of APC and OPCache, closures aren’t cacheable.

Depending on your application, this may not be a problem. But as most projects, invariably, grow over time, it’s likely they’ll inevitably become a performance bottleneck.

Gladly, he reminded me that there are alternative approaches which do allow for caching with Memcached, Redis and so on; specifically – if your configuration is based on a class which implements Zend\ServiceManager\FactoryInterface.

So in today’s tutorial, I’m going to show you a simple example of how to migrate from closures using this approach.

Standard Closure Approach

Ok, firstly here’s what a sample configuration in an app I’m making looks like:

class Module
{
    public function getServiceConfig()
    {
	return array(
	    'factories' => array(
                'Form\Feed\DeleteForm' => function ($sm) {
                    $form = new DeleteFeedRecordForm();
                    $form->setValidationGroup(
			array('feedId')
		    );
		    $form->setInputFilter(
			new DeleteFeedRecordInputFilter()
		    );
	            return $form;
            	},
	    )
    	);
    }
}

You can see that I’ve setup a service manager service entry, Form\Feed\DeleteForm, by using a simple closure, instantiating the form, specifying a validation group and input filter, then returning the instantiated form.

For the purposes of today’s tutorial, this will work just nicely. Let’s see how we could migrate it.

Using The Factory Interface

To implement the FactoryInterface approach, we’ll need to create a class which implements Zend\ServiceManager\FactoryInterface. Below is my class. Have a look, then let’s go over it.

<?php
namespace BabyMonitor\ServiceManager\Form\Feed;

use Zend\ServiceManager\FactoryInterface,
    Zend\ServiceManager\ServiceLocatorInterface,
    BabyMonitor\Form\DeleteFeedRecordForm as DeleteForm,
    BabyMonitor\InputFilter\DeleteFeedRecordInputFilter as DeleteFilter;

class DeleteFormFactory implements FactoryInterface
{
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
    	$form = new DeleteForm();
        $form->setValidationGroup(array('feedId'))
             ->setInputFilter(new DeleteFilter());
        return $form;
    }
}

As it’s configuring a service for the ServiceManager, providing a Form, managing deletion of feeds, BabyMonitor\ServiceManager\Form\Feed\DeleteFormFactory, the class namespace is semi-intuitive.

The factory interface only defines one required function, createService. I’ve included the use statements for the required classes and largely copied the code, verbatim, from the closure. Nice and simple.

Note: I’ve aliased the classes only for the purposes of readability and formatting. No real requirement otherwise.

Updating Module.php

With the class defined, I can now update getServiceConfig() by replacing our existing configuration with the one below:

'Form\Feed\DeleteForm' => 'BabyMonitor\ServiceManager\Form\Feed\DeleteFormFactory',

That’s it! Just one class (per/closure), and update the Module configuration. Assuming this was the only only configuration needing migration, we could then enable caching, as Akrabat explains.

Winding Up

Admittedly these were trivial examples, and depending on your configurations, you may need to go to a bit of effort to migrate your closures. But I hope you see that the migration’s quite straight-forward to do.

Intermediate Modules Tutorial
  • Witold Wasiczko

    It’s very imortant if you create public modules. I’m waiting for more pro posts :-)

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

      Witold, very true. If they’re available to be downloaded and used in all kinds of environments, I agree. What topics interest you most? Anything you’re having trouble with, specifically, or just want more information on?

  • Emmanuel Sayson

    Hmm how can I pass a value to the factory that implements FactoryInterface.
    Say,

    class PageServiceFactory implements FactoryInterface
    {
    protected $page_group;
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
    switch ($this->page_group) {
    case ’1′ : new Class1(); break;
    case ’2′ : new Class2(); break;
    }
    }
    }

    page_group would come from db, any advice how can I correctly pass it to PageServiceFactory before calling it?

  • Emmanuel Sayson

    Hi How can you pass an object to the class that implements FactoryInterface.
    The only object accepted by createService is the serviceLocator instance.