The EventManager and Bootstrap classes
We will be showing off one of the most beautiful features of Zend Framework 2: The EventManager.
How to do it…
The EventManager
and Bootstrap
classes are an essential part of our application, this recipe is all about how to use those two tools:
Using the bootstrap
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.
Starting a session
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'); } }
Using the EventManager class
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
).
Changing the View output
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.
How it works…
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.
The Observer pattern explained
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.
Aspect-Oriented Programming (AOP) explained
If we want to explain what AOP is, we could say that in short it stands for writing clean code that have only function and are as isolated from the rest of the code as possible.
Event-driven architecture explained
The benefit of an Event-driven architecture is that instead of creating bulks of code that need to check every condition, we can easily hook ourselves to different events, which in essence will create a more responsive application.
There's more…
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.