We will be showing off one of the most beautiful features of Zend Framework 2: The EventManager.
The EventManager
and Bootstrap
classes are an essential part of our application, this recipe is all about how to use those two tools:
The bootstrap is in our case the start of a module, whenever a module is requested it will use the onBootstrap()
method located in the Module.php
file. Although the method is not required, we usually want this method in our module as it is an easy method of making sure that some instances already exist or are configured before venturing further in our client request.
Sessions are a wonderful way of saving information about a user on a temporary basis. Think about saving the information of a logged-in user, or history on the pages they have been. Once we begin creating an application we find ourselves saving a lot of things in the session.
The first thing we need to do is modify the /module/Application/config/module.config.php
file, and add another section called session
to it. Let's assume that we have a completely empty module configuration:
<?php return array( 'service_manager' => array( // These are the factories needed by the Service // Locator to load in the session manager 'factories' => array( 'Zend\Session\Config\ConfigInterface' => 'Zend\Session\Service\SessionConfigFactory', 'Zend\Session\Storage\StorageInterface' => 'Zend\Session\Service\SessionStorageFactory', 'Zend\Session\ManagerInterface' => 'Zend\Session\Service\SessionManagerFactory', ), 'abstract_factories' => array( 'Zend\Session\Service\ContainerAbstractFactory', ), ), 'session_config' => array( // How long can the session be idle for in seconds // before it is being invalidated 'remember_me_seconds' => 3600, // What is the name of the session (can be anything) 'name' => 'some_name', ), // What kind of session storage do we want to use, // only SessionArrayStorage is available at the minute 'session_storage' => array( 'type' => 'SessionArrayStorage', 'options' => array(), ), // These are session containers we can use to store // our information in 'session_containers' => array( 'ContainerOne', 'ContainerTwo', ), );
And that is it. Sessions are now useable in our controllers and models. We have now created two session containers that we can use to store our information in. We can access these containers in any Controller or Model that has a service locator available by doing the following (file: /module/Application/src/Application/Controller/IndexController.php
):
<?php namespace Application; use Zend\Mvc\Controller\AbstractActionController; class IndexController extends AbstractController { public function indexAction() { // Every session container we define receives a // SessionContainer\ prefix before the name $containerOne = $this->getServiceLocator() ->get('SessionContainer\ContainerOne'); } }
The EventManager
class is possibly one of the nicest features in the framework. When used properly, it can make our code a lot more dynamic and maintainable without creating spaghetti code.
What it does is relatively simple, for example; a class might have a method called MethodA
. This MethodA
has a list of listeners, which are interested in the outcome of that class. When MethodA
executes, it just runs through its normal procedures, and when finished it just notifies the EventManager
a specific event has occurred. Now the EventManager
will trigger all of the interested parties that this event has taken place, and the parties in their turn will execute their code.
Got it? Don't worry if you don't, because this example code might clear things up (file: /module/Application/src/Application/Model/SwagMachine.php
):
<?php // Don't forget to add the namespace namespace Application\Model; // We shouldn't forget to add these! use Zend\EventManager\EventManager; class SwagMachine { // This will hold our EventManager private $em; public function getEventManager() { // If there is no EventManager, make one! if (!$this->em) { $this->em = new EventManager(__CLASS__); } // Return the EventManager. return $this->em; } public function findSwag($id) { // Trigger our findSwag.begin event // and push our $id variable with it. $response = $this->getEventManager() ->trigger( 'findSwag.begin', $this, array( 'id' => $id ) ); // Make our last response, the final // ID if there is a response. if ($response->count() > 0) $id = $response->last(); // ******************************** // In the meantime important code // is happening... // ******************************** // ...And that ends up with the // folowing return value: $returnValue = 'Original Value ('. $id. ')'; // Now let's trigger our last // event called findSwag.end and // give the returnValue as a // parameter. $this->getEventManager() ->trigger( 'findSwag.end', $this, array( 'returnValue' => $returnValue ) ); // Now return our value. return $returnValue; } }
As we can see we created a little class with two event triggers, findSwag.begin
and findSwag.end
, respectively on the beginning of the method, and one on the end of the method. The findSwag.begin
event will potentially modify the $id
, and the findSwag.end
event only parses the returnValue
object, with no modification possible to the value.
Now let's see the code that implements the triggers (file: /module/Application/src/Application/Controller/IndexController.php
):
<?php namespace Application\Controller; use Zend\Mvc\Controller\AbstractActionController; class IndexController extends AbstractActionController { public function indexAction() { // Get our SwagMachine $machine = new SwagMachine(); // Let's attach our first callback, // which potentially will increase // the $id with 10, which would // make it result in 30! $machine->getEventManager() ->attach( 'findSwag.begin', function(Event $e) { // Get the ID from our findSwag() // method, and add it up with 12. return $e->getParam('id') + 10; }, 200 ); // Now attach our second callback, // which potentially will increase // the value of $id to 60! We give // this a *higher* priority then // the previous attached event // trigger. $machine->getEventManager() ->attach( 'findSwag.begin', function(Event $e) { // Get the ID from our findSwag() // method, and add it up with 15. return $e->getParam('id') + 40; }, 100 ); // Now create a trigger callback // for the end event called findSwag.end, // which has no specific priority, // and will just output to the screen. $machine->getEventManager() ->attach( 'findSwag.end', function(Event $e) { echo 'We are returning: ' . $e->getParam('returnValue'); } ); // Now after defining the triggers, // simply try and find our 'Swag'. echo $machine->findSwag(20); } }
As we can see attaching triggers to events is pretty straightforward. And – if the events are properly documented – can come in handy when we want to, say, modify parameters going into a method (like we did with the findSwag.begin
), or just outputting the results to a log (like findSwag.end
).
When we look at what is on our screen, it should be something like this:
We are returning: Original Value (60) Original Value (60)
The result consists of the top line being the output from the findSwag.end
trigger, while the value 60
comes from the highest priority trigger, the one with priority 100
(as that is considered a higher priority than 200
).
Sometimes it is necessary that we have different View outputs, for example when we need to build ourselves a REST service or a SOAP service. Although this can be arranged much simpler by a controller plugin, it is an example on how to hook into the dispatch
event, and see what is going on there.
Without further ado, let us take a look at the following code snippet:
Module.php: namespace Application; // We are going to use events, and because we use a MVC, // we need to use the MvcEvent. use Zend\Mvc\MvcEvent; class Module { public function onBootstrap(MvcEvent $e) { // Get our SharedEventManager from the MvcEvent $e // that we got from the method $sharedEvents = $e->getApplication() ->getEventManager() ->getSharedManager(); // Also retrieve the ServiceManager of the // application. $sm = $e->getApplication()->getServiceManager(); // Let's propose a new ViewStrategy to our // EventManager. $sharedEvents->attach( // We are attaching the event to this namespace // only. __NAMESPACE__, // We want to attach to this very specific // event, the Dispatch event of our controller. MvcEvent::EVENT_DISPATCH, // The callback function of the event, used when // the event we attached to happens. In our // callback we also want our local variable $sm // to be available for use. function($e) use ($sm) { // Get our alternate view strategy from the // ServiceManager and attach the EventManager // to the strategy. $strategy = $sm->get('ViewJsonStrategy'); $view = $sm->get('ViewManager')->getView(); $strategy->attach($view->getEventManager()); }, // We want to give this a priority, so this will // get more priority. 100 ); }
As we can see it is relatively simple to attach a callback function to the EventManager
object. In this example we are using McvEvent::EVENT_DISPATCH
as the event we want to hook in to. So what basically happens is that whenever a controller executes the onDispatch()
method, this event will be triggered as well. This means that through events we can modify the outcome of a method without actually needing to modify the code.
The EventManager class works through a couple of different methods, namely the Observer pattern, the Aspect-Oriented Programming technique (or AOP) and the Event-Driven architecture.
Simply said the Observer pattern means that there are several interested parties, called listeners that want to know when the application triggers a certain event. When a specific event is triggered, the listeners will be notified so that they can take their necessary actions.
The EventManager
object is queried through a PriorityQueue
, which tells us that an important event will generally get a lower value, while an unimportant event a higher value. For example, the highest priority might get priority -1000
while a quite low priority might get 40. The EventManager
class then gets the queue through a FIFO (First In, First Out) concept, meaning the higher the priority, the lower the number.