<?php
/**
* ACE - Advanced Content Environment Framework
*
* LICENSE
*
* This is a part of ACE - Advanced Content Environment Framework,
* any usage without permission of the author is prohibited.
* Licence can be obtained by sending email to hide@address.com
*
* @category ACE
* @package ACE
* @author MichaÅ Szpakowski
* @copyright Copyright (c) 2007 MichaÅ Szpakowski
* @license Limited, email hide@address.com
*/
/**
* @category ACE
* @package Event
* @copyright Copyright (c) 2007 MichaÅ Szpakowski
* @license Limited, email hide@address.com
*/
final class Event
{
/**
* Public constants for signalling access of Event.
* Access is meant to restrict possibility to call the event
* via the {@link Event::call()} method.
* E_PRIVATE: event can only be called by the object passed to
* the constructor. Useful, when the caller is private or protected
* as var inside a class.
* E_PROTECTED: event can be called only by the object
* of specific class - for information purposes only, belonging
* of the event to the specific package, as there is no posiibility
* to define 'private' class in php/
* E_PUBLIC: event can be called by any instance of {@link EventCaller}
* or it's descendants.
*
*/
const E_PRIVATE = 0;
const E_PROTECTED = 1;
const E_PUBLIC = 2;
/**
* Private static array to hold instances of Event
*
* @var array
* @access private
*/
private static $__events = array();
/**
* Name of the event, same as key to {@link Event::$__events}
* array.
*
* @var string
* @access private
*/
private $__eventname;
/**
* Access flag: {@link Event::E_PRIVATE},
* {@link Event::E_PROTECTED}, {@link Event::E_PUBLIC}
* {@link Event::getAccess()}
*
* @var int
* @access private
*/
private $__access;
/**
* Instance of EventCaller or string classname;
* {@link Event::getCallerClass()}
*
* @var object|string
* @access private
*/
private $__caller;
/**
* Dataset passed by *Call() methods
* {@link Event::getDataset()}
*
* @var string
* @access private
*/
private $__dataset;
/**
* Flag - if to destroy the event after calling
* {@link Event::isDisposable()}
*
* @var bool
* @access private
*/
private $__disposable;
/**
* Name of the interface implemented by listeners
* {@link Event::factory()}
* {@link Event::getEventInterface()}
*
* @var string
* @access private
*/
private $__eventinterface;
/**
* Array of listeners
* {@link Event::__eventinterface}
* {@link Event::isListener()}
* {@link Event::addListener()}
* {@link Event::removeListener()}
*
* @var string
* @access private
*/
private $__listeners = array();
/**
* Static private object of EventCaller, to call every time new event
* is created.
* {@link Event::init()}
*
* @var object
* @access private
*/
private static $__eventcreatedcaller;
/**
* Flag if to rethrow the exceptions
* {@link Event::factory()}
* {@link Event::throwExceptions()}
*
* @var bool
* @access private
*/
private static $__throwexceptions = false;
/**
* Private constructor, singleton pattern.
* The aim is to construct the object and save it in the
* {@link Event::$__events} array, giving only the key
* {@link Event::factory()}
*
* @param string $name
* @param int $access
* @param object|string $caller
* @param object|string $dataset
* @param bool $disposable
* @param string $eventinterface
*/
private function __construct($name, $access, $caller, $dataset, $disposable, $eventinterface)
{
if (is_string($name) AND $name != '' AND !isset(self::$__events[$name]))
{
switch ($access)
{
case self::E_PRIVATE :
$this->__eventname = $name;
if($caller instanceof EventCaller)
{
$this->__access = self::E_PRIVATE ;
$this->__caller = $caller;
if (!$caller->bind($name)) throw new ACE_Exception('No unique $caller given');
}
else throw new ACE_Exception('$caller has to be instance of EventCaller or one of it\'s descendants');
if ($dataset === null) $this->__dataset = null;
elseif (is_object($dataset)) $this->__dataset = get_class($dataset);
elseif (is_string($dataset) AND class_exists($dataset)) $this->__dataset = $dataset;
else throw new ACE_Exception('$dataset must be either null or an object or an existing class name');
if (is_bool($disposable)) $this->__disposable = ($disposable ? true : false);
else throw new ACE_Exception('$disposable must be boolean');
if (interface_exists($eventinterface) AND in_array('on'.$name.'Call',get_class_methods($eventinterface),true) AND in_array('on'.$name.'Destroy',get_class_methods($eventinterface),true)) $this->__eventinterface = $eventinterface;
else throw new ACE_Exception('$eventinterface has to be an existing interface and define the on'.$name.'Call and on'.$name.'Destroy method');
if ($name != 'EventCreated') self::call('EventCreated',self::$__eventcreatedcaller,new EventInfo($name,$access,'EventCaller',$dataset,$disposable,$eventinterface));
break;
case self::E_PROTECTED :
$this->__eventname = $name;
if(is_object($caller) AND $caller instanceof EventCaller)
{
$this->__access = self::E_PROTECTED ;
$this->__caller = get_class($caller);
}
elseif (is_string($caller) AND ($caller == 'EventCaller' OR is_subclass_of($caller,'EventCaller')))
{
$this->__access = self::E_PROTECTED ;
$this->__caller = $caller;
}
else throw new ACE_Exception('$caller has to be either instance of EventCaller or it\'s descendatns or appropriate classname string');
if ($dataset == null) $this->__dataset = null;
elseif (is_object($dataset)) $this->__dataset = get_class($dataset);
elseif (is_string($dataset) AND class_exists($dataset)) $this->__dataset = $dataset;
else throw new ACE_Exception('$dataset must be either null or an object or an existing class name');
if (is_bool($disposable)) $this->__disposable = ($disposable ? true : false);
else throw new ACE_Exception('$disposable must be boolean');
if (interface_exists($eventinterface) AND in_array('on'.$name.'Call',get_class_methods($eventinterface),true) AND in_array('on'.$name.'Destroy',get_class_methods($eventinterface),true)) $this->__eventinterface = $eventinterface;
else throw new ACE_Exception('$eventinterface has to be existing interface and define the on.$name.Call and on.$name.Destroy method');
if ($name != 'EventCreated') self::call('EventCreated',self::$__eventcreatedcaller,new EventInfo($name,$access,'EventCaller',$dataset,$disposable,$eventinterface));
break;
case self::E_PUBLIC :
$this->__eventname = $name;
$this->__caller = 'EventCaller';
$this->__access = self::E_PUBLIC ;
if ($dataset == null) $this->__dataset = null;
elseif (is_object($dataset)) $this->__dataset = get_class($dataset);
elseif (is_string($dataset) AND class_exists($dataset)) $this->__dataset = $dataset;
else throw new ACE_Exception('$dataset must be either null or an object or an existing class name');
if (is_bool($disposable)) $this->__disposable = ($disposable ? true : false);
else throw new ACE_Exception('$disposable must be boolean');
if (interface_exists($eventinterface) AND in_array('on'.$name.'Call',get_class_methods($eventinterface),true) AND in_array('on'.$name.'Destroy',get_class_methods($eventinterface),true)) $this->__eventinterface = $eventinterface;
else throw new ACE_Exception('$eventinterface has to be existing interface and define the on.$name.Call and on.$name.Destroy method');
if ($name != 'EventCreated') self::call('EventCreated',self::$__eventcreatedcaller,new EventInfo($name,$access,'EventCaller',$dataset,$disposable,$eventinterface));
break;
default:
throw new ACE_Exception('$access must be Event::E_PRIVATE, E_PROTECTED or E_PUBLIC');
}
self::$__events[$name] = $this;
}
else throw new ACE_Exception('Event already exists or $name is not a string!');
}
/**
* Private cloner, singleton pattern.
*/
private function __clone()
{}
/**
* Public factory method to create event and save it in the
* {@link Event::$__events} array.
* {@link Event::__construct}
*
* @param string $name
* @param int $access
* @param object|string $caller
* @param object|string $dataset
* @param bool $disposable
* @param string $eventinterface
* @return bool|string
*/
public static function factory($name, $access = self::E_PUBLIC, $caller = null, $dataset = null, $disposable = true, $eventinterface = null)
{
self::init();
if ($eventinterface == null) $eventinterface = 'i'.$name.'Event';
try {
new Event($name,$access,$caller, $dataset, $disposable,$eventinterface);
} catch (ACE_Exception $e) {
if (self::$__throwexceptions == true) throw $e;
return false;
} return $name;
}
/**
* Static function to get access flag of event $name
* {@link Event::E_PRIVATE}, {@link Event::E_PROTECTED},
* {@link Event::E_PUBLIC}
* {@link Event::__access}
*
* @param string $name
* @return int
*/
public static function getAccess($name)
{
return self::$__events[$name]->__access;
}
/**
* Static function to get caller class. If event is {@link Event::E_PRIVATE} returns
* null, else returns classname of caller.
* {@link Event::__caller}
*
* @param unknown_type $name
* @return null|string
*/
public static function getCallerClass($name)
{
if (is_string(self::$__events[$name]->__caller)) return self::$__events[$name]->__caller;
return null;
}
/**
* Static function to determine if the $caller parameter is
* able to call event. If Event is {@link Event::E_PRIVATE},
* $caller has to be object
* {@link Event::__caller}
*
* @param string $name
* @param string|object $caller
* @return bool
*/
public static function isCaller($name,$caller)
{
if (isset(self::$__events[$name]))
{
if (is_object(self::$__events[$name]->__caller)) return (self::$__events[$name]->__caller === $caller);
if (is_object($caller)) return ($caller instanceof self::$__events[$name]->__caller);
return (self::$__events[$name]->__caller == $caller OR is_subclass_of($caller,self::$__events[$name]->__caller));
}
return false;
}
/**
* Static function to determine if event $name exists
* {@link Event::__events}
*
* @param string $name
* @return bool
*/
public static function eventExists($name)
{
if(isset(self::$__events[$name])) return true;
return false;
}
/**
* Function to inform Event that caller is just to be destroyed.
* ALWAYS EXPLICITLY call this function if your event is {@link Event::E_PRIVATE}
* as the {@link Event::__caller} is a reference to the caller object, so
* unsetting or $caller = null will not work for destroying object.
*
* @param unknown_type $caller
* @param unknown_type $name
* @return unknown
*/
public static function callerDestroyed($caller,$name)
{
if (isset(self::$__events[$name]) AND self::$__events[$name]->__access == self::E_PRIVATE AND self::$__events[$name]->__caller === $caller)
{
unset(self::$__events[$name]);
return true;
}
return false;
}
/**
* Static function to determine dataset class.
* {@link Event::__dataset}
*
* @param string $name
* @return null|object
*/
public static function getDataset($name)
{
return self::$__events[$name]->__dataset;
}
/**
* Static function to determine if the event should be destroyed
* after {@link Event::call()}ing
* {@link Event::__disposable}
*
* @param string $name
* @return bool
*/
public static function isDisposable($name)
{
return self::$__events[$name]->__disposable;
}
/**
* Static function to determine the listener's interface.
* Only objects of classes that implement this interface can be
* added to {@link Event::__listeners} array.
* {@link Event::__eventinterface}
*
* @param string $name
* @return string
*/
public static function getEventInterface($name)
{
return self::$__events[$name]->__eventinterface;
}
/**
* Static function to determine if the provided object
* is a listener. Returns int if the object is a listener.
* CAUTION! It CAN RETURN _0_ which means that object IS the listener
* of the event, and has index 0 (first element in array).
* USE === when comparing to null or false.
* If the $listener object IS NOT a listener, return null
* If the $name event doesn't exist, return false
* {@link Event::__listeners}
*
* @param string $name
* @param object $listener
* @return bool|null|int
*/
public function isListener($name,$listener)
{
if (isset(self::$__events[$name]) AND $listener instanceof self::$__events[$name]->__eventinterface)
{
foreach (self::$__events[$name]->__listeners as $key => $val)
{
if ($listener === $val) return $key;
}
return null;
}
return false;
}
/**
* Static function to add listener to an object name.
* $listener class must implement the {@link Event::__eventinterface}
* interface.
* {@link Event::__listeners}
*
* @param string $name
* @param object $listener
* @return bool
*/
public static function addListener($name,$listener)
{
if (self::isListener($name,$listener) === null)
{
self::$__events[$name]->__listeners[] = $listener;
return true;
}
return false;
}
/**
* Static function to remove listener from
* {@link Event::__listeners} array.
* {@link Event::__listeners}
*
* @param string $name
* @param object $listener
* @return true
*/
public static function removeListener($name,$listener)
{
$key = self::isListener($name,$listener);
if (is_int($key))
{
unset(self::$__events[$name]->__listeners[$key]);
return true;
}
return false;
}
/**
* Static function to call an event.
* {@link Event::__listeners}
*
* @param string $name
* @param object $caller
* @param object $dataset
* @return bool
*/
public static function call($name,$caller,$dataset = null)
{
if (isset(self::$__events[$name]))
{
if (self::$__events[$name]->__dataset != null and !($dataset instanceof self::$__events[$name]->__dataset)) throw new ACE_Exception('$dataset must be instance of '.self::$__events[$name]->__dataset);
if (self::$__events[$name]->__dataset == null) $dataset = null;
switch (self::$__events[$name]->__access)
{
case self::E_PRIVATE :
if (self::$__events[$name]->__caller === $caller)
{
$call = 'on'.$name.'Call';
$result = true;
foreach (self::$__events[$name]->__listeners as $val) if ($val instanceof self::$__events[$name]->__eventinterface AND !($val->$call($dataset))) $result = false;
if (self::$__events[$name]->__disposable) unset(self::$__events[$name]);
return $result;
}
else throw new ACE_Exception('$caller is not a valid caller');
break;
case self::E_PROTECTED :
case self::E_PUBLIC :
if ($caller instanceof self::$__events[$name]->__caller)
{
$call = 'on'.$name.'Call';
$result = true;
foreach (self::$__events[$name]->__listeners as $val) if ($val instanceof self::$__events[$name]->__eventinterface AND !($val->$call($dataset))) $result = false;
if (self::$__events[$name]->__disposable) unset(self::$__events[$name]);
return $result;
}
else throw new ACE_Exception('$caller is not a valid caller');
break;
}
}
else throw new ACE_Exception('$name is not a valid event');
}
/**
* Initialisation function, initialises the EventCreated Event.
* Can also set wether to re-throw exceptions or not.
* {@link Event::__eventcreatedcaller}
*
* @param bool $te
* @return string|bool
*/
public static function init($te = null)
{
if (!isset(self::$__events['EventCreated']))
{
if (is_bool($te) AND $te == true) self::$__throwexceptions = true;
self::$__eventcreatedcaller = new EventCaller();
try {
new Event('EventCreated',self::E_PRIVATE ,self::$__eventcreatedcaller,'EventInfo',false,'iEventCreatedEvent');
} catch (ACE_Exception $e) {
if (self::$__throwexceptions == true) throw $e;
return false;
} return $name;
}
return false;
}
/**
* Static function to set {@link Event::__throwexceptions} flag.
* {@link Event::__throwexceptions}
*
* @param bool $te
* @return bool
*/
public static function throwExceptions($te = null)
{
if (is_bool($te))
{
self::$__throwexceptions = $te;
return true;
}
return false;
}
/**
* Static function for Event destruction.
* {@link Event::E_PRIVATE} Events can be destroyed only by "their",
* proper callers. {@link Event::E_PROTECTED} by the object of a
* {@link Event::__caller} class, or one of it's descendants.
* {@link Event::E_PUBLIC} events can be destroyed by EventCaller objects.
* See {@link Event::call()}, {@link Event::__destruct()}, {@link Event::callerDestroyed()}.
*
* @param string $name
* @param object $caller
* @return bool
*/
public static function destroyEvent($name, $caller)
{
if (isset(self::$__events[$name]))
{
switch (self::$__events[$name]->__access)
{
case self::E_PRIVATE :
if (self::$__events[$name]->__caller === $caller)
{
unset(self::$__events[$name]);
return true;
}
return false;
case self::E_PROTECTED :
case self::E_PUBLIC :
if ($caller instanceof self::$__events[$name]->__caller)
{
unset(self::$$__events[$name]);
return true;
}
return false;
}
}
return false;
}
/**
* Destructor class, called on unsetting event in
* {@link Event::__events}
*
* @return unknown
*/
public function __destruct()
{
$call = 'on'.$this->__eventname.'Destroy';
$result = true;
foreach ($this->__listeners as $val) if ($val instanceof $this->__eventinterface AND !($val->$call())) $result = false;
return $result;
}
}
interface iEventCreatedEvent
{
public function onEventCreatedCall($dataset);
public function onEventCreatedDestroy();
}
?>