ZF2 Modules, Services, and Events
In this post, we are going to explore a few design patterns and architectures that reach into the heart of any ZF2 based application: The ServiceManager, the EventManager, and the Module architecture.
When it comes to building an application with ZF2, your application will be constructed with one or more modules, each of them providing services for other modules to consume. Likewise, these modules can trigger events which allow additional modules to react to things happening within the application at large.
To get started, we will begin with an introduction to the ServiceManager in ZF2.
The Service Manager
The ServiceManager component is a core feature of the ZF2 framework, responsible for providing access to everything from configuration values to database adapters within a framework application.
The idea behind the ServiceManager is a great one: provide a consistent mechanism that allows you to access complex functionality throughout your application without creating unnecessary dependencies.
For example, consider a user model which interacts with the presentation sharing site SlideShare through the ZendService\SlideShare
component for ZF2. One potential implementation would be to simply create an instance of your SlideShare API and then use it from within your controller. This is less than ideal, as now your controller is dependent on the SlideShare component as well as your User model. Moreover, unnecessary code must exist to retrieve the login credentials for the SDK, and so on.
Alternatively, you could move the SlideShare component into the User model itself. This removes the dependency from the controller, but really only shifts the dependency from the controller to model, rather than eliminating it.
It is situations like this where the ServiceManager is the most useful. By using this design pattern, we abstract the initialization of composite services into factory mechanisms that allow us to consolidate our code as well as decouple the various components.
Let’s step through what that means.
Using ServiceManager in your ZF2 application
Internally, ZF2 makes use of the ServiceManager for many things already. From controllers to database adapters. Whether you know it or not, the ServiceManager has been helping you already.
To create your own services, we need to get a few things done in our modules:
- Create the service class itself
- Create a factory to initiate the service
- Register the factory with the framework
Creating a service is as simple or as complex a process as your application requires. In our previous example where we wanted to take advantage of the SlideShare API within our module, the work is basically already done for us since the API interface has already been written. So, we can move on to the second step, which is to create a factory that initiates the service.
ServiceManager factories can take many forms within ZF2, depending on the needs and complexity of your application. It their simplest form, they are simple closures that return an instance of the service. In more complex situations, they are full-blown objects implementing one or more interfaces such as FactoryInterface.
Below is an example of what a simple closure factory for producing a SlideShare object as a service might look like:
<?php
// ...
'service_manager' => [
'MyApp\SlideShare' => function($sm) {
$config = $sm->get('Config');
$slideShare = new SlideShare($config['slideshare']['appId'], $config['slideshare']['secret']);
return $slideShare;
}
]
// ...
?>
This closure is executed whenever the service is requested. The closure (like all factories) is then provided an instance of the ServiceManager, and uses that to retrieve the API credentials for the service and inject them into the object prior to returning it.
We can now access this service from anywhere ZF2 provides a ServiceLocator or ServiceManager, as shown in this controller snippet:
<?php
//... Controller code
$slideShare = $this->getServiceLocator()->get('MyApp\SlideShare');
// Do something interesting with the SL API
?>
Examining this code, note first that the terms ServiceLocator and ServiceManager are often used interchangeably, which is only partially accurate. The ServiceLocator in ZF2 is an interface that implements the has()
and get()
prototypes, while the ServiceManager is a concrete implementation of that Interface. Thus, while in ZF2 these are typically the same thing, it is not always the case.
Secondly, the MyApp\SlideShare
identifier used to represent our service. This, by convention, is represented like a namespaced object within PHP. But is not a requirement. In fact, any string may be used to represent your service.
This is a useful feature, as it allows the underlying implementation (and even the factory) to be arbitrarily defined. That is to say, other aspects of the application do not care how it works, only that the service as requested is retrieved.
Using Invokables as Factories
An simpler approach to the closure method is when the factory needs to do nothing else but return an instance of an object.
In that case, it can be listed as an invokable
as shown:
<?php
// ...
'invokables' => [
'MyApp\SomeObject' => 'Fully\Qualified\Namespaced\Class'
]
// ...
?>
When using an invokable, the class in question must have a constructor that requires no parameters in order to be eligible.
Using Dedicated Factory Classes for Services
For situations where the complexity of the factory is too extensive to do within a closure, it can be moved into its own factory class. Simply create a class that implements the ZF2 FactoryInterface
and reference it under the factories
key just as you did with the invokables
key:
<?php
// MyApp/Service/MyFactory.php
class MyFactory implements FactoryInterface
{
public function createService(\Zend\ServiceManager\ServiceLocatorInterface $serviceLocator) {
$config = $serviceLocator->get('Config');
$slideShare = new SlideShare($config['slideshare']['appId], $config['slideshare']['secret']);
return $slideShare;
}
}
Then, in your configuration:
<?php
// ...
'factories' => [
'MyApp\SlideShare' => '\MyApp\Service\MyFactory'
]
// ...
?>
More to Come!
Services are wonderful tools for your ZF toolbox, and when used correctly can greatly ease the burden of testing, maintaining, and refactoring your code. You certainly have been shown enough to get started with them, but know that you can get even more complex using abstract factories (that simply care about the interface returned) and even more.
That is it for today, but we have plenty more to discuss in future posts.
Now that you know how to create services, we can explore how to decouple communication between various components of the application using events, and finally we can pull all of it together into complete self-contained modules!
P.S. How are you using the ZF2 Service Architecture in your application? Any pitfalls we’ve missed you’d like to point out? We’d love to hear how your experiences have been.
Share your thoughts with @engineyard on Twitter