jump to navigation

Using ZF2’s Event Manager Component as Event Driven Programming Example March 27, 2014

Posted by Tournas Dimitrios in Zend Framework 2.
Tags: ,
trackback

Zend Framework 2 logoA fundamental principle of Java , .NET platform and other  popular programming languages is based on Event-driven Architecture (EDA). Simply put, the idea is that activities occur in response to events . In other words, activities are attached to events, and when a given event occurs, its attached activity[ies] is[are] executed.This programming paradigm is named  “event-driven programming”  and is nowadays adopted by almost all programming languages (client / server side) . Although event-driven programming was adopted by each programming language for a completely different purpose, the ultimate goal was to write applications that are flexible, scalable and “plug-able” at run-time . Of course , each language has its own implementation, but at the end , all of them were envisioned by the same concepts .

Event-driven programs can be written in any language, although the task is easier in languages that provide high-level abstractions, such as Closures .Back in December 2010 , PHP 5.3 was released . Three of the most notable new features of that release were : support for namespacesLate static binding and Lambda Functions / Closures . At that time , many PHP Frameworks started to flirt with event-driven architecture which leaded to a permanent relation . Laravel-4 , Symfony-2 and Zend Framework-2 have now event driven functionality incorporated into their core . But that’s just history, lets bypass this introductory chatter and go straight to the point .

Prerequisites : The reader should have a good grasp of OOP concepts . Also knowing to use Composer’s basic functionality is helpful , as it will be used to offload basic tasks like : downloading the required component and using its auto-load functionality .
Composer file used for this article :

{
	"repositories": [
		{
		    "type": "composer",
		    "url": "https://packages.zendframework.com/"
		}
	],
	"require": {
"zendframework/zend-eventmanager": "2.*",

	}

}

 

This article will demonstrate a practical example of using “events” into a standalone code base . For no specific reason , Zend Framework 2’s “Event Manager” component was used to off-load the implementation of an “Observer Design pattern” (we don’t forget the DRY principle) .We could had used Laravel’s , Symfony’s or similar components provided from other component-based PHP Frameworks .

 

Terminology first : The way how events are raised and how listeners are attached on the events is a part of a core in many modern Frameworks. It plays an important role in some enterprise design patterns (MVC, for example) . The basic event terminology includes the following terms:

  • EventManager — is just an object that holds collection of listeners for one or more named events and which trigger events.
  • Event — is a named action .
  • Listener — is a php callback (Closure) that can react to an event
  • Target — is an object that creates events

hooray picturehooray — hooray  , boring introduction is ended . Let’s have fun ……..
A two thousand meter view : Three necessary steps to start using events are :

  • Step1: An EventManager Object (or a derivative) . Our example below will use an abstract “EventProvider” Class which extends ZF’s EventManager .
  • Step2: Registering (attaching) one or more listeners on one or more events .
  • Step3: Calling an event (triggering an event) . All attached listeners will take actions . For illustration and simplicity our examples below use closures as listeners . However, any valid PHP callback can be attached as a listeners : PHP function names, static class methods, object instance methods, functions, or closures.

The following example was copied from ZF’s documentation :

use Zend\EventManager\EventManager;

$events = new EventManager();
$events->attach('do', function ($e) {
    $event = $e->getName();
    $params = $e->getParams();
    printf(
        'Handled event "%s", with parameters %s',
        $event,
        json_encode($params)
    );
});

$params = array('foo' => 'bar', 'baz' => 'bat');
$events->trigger('do', null, $params);


Implementation of EventManager in a standalone code-base :

require 'vendor/autoload.php' ; 

abstract class EventProvider implements Zend\EventManager\EventManagerAwareInterface
{
    protected $eventManager ;

    public function setEventManager(Zend\EventManager\EventManagerInterface $eventManager)
    {
        $this->eventManager = $eventManager ;
    }

    public function getEventManager()
    {
     	if (!$this->eventManager instanceof Zend\EventManager\EventManagerInterface ) {
   		$this->setEventManager(new Zend\EventManager\EventManager(array(__CLASS__, get_called_class())));
        }
        return $this->eventManager ;
    }

	protected function trigger($function , $params = array())
	{
 		$events = $this->getEventManager()->getEvents()  ;
		$class = get_called_class() ; 

		if (in_array($class.':'.$function , $events)) {

			$this->getEventManager()->trigger($class.':'.$function , $this , $params ) ; 

			} elseif (in_array( $class.':*' , $events)){

			$this->getEventManager()->trigger($class.':*' , $this , $params ) ;
		}
	 }
 }

class Example  extends EventProvider
{

    public function doSomething($foo, $baz)
	{

		$params = compact('foo' , 'baz') ;
		$this->trigger(__FUNCTION__ , $params) ;  

	}

	public function doagain()
	{

		$params = array('one'=>'foo' , 'two' => 'baz') ;
		$this->trigger(__FUNCTION__ , $params) ; 

	}

	public function dolast($error)
	{

		if ($error) {
		$this->trigger(__FUNCTION__) ; 

		}
	}
}

Explanation :

  • Including Composer’s auto-load function , it will lazy-load all the necessary Classes and Interfaces of ZF2’s component
  • An abstract Class (EventProvider) implements a “EventManagerAwareInterface” Interface . In simple words , this Interface is a “contract” that our Class will compose an EventManager within it , to allow triggering actions within methods .The contract ensures an implementation of three functions (setEventManager , getEventManager and trigger) .  Every Class that extend our “EventProvider” , will have an full featured Event-Manager to its disposal .
  • An “Example” Class is extending EventProvider’s functionality . Any  method of this Class can trigger events . But before triggering events , our EventManager should know what events to trigger . The following code shows how to register events into EventManager and how to attach listeners on those events .

 

$listener1 = function(Zend\EventManager\Event $e) {

 // code to send email-notificationis omitted
 echo 'email issended to administrator' ;
 } ;
$listener2 = function(Zend\EventManager\Event $e) {
 $event = $e->getName();
 $target = get_class($e->getTarget()); // \"Example\"
 $params = $e->getParams();
 printf(
 'Handled event \"%s\" on target--- \"%s\", withparameters %s',
 $event,
 $target,
 json_encode($params)
 );
 }; 

$example = new Example() ;
$example->getEventManager()->attach('Example:*' , $listener2) ;
$example->getEventManager()->attach( 'Example:dolast' , $listener1);
$example->getEventManager()->attach( 'Example:dolast' , $listener2);
$example->getEventManager()->attach( array('Example:doSomething', 'Example:doagain') , $listener2 );

echo '</pre>
<pre>' ;
$example->dosomething('bar', 'bat');
echo '
' ;
$example->doagain() ;
echo '
' ;
$example->dolast(true) ;
echo '
' ;
$example->dolast(false) ; // Event will not be triggered
echo '
' ;
//$example->getEventManager()->clearListeners('Example:dolast') ;
$example->dolast(true) ;//Event will not be triggered as it was already detached
print_r($example->getEventManager()->getEvents()) ;</pre>
<pre>

Explanation : 

  • $listener1 and $listener2 are actions taken when a specific event is triggered ,we use closures as listeners (for simplicity reasons) .
  • Instantiating “Example” Class
  • Registering events to specific listeners (actions) . What actually is done here : an “EventManager” Class is retrieved out of  “Example” Class . On the “EventManager” Class events are attached to actions (listeners) .
  • Finally , “Example’s” methods are called . Methods can trigger events and “EventManager” matches events to specified actions (closures ) .

An experienced eye should have discovered two aspects into our implementation :

  1. While attaching events into EventManager , each event-name was prefixed with a Class name . This is done for identification reasons . Down the road , that prefix indicates us from what Class that event was triggered .
  2. “EventProvider” Class is assigned the duty to prefix each triggered event with the name of the Class it was called . Giving Classes unnecessary duties is a bad practice , our EventProvider Class should only be responsible for assigning , attaching and dispatching events . Identification task should be offloaded to other Classes . Fortunately , EventManager component provides a “SharedEventManager”  Class for this functionality (see next paragraph) .

Summing up : Any Class that wants to inherit EventManager’s functionality (registering events and triggering actions ) should extend “EventProvider” . Implementation of actions can be done via static class methods, object instance methods, functions, or Closures . Registering events to actions is done via EventManager’s “attach” method . Zend Framework 2’s EventManager component has a full suit of supplementary functionality : Clearlisteners (detaching events) , priority (if multiple actions are attached to one event) to name a few . It’s highly recommended to dig into EventManager’s code and discover more hidden gems of this Class .

Digging  into ZF2’s  SharedEventManager Class :
SharedEventManager is an object that aggregates listeners for events attached to objects with specific identifiers. It does not trigger events itself. Instead, an EventManager instance that composes a SharedEventManager will query the SharedEventManager for listeners on identifiers it’s interested in, and trigger those listeners as well. Let’s re-factor our previous code , this time , our EventProvider shall offload the identification task into an external object (SharedEventManager) . If Zend Framework is used as a whole package , SharedVentManager Class serves an important role, it ensures that registered events are accessible (shared) from all modules. For those readers that are new to ZF2 : imagine a module as an autonomous block of code . From event-system perspective, each ZF’s module is an isolated unit .

The following code demonstrates how SharedEventmanager takes care to prefix each event with a “tag” (specified by us) . Our “EventProvider” Class in not anymore (and shouldn’t be) responsible to do this task .

require 'vendor/autoload.php' ; 

abstract class EventProvider implements Zend\EventManager\EventManagerAwareInterface
{
    protected $eventManager ;

    public function setEventManager(Zend\EventManager\EventManagerInterface $eventManager)
    {
		$eventManager->setIdentifiers(array(__CLASS__ , get_class($this)));
        $this->eventManager = $eventManager ;
    }

    public function getEventManager()
    {
     	if (!$this->eventManager instanceof Zend\EventManager\EventManagerInterface ) {
   		$this->setEventManager(new Zend\EventManager\EventManager(array(__CLASS__, get_called_class())));
        }
        return $this->eventManager ;
    }

	protected function trigger($function , $params = array())
	{
		$this->getEventManager()->trigger($function , $this , $params);
	}
}// End of Abstract Class EventProvider 

class Example  extends EventProvider
{

    public function doSomething($foo, $baz)
	{

		$params = compact('foo' , 'baz') ;
		$this->trigger(__FUNCTION__ , $params) ;  

	}

	public function doagain()
	{

		$params = array('one'=>'foo' , 'two' => 'baz') ;
		$this->trigger(__FUNCTION__ , $params) ; 

	}

	public function dolast($error)
	{

		if ($error) {
		$this->trigger(__FUNCTION__) ; 

		}
	}
}// End of Class Example 

class SubExample extends Example {}

$listener1 = function(Zend\EventManager\Event $e) {

		// code to send email-notification is omitted
		echo 'email is sended to administrator
' ;
	} ;
$listener2 = function (Zend\EventManager\Event $e) {
			$event  = $e->getName();
			$target = get_class($e->getTarget()); // "Example"
			$params = $e->getParams();
			printf(
				'Handled event "%s" on target "%s", with parameters %s', $event,
				 $target , json_encode($params)) ;
			};

$sharedEventManager = new Zend\EventManager\SharedEventManager();
$sharedEventManager->attach(array('Example', 'SubExample'), 'doSomething', $listener1 );
$sharedEventManager->attach('SubExample', 'doagain', $listener2 );

$example = new Example();
$example->getEventManager()->setSharedManager($sharedEventManager);
$example->doSomething('bar', 'bat');
//$example->doagain();
$subExample = new SubExample() ;
$subExample->doSomething('bar', 'bat');
$subExample->doagain() ;

.
Links :

 

 

Comments»

1. devEric69 - October 22, 2015

Thanks for this detailed example of the EventManager in a “standalone component mode”: I’ve use it in an application that was absolutely not designed around the ZF2’s dispatch loop with the central MvcEvent class.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s