Skip to content

Instantly share code, notes, and snippets.

@acelaya
Created July 21, 2017 14:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save acelaya/41cf7457c2dbc434c4bb919a21e002da to your computer and use it in GitHub Desktop.
Save acelaya/41cf7457c2dbc434c4bb919a21e002da to your computer and use it in GitHub Desktop.
Reusing factories in zend-servicemanager

There are several situations in which it is possible to reuse the same factory to create an object.

Same dependencies

The simplest one is when the objects share the same dependencies. For example, imagine your app uses one TableGateway to manage every DB table. Each one of them will need a database adapter to be injected on it.

Instead of creating one factory for every TableGateway, you can create just one with this content

class TableGatewayFactory
{
    public function __invoke(ContainerInterface $container, $requestedName)
    {
        $adapter = $container->get(AdapterInterface::class);
        return new $requestedName($adapter);
    }
}

Then, when registering the table gateways you just need to use their FQCN

return [
    'factories' => [
        UserTableGateway::class => TableGatewayFactory::class,
        ArticleTableGateway::class => TableGatewayFactory::class,
        FooTableGateway::class => TableGatewayFactory::class,
        // ...
    ],
];

If you are even lazier, you can use an abstract factory instead, then you don't need to register every table gateway.

class TableGatewayAbstractFactory implements AbstractFactoryInterface
{
    public function canCreate(ContainerInterface $container, $requestedName)
    {
        return is_subclass_of($requestedName, TableGatewayInterface::class);
    }

    public function __invoke(ContainerInterface $container, $requestedName)
    {
        $adapter = $container->get(AdapterInterface::class);
        return new $requestedName(adapter);
    }
}
return [
    'abstract_factories' => [
        TableGatewayAbstractFactory::class,
    ],
];

However, I would recommend the first approach, because is more efficient

Using ConfigAbstractFactory

Version 3.2 introduced a built-in factory that can inject dependencies on services based on configuration.

80% of the cases, a factory basically consists on grabbing some dependencies from the container, and creating a new instance of an object where those dependencies are injected. In those cases the ConfigAbstractFactory is perfect.

return [
    'dependencies' => [
        'factories' => [
            FooService::class => ConfigAbstractFactory::class,
            BarService::class => ConfigAbstractFactory::class,
            BazService::class => ConfigAbstractFactory::class,
            OtherService::class => InvokableFactory::class,
            // ...
        ],
    ],
    
    ConfigAbstractFactory::class => [
        BarService::class => [BazService::class],
        FooService::class => [BarService::class, OtherService::class],
    ],
];

The official documentation perfectly explains how to use this in detail: http://zendframework.github.io/zend-servicemanager/config-abstract-factory/

Using ReflactionBasedAbstractFactory

Similar to the previous one, but it uses reflection and you don't need to manually map the dependencies of every service.

It is only meant for prototyping, because it is veeeery inefficient, so don't use it in production.

http://zendframework.github.io/zend-servicemanager/reflection-abstract-factory/

Using acelaya/zsm-annotated-services package

Some time ago (when the ConfigAbstractFactory didn't exist yet), I created a package that provieds one factory that can discover the list of dependencies based on an @Inject annotation in the service constructor.

https://github.com/acelaya/zsm-annotated-services

However, I would tell you to use the ConfigAbstractFactory instead, because annotations couple configuration with the service.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment