View Source

<ac:macro ac:name="info"><ac:parameter ac:name="title">Zend_Message</ac:parameter></ac:macro>
<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{zone-template-instance:ZFDEV:Zend Proposal Zone Template}
{composition-setup}
{zone-data:component-name}
Zend_Message
{zone-data}

{zone-data:proposer-list}
[~matthew]
[Alexander Veremyev (Zend Liaison)|~alexander]
{zone-data}

{zone-data:revision}
1.5 - 16 June 2008: Renamed, added usecase from comments
1.4 - 11 March 2008: updated class skeletons and text
1.3 - 20 Februari 2008: Updated link and added another usecase
1.2 - 3 January 2008: Added some use cases, operation description and updated skeletons
1.1 - 30 December 2007: Refactoring and renaming
1.0 - 29 December 2007: Setting up first draft
{zone-data}

{zone-data:overview}
Zend_Message provides an infrastructure to relay messages between objects that don't necessarily know eachother.
This provides a loose coupling between components and makes your app more flexible.

Also, this might be of interest for the Zend_Tool CLI tooling?

An implementation of this proposal is available in [Zym|http://www.zym-project.com/].
{zone-data}

{zone-data:references}
* [Zym_Notification|http://svn2.assembla.com/svn/zym/trunk/library/Zym/]
* [Zym_Notification docs|http://www.zym-project.com/docs/manual/en/zym.notification.html]
* [Cocoa NSNotification documentation|http://developer.apple.com/documentation/Cocoa/Conceptual/Notifications/Introduction/introNotifications.html]
{zone-data}

{zone-data:requirements}
* This component *must* provide a clean, non-intrusive way to handle event notification
* *Must not* require existing classes to implement interfaces or extend classes for it to work
{zone-data}

{zone-data:dependencies}
* Zend_Exception
{zone-data}

{zone-data:operation}
The operation is very simple, but also very powerful. You can attach an object to an event by calling the notification center's attach() method.
This method takes the observer and the name of the event as argument. More than one object can register to the same event.
When a message is sent, all objects that subscribed to that event will be notified in order of subscription.
By default the observer's notify() method will be called. You can also register another method when registering the observering object at the message dispatcher.

The message constructor takes three arguments (of which the last is optional). The first is the name of the message. The second is (usually) the object that sent the message.
The last argument is an array that allows for extra data to be sent along with the message.

When attaching an observer to an event you can either use a string to attach it to just one event, or provide an array with multiple event names. (See usecase UC-02)
When you attach an observer to an event with an asterisk in the name, the asterisk will be used as wildcard. See usecase UC-03 for an example.
{zone-data}

{zone-data:milestones}
* Milestone 1: \[DONE\] finish this proposal
* Milestone 2: \[DONE\] write documentation
* Milestone 3: \[DONE\] write unit tests
* Milestone 4: \[IN PROGRESS\] process comments
* Milestone 5: get approval and move the component to incubator
* Milestone 6: finish component and move it to core
{zone-data}

{zone-data:class-list}
* Zend_Message
* Zend_Message_Dispatcher
* Zend_Message_Registration
* Zend_Message_Exception
{zone-data}

{zone-data:use-cases}
{deck:id=Use Cases}
{card:label=UC-01: Basic usage}
{code}
class CoffeeShop_Customer
{
public function __construct()
{
$dispatcher = Zend_Message_Dispatcher::get();

$dispatcher->attach($this, CoffeeShop::PIE_READY) // Uses the default callback notify()
->attach($this, CoffeeShop::COFFEE_READY, 'drinkCoffee'); // Uses a custom callback
}

public function notify(Zend_Message $message)
{
$sender = $message->getSender();
$data = $message->getData();

$data['pie']->eat();

echo "Eating the pie...\n";

if ($sender instanceof CoffeeShop) {
$sender->recommendToFriends();
}
}

public function drinkCoffee(Zend_Message $message)
{
$data = $message->getData();

$data['coffee']->drink();

echo "Drinking the coffee...\n";
}
}

class CoffeeShop
{
const COFFEE_READY = 'coffeeReady';
const PIE_READY = 'pieReady';

protected $_dispatcher;

public function __construct()
{
$this->_dispatcher = Zend_Message_Dispatcher::get();
}

public function bakePie()
{
$pie = new Pie();

while (!$pie->isDone()) {
$pie->bake();
}

$this->_dispatcher->post(CoffeeShop::PIE_READY, $this, array('pie' => $pie));
}

public function makeCoffee()
{
$coffee = new Coffee();

$this->_dispatcher->post(CoffeeShop::COFFEE_READY, $this, array('coffee' => $coffee));
}

public function recommendToFriends()
{
echo 'This place rocks!';
}
}

$customer = new CoffeeShop_Customer();

$coffeeShop = new CoffeeShop();
$coffeeShop->bakePie();
$coffeeShop->makeCoffee();

/*
This prints:

Eating the pie...
Drinking the coffee...
This place rocks!
*/
{code}
{card}
{card:label=UC-02: Register for multiple notifications}
{code}
class CoffeeShop_Customer
{
public function __construct()
{
$dispatcher = Zend_Message_Dispatcher::get();

// Assumes both use the default callback
$dispatcher->attach($this, array(CoffeeShop::PIE_READY, CoffeeShop::COFFEE_READY)); // Uses the default callback notify()
}

// Rest of the code here ...
}
{code}
{card}
{card:label=UC-03: Wildcards}
{code}
class CoffeeShop_Customer
{
public function __construct()
{
$dispatcher = Zend_Message_Dispatcher::get();

// Attaches the customer to all event starting with foo e.g. fooBar, fooBaz etc.
$dispatcher->attach($this, 'foo*'); // Uses the default callback notify()
}

// Rest of the code here ...
}
{code}
{card}
{card:label=UC-04: Intergrated in Zend_Controller_Front::dispatch()}
{code}
/** Front Controller **/

$dispatcher = Zend_Message_Dispatcher::get();
/**
* Notify plugins of router startup
*/
$this->_plugins->routeStartup($this->_request);
$dispatcher->post('FCRouteStartup', $this, array('request' => $this->_request));

$router->route($this->_request);

/**
* Notify plugins of router completion
*/
$this->_plugins->routeShutdown($this->_request);
$dispatcher->post('FCRouteShutdown', $this, array('request' => $this->_request));

/**
* Notify plugins of dispatch loop startup
*/
$this->_plugins->dispatchLoopStartup($this->_request);
$dispatcher->post('FCDispatchLoopStartup', $this, array('request' => $this->_request));

/**
* Attempt to dispatch the controller/action. If the $this->_request
* indicates that it needs to be dispatched, move to the next
* action in the request.
*/
do {
$this->_request->setDispatched(true);

/**
* Notify plugins of dispatch startup
*/
$this->_plugins->preDispatch($this->_request);
$dispatcher->post('FCPreDispatch', $this, array('request' => $this->_request));

/**
* Skip requested action if preDispatch() has reset it
*/
if (!$this->_request->isDispatched()) {
continue;
}

/**
* Dispatch request
*/
try {
$dispatcher->dispatch($this->_request, $this->_response);
} catch (Exception $e) {
if ($this->throwExceptions()) {
throw $e;
}
$this->_response->setException($e);
}

/**
* Notify plugins of dispatch completion
*/
$this->_plugins->postDispatch($this->_request);
$dispatcher->post('FCPostDispatch', $this, array('request' => $this->_request));

} while (!$this->_request->isDispatched());
} catch (Exception $e) {
if ($this->throwExceptions()) {
throw $e;
}

$this->_response->setException($e);
}

/**
* Notify plugins of dispatch loop completion
*/
try {
$this->_plugins->dispatchLoopShutdown();
$dispatcher->post('FCDispatchLoopShutdown', $this, array('request' => $this->_request));
} catch (Exception $e) {
if ($this->throwExceptions()) {
throw $e;
}

$this->_response->setException($e);
}

/** A plugin or other script that responds to the postDispatch notification **/
class MyLogger
{
public function __construct()
{
$dispatcher = Zend_Message_Dispatcher::get();

// Assumes both use the default callback
$dispatcher->attach($this, 'FCPostDispatch'); // Uses the default callback notify()
}

public function notify()
{
MyLog::writeLine('Another postDispatch has been fired! \o/');
}
}
{code}
{card}
{card:label=UC-05: Usage in dependicy problems (Zym_App)}
{code}
class SetupDb
{
public function __construct(Zend_Config $config)
{
if (!Zend_Registry::isRegistered('cache')) {
Zend_Message_Dispatcher::get()->attach($this, 'CacheIsAvailable');
} else {
$this->_setupFromRegistry();
}

$db = Zend_Db::factory($config);
Zend_Db_Table_Abstract::setDefaultAdapter($db);
}

public function notify(Zend_Message $message)
{
// alternatively, we could've include the cache instance in the message

if ($message->getName() == 'CacheIsAvailable') {
$this->_setupFromRegistry();
}
}

protected function _setupFromRegistry()
{
$cache = Zend_Registry::get('cache');
Zend_Db_Table_Abstract::setDefaultMetadataCache($cache);
}
}

class SetupCache
{
public function __construct(Zend_Config $config)
{
// get front- and backedn options from config
$cache = Zym_Cache::factory('Core', 'File', $frontendOptions, $backendOptions);
Zend_Registry::set('cache', $cache);
Zend_Message_Dispatcher::get()->post('CacheIsAvailable', $this);
}
}

// Note that it doesn't matter in which order I instantiate these, the result will be the same: a db setup with a cache.
new SetupDb(new Zend_Config_Xml('dbconfig.xml'));
new SetupCache(new Zend_Config_Xml('cacheconfig.xml'));
{code}
{card}
{deck}
{zone-data}

{zone-data:skeletons}
{deck:id=Class Skeletons}
{card:label=Zend_Message_Registration}
{code}
class Zend_Message_Registration
{
/**
* @var object
*/
protected $_observer = null;

/**
* @var string
*/
protected $_callback = null;

/**
* Constructor
*
* @param object $observer
* @param string $callback
*/
public function __construct($observer, $callback)
{
}

/**
* Get the observer
*
* @return object
*/
public function getObserver()
{
}

/**
* Get the name of the callback method
*
* @return string
*/
public function getCallback()
{
}
}
{code}
{card}
{card:label=Zend_Message}
{code}
class Zend_Message
{
/**
* Message name
*
* @var string
*/
protected $_name;

/**
* The object that sent the message
*
* @var object
*/
protected $_sender;

/**
* Optional objects
*
* @var array
*/
protected $_data = array();

/**
* Constructor
*
* @param string $name
* @param object $sender
* @param array $data
*/
public function __construct($name, $sender, array $data = array())
{
}

/**
* Get message name
*
* @return string
*/
public function getName()
{
}

/**
* Get the object that sent the message
*
* @return object
*/
public function getSender()
{
}

/**
* Get the optional information
*
* @return array
*/
public function getData()
{
}
}
{code}
{card}
{card:label=Zend_Message_Dispatcher}
{code}
class Zend_Message_Dispatcher
{
/**
* The default callback method name
*
* @var string
*/
protected $_defaultCallback = 'notify';

/**
* Wildcard for the catch-all event
*
* @var string
*/
protected $_wildcard = '*';

/**
* The collection of objects that registered to messages
*
* @var array
*/
protected $_observers = array();

/**
* Messag dispatcher instances
*
* @var array
*/
protected static $_instances = array();

/**
* Get a dispatcher instance from the internal registry
*
* @param string name
* @return Zend_Message_Dispatcher
*/
public static function get($namespace = 'default')
{
}

/**
* Remove a dispatcher instance from the internal registry
*
* @param string $name
*/
public static function remove($namespace)
{
}

/**
* Check if the namespace is already set
*
* @return boolean
*/
public static function has($namespace)
{
}

/**
* Protected constructor
*
*/
protected function __construct()
{
}

/**
* Get the wildcard
*
* @return string
*/
public function getWildcard()
{
}

/**
* Post a message
*
* @param string $name
* @param object $sender
* @param array $data
* @return Zend_Message_Dispatcher
*/
public function post($name, $sender = null, array $data = array())
{
}

/**
* Post the notification
*
* @param Zend_Message $message
* @param Zend_Message_Registration $observerData
*/
protected function _postNotification(Zend_Message $message, Zend_Message_Registration $registration)
{
}

/**
* Register an observer for the specified event
*
* @param object $observer
* @param string|array $events
* @param string $callback
* @return Zend_Message_Dispatcher
*/
public function attach($observer, $events = null, $callback = null)
{
}

/**
* Remove an observer
*
* @param object $observer
* @param string|array $event
* @return Zend_Message_Dispatcher
*/
public function detach($observer, $events = null)
{
}

/**
* Clear an event.
* If no event is specified all events will be cleared.
*
* @param string $event
* @return Zend_Message_Dispatcher
*/
public function reset($event = null)
{
}

/**
* Check if an event is registered
*
* @param string $event
* @return boolean
*/
public function isRegistered($event)
{
}

/**
* Check if the observer is registered for the specified event
*
* @param object|string &$observer either spl_object_hash or the object itself
* @param string $event
* @return boolean
*/
public function hasObserver(&$observer, $event)
{
}
}
{code}
{card}
{card:label=Zend_Message_Exception}
{code}
class Zend_Message_Exception extends Zend_Exception
{
}
{code}
{card}
{zone-data}

{zone-template-instance}]]></ac:plain-text-body></ac:macro>