C#-like cобытия для PHP. Reflection, closures…

Posted by Olegas on Июнь 27, 2009
IT

Задача – сделать на 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));

Tags: , ,

Комментариев нет.

Оставить комментарий

WP_Big_City