Задача – сделать на PHP эвенты а-ля C# т.е. произвольный объект может генерировать события. Другие объекты могут на эти события подписываться непосредственно у экземпляра генерирующего объекта.
abstract class EventClass {
/**
* Хранятся колбэки для событий
*
* @var array
*/
protected $eventRecievers = array();
/**
* События - массив строк
*
* @var array
*/
protected $myEvents = array();
/**
* Сеттер для событий
*
* @param string $prop Событие
* @param callback $val Колбэк
*/
function __set($prop, $val)
{
if(in_array($prop, $this->myEvents))
$this->attachEvent($prop, $val);
else
throw new Exception("Property {$prop} is not found or read only");
}
/**
* Caller overloader
*
* @param string $name Имя вызываемого метода
* @param array $args Массив аргументов
*/
function __call($name, $args)
{
if(in_array($name, $this->myEvents))
$this->fireEvent($name, current($args));
else
throw new Exception("No such event or bad arguments given");
}
/**
* Присоединяет хэндлер события к выбранному событию
*
* @param srting $event Событие
* @param callback $callback Колбэк
*/
protected function attachEvent($event, $callback)
{
if(in_array($event, $this->myEvents))
{
if(is_array($callback) && is_object($callback[0]) && is_string($callback[1]))
{
$reflection = new ReflectionClass(get_class($callback[0]));
try {
$method = $reflection->getMethod($callback[1]);
if($method->getNumberOfParameters() == 1)
$this->eventRecievers[$event][]=$callback;
else
throw new Exception(get_class($callback[0])."::{$callback[1]} have more than 1 arguments");
}
catch(Exception $e) {
throw new Exception("Class ".get_class($callback[0])." doesn't have method {$callback[1]}");
}
}
else
throw new Exception("Wrong callback format");
}
else
throw new Exception(__CLASS__." have no event {$event}");
}
/**
* "Зажигает" событие
*
* @param string $event
* @param EventArgument $argument
*/
protected function fireEvent($event, $argument)
{
if(in_array($event, $this->myEvents))
{
if(is_array($this->eventRecievers[$event]) && count($this->eventRecievers[$event]))
foreach($this->eventRecievers[$event] as $callback)
call_user_func_array($callback, array(new EventArgument($this, $argument)));
}
else
throw new Exception(__CLASS__." have no event {$event}");
}
}
class CanGenerateEvents extends EventClass {
protected $myEvents = array('onSomething', 'onSomethingElse');
// Code goes here
function makeSomething()
{
// Doing something useful...
$this->onSomething("someText");
}
}
class WantSomething {
function recieveHere(EventArgument $arg)
{
echo $arg->argument;
}
}
$w = new WantSomething();
$g = new CanGenerateEvents();
$g->onSomething = array($w, 'recieveHere');
Когда в PHP повится метод ReflectionMethod::getClosure() (который, судя по всему, не войдет в 5.3 хотя ранее присутствовал в документации) можно будет отказаться от использования call_user_func_array и использовать замыкания, заменив строку 63 на следующий код
$this->eventRecievers[$event][]=$method->getClosure();
А строку в функции fireEvent на следующее…
$callback(new EventArgument($this, $argument));
