Pull to refresh

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

Reading time4 min
Views1.2K
Задача — сделать на PHP эвенты а-ля C# т.е. произвольный объект может генерировать события. Другие объекты могут на эти события подписываться непосредственно у экземпляра генерирующего объекта.

Основное отличие от того что видел ранее — строгая проверка навешиваемого хука. Проверяется наличие метода, количество его аргументов, etc…

  1. <?php
  2.  
  3.  abstract class EventClass {
  4.   
  5.   /**
  6.   * Хранятся колбэки для событий
  7.   *
  8.   * @var array
  9.   */
  10.   protected $eventRecievers = array();
  11.   
  12.   /**
  13.   * События - массив строк
  14.   *
  15.   * @var array
  16.   */
  17.   protected $myEvents = array();
  18.   
  19.   /**
  20.   * Сеттер для событий
  21.   *
  22.   * @param string $prop Событие
  23.   * @param callback $val Колбэк
  24.   */
  25.   function __set($prop, $val)
  26.   {
  27.    if(in_array($prop, $this->myEvents))
  28.     $this->attachEvent($prop, $val);
  29.    else
  30.     throw new Exception("Property {$prop} is not found or read only");
  31.   }
  32.   
  33.   /**
  34.   * Caller overloader
  35.   *
  36.   * @param string $name Имя вызываемого метода
  37.   * @param array $args Массив аргументов
  38.   */
  39.   function __call($name, $args)
  40.   {
  41.    if(in_array($name, $this->myEvents))
  42.     $this->fireEvent($name, $args);
  43.    else
  44.     throw new Exception("No such event or bad arguments given");
  45.   }
  46.   
  47.   /**
  48.   * Присоединяет хэндлер события к выбранному событию
  49.   *
  50.   * @param srting $event Событие
  51.   * @param callback $callback Колбэк
  52.   */
  53.   protected function attachEvent($event, $callback)
  54.   {
  55.    if(in_array($event, $this->myEvents))
  56.    {
  57.     if(is_array($callback) && is_object($callback[0]) && is_string($callback[1]))
  58.     {
  59.      $reflection = new ReflectionClass(get_class($callback[0]));
  60.      try {
  61.       $method = $reflection->getMethod($callback[1]);
  62.       if($method->getNumberOfParameters() == 1)
  63.        $this->eventRecievers[$event][]=$callback; 
  64.       else
  65.        throw new Exception(get_class($callback[0])."::{$callback[1]} have more than 1 arguments");
  66.      }
  67.      catch(Exception $e) {
  68.       throw new Exception("Class ".get_class($callback[0])." doesn't have method {$callback[1]}");
  69.      }
  70.     }
  71.     else
  72.      throw new Exception("Wrong callback format");
  73.    }
  74.    else
  75.     throw new Exception(__CLASS__." has no event {$event}");
  76.   }
  77.   
  78.   /**
  79.   * "Зажигает" событие
  80.   *
  81.   * @param string $event
  82.   * @param EventArgument $argument
  83.   */
  84.   protected function fireEvent($event, $argument)
  85.   {
  86.    if(in_array($event, $this->myEvents)) 
  87.    {
  88.     if(is_array($this->eventRecievers[$event]) && count($this->eventRecievers[$event]))
  89.      foreach($this->eventRecievers[$event] as $callback)
  90.       call_user_func_array($callback, array(new EventArgument($this, $argument)));
  91.    }
  92.    else
  93.     throw new Exception(__CLASS__." have no event {$event}");
  94.   }
  95.   
  96.  }
  97.  
  98.  class CanGenerateEvents extends EventClass {
  99.   
  100.   protected $myEvents = array('onSomething', 'onSomethingElse');
  101.  
  102.   // Code goes here  
  103.  
  104.   function makeSomething()
  105.   {
  106.    // Doing something useful...
  107.  
  108.    $this->onSomething("someText");
  109.   }
  110.  
  111.  }
  112.  
  113.  class WantSomething {
  114.  
  115.   function recieveHere(EventArgument $arg)
  116.   {
  117.    echo $arg->argument;
  118.   }
  119.  
  120.  }
  121.  
  122.  $w = new WantSomething();
  123.  $g = new CanGenerateEvents();
  124.  
  125.  $g->onSomething = array($w, 'recieveHere');
  126.  
  127. ?>
* This source code was highlighted with Source Code Highlighter.

Когда в PHP повится метод ReflectionMethod::getClosure() (который, судя по всему, не войдет в 5.3 хотя ранее присутствовал в документации) можно будет отказаться от использования call_user_func_array и использовать замыкания, заменив строку 63 на следующий код
 $this->eventRecievers[$event][]=$method->getClosure();

* This source code was highlighted with Source Code Highlighter.

А строку в функции fireEvent на следующее…
$callback(new EventArgument($this, $argument));

* This source code was highlighted with Source Code Highlighter.


UPD: Забытый класс EventArgument
 class EventArgument {
  
  public $caller = null;
  
  public $argument = null;
  
  function __construct($caller, $argument = null)
  {
   $this->caller = $caller;
   $this->argument = $argument;
  }
  
 }


* This source code was highlighted with Source Code Highlighter.

Tags:
Hubs:
+1
Comments17

Articles

Change theme settings