21 July 2017 —
I think it is doubtless that modern PHP embraces SOLID principles, and therefore, dependency injection.
That’s why every modern PHP application needs a dependency injection container to deal with it.
There are several options out there, depending on the way you like to work. Every container has a slightly different approach.
My choice is zend-servicemanager, it is the one that better suits me.
Other containers rely on auto wiring and auto discovery in order to know which dependencies need to be injected on every service, and I think that leads to errors when an application grows.
I like zend-servicemanager because it is explicit, and you are always in control of what’s done, without loosing flexibility in the process.
However, I have to recognize that it comes with a prize.
Factories everywhere
Since this container expects you to define factories for every service, you usually end up writing, testing and maintaining a lot of factories that doesn’t add value to the application.
That’s why it is so important to properly reuse factories when possible, not only because you will have to maintain less classes, but because the ServiceManager will instantiate less objects at runtime when it can reuse a factory.
This article is born because of the question of a reader of this blog, which asked me what did I mean with the “redundancy mitigated by reusing same factory” sentence, in one of my Zend Expressive articles.
I created a gist with some of the approaches to reuse factories, but I think it deserves a blog post, so here it is.
Shared dependencies
The simplest situation to reuse a factory is when you have more than one service with the same dependencies.
This situation can be solved with an abstract factory, but you can also use a concrete factory, which is more efficient.
Let’s imagine you are using the zend-db package to deal with persistence, and you have created one TableGateway
class for every table in your database.
All of the TableGateways
depend on a Zend\DB\Adapter\AdapterInterface
to be injected on them, so creating a different factory for every one of them would be a waste of time.
Instead, you just need to create a factory like this:
Then, register your table gateways using the FQCN:
If you prefer using an abstract factory, just do this:
Using ConfigAbstractFactory
The zend-servicemanager v3.2 introduced a built-in factory that can inject dependencies on services based on configuration.
This is probably one of the better ways to reuse factories (indeed, you won’t have to write any factory, since this one is included in the package).
In 80% of the cases (if not more), 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.
When using this factory, you just need to define a configuration block where you define the service names on which every other service depends.
For example, if we have this code base:
We just need to define a configuration like this:
Then, the ConfigAbstractFactory
will look for all the services on which requested service depends, and inject them into it.
Also, the package includes a binary that can be used to generate the ConfigAbstractFactory
config for a service, so you won’t even need to write that.
This factory can be registered as an abstract factory too (indeed, it is an abstract factory), but as mentioned above, it is less efficient, and I prefer this approach.
You have an in-detail documentation of this factory here: https://docs.zendframework.com/zend-servicemanager/config-abstract-factory/
Using ReflectionBasedAbstractFactory
This factory works similarly to the previous one, but instead of discovering the dependencies to inject based on a configuration array, it uses reflection on requested service.
This has the small advantage that you don’t have to specify the dependencies for every service. However, reflection is very inefficient, so this factory is not suited for production, but only for prototyping purposes.
Also, you need to register services with the same type used in the requested service constructor, so you can’t type hint to an interface and then inject a service which is registered with an implementation name, or a string which is not even a class name.
Apart from that, registration is the same as in previous factories:
The extended documentation for this factory can be found here: https://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 the acelaya/zsm-annotated-services package, which provides one factory that can discover the list of dependencies of a service based on an @Inject
annotation in its constructor, instead of using a configuration array.
The first example could be redeclared like this:
And then, you just need to map the services to the AnnotatedFactory
(select the right one, depending on the ServiceManager version)
The only drawback of this factory, is that you need to define a cache adapter in production, because processing annotations is very slow (it uses reflection too).
Here you have the complete documentation: https://github.com/acelaya/zsm-annotated-services.
However, if I were you, I would choose the ConfigAbstractFactory
over this one, because using annotations couples the configuration with the service.
Conclusion
The problem of defining several factories is usually the main argument I hear against using this component, but as you can see, there are several approaches to deal with it, without loosing control over your code base or depending on black magic.
There are probably other options that I’ve missed here, so if you know any other approach, just comment this post and I will gladly add it.