31 December 2015 —
I have spoken many times at this blog about dependency injection, and how the ZF2 ServiceManager is one of the best tools to solve it. You can read these related articles to know more:
- How to properly use the Zend framework 2 service manager as a dependency injection container
- Advanced usage of ServiceManager in Zend Framework 2
- Managing objects creation and dependency injection with Zend\ServiceManager
In this article I’m going to speak about another utility that comes with the Zend\ServiceManager
package, the AbstractPluginManager
.
Injecting a service container
Generally it is a very bad practice to inject a service container into any object, but there are some situations where it could be even good, when certain conditions are met.
In one of the ZF2 mailing lists somebody asked which were these situations. I couldn’t find the email, but the answers said that you can do it when the service container manages resources of the same type, and your object virtually depends on all of them.
For example, imagine you have a database connections pool. It is responsible of creating connection objects, and knowing which one should be returned.
If you have another object that needs to perform database connections, you don’t want to inject all of the connection objects into it, you should rather inject the connection pool. That will reduce the number of dependencies of your object.
In this situation, the connection pool is some kind of service container, but injecting it has more benefits than disadvantages.
The AbstractPluginManager implementation
Once we know the theory, we can see how to implement it.
The AbstractPluginManager is a class that extends the ServiceManager, but includes an abstract method validatePlugin
that implementors should use to validate objects when they are created. It just needs to throw an exception if it is not valid, for example because it doesn’t implement certain interface.
This way we can be sure that all the services managed by it can be used for a specific task. In the connection pool example, all the managed objects should be capable of connecting to a database.
A real example
Imagine that we have a service that needs to connect to different social networks in order to generate social login URLs and get user information from them. That service abstracts the concrete implementation for each social network, and to do it, it gets concrete social connectors injected.
It could look like this:
The service methods call to the proper connector based on the social network we specify. Apparently, it’s a simple implementation.
But there is a problem. If the number of social networks we support grows too much, at some point this service is going to have too many dependencies, and that’s a code smell. Also, having a lot of if
statements per method is not very clean either.
The solution would be to create a SocialPluginManager
, an object that extends Zend\ServiceManager\AbstractPluginManager
and manages and creates each social connector transparently. Then, we just need to make the SocialUsers
service to depend on the SocialPluginManager
.
Each social connector should implement an interface like this.
Then the SocialPluginManager
will just need to check if any created object implements it.
We now need to refactor the SocialUsers
service so that it depends on the SocialPluginManager
.
The resulting code is much cleaner, and now we can add any new social network without having to change the SocialUsers
service. We just need to create and register the new social connector.
The last thing we have to do is defining the SocialPluginManager
configuration. Since the AbstractPluginManager
extends the ServiceManager
, it is exactly the same as the one used for it. To make the previous code work, we have to use the social network name as the service name for each connector, so the configuration could look like this.
I have used factories as an example, but you can use any valid strategy you want.
You can see a small example project here. The concrete social connector doesn’t do nothing, but you can see how they work.
The plugin managers are widely used in Zend\Mvc
for tasks like this. The controller plugins, view helpers, form elements and such can all be managed by its own plugin managers.