Прочитав недавно нововведения PHP 5.3, я обратил внимание на несколько интересных особенностей, скомпоновав которые можно получить реализацию шаблона проектирования Observer, гораздо красивее, чем имеющиеся в pear и symfony, причём вся реализация займёт всего несколько строк кода.
Новое в PHP5.3
Анонимные функции.
Анонимные функции позволяют нам создавать callback'и, фактически на месте, не объявляя никаких функций в основном коде.
__invoke()
Благодаря новому магическому методу, мы можем переопределять событие, происходящее при вызове объекта. Т.е., фактически, к объекту можно обращаться как к функции.
Класс Event
Начнём с, собственно, класса Event. Чтение чужих исходников не могло пройти в пустую и единственный способ управления событиями, который я по началу видел, состоял в реализации кучи методов типа connectEvent и т.п.
В силу же своей природной лени, не хотелось лишний раз заниматься этой рутиной и тогда я вспомнил про замечательный класс ArrayObject, позволяющий хранить данные прямо в объекте и работать с ними напрямую, как с элементами массива. Вот что получилось:
Объект данного класса будет хранить в себе callback'и, а при обращении к объекту как к функции, он будет вызывать по очереди все callback'и.
Пример:
Теперь, казалось бы, осталось самую малость — прикрутить это к какому-нибудь классу. Но всё не так просто.
Класс Eventable
Дело в том, что при попытки обращения к property, как к функции, PHP попытается найти такую функцию в классе и выдаст ошибку, вместо того, чтобы вызывать __invoke для property.
Можно убедиться на этом примере:
Это значит, что нам придется переубедить класс, что он хочет вызывать функцию, тем более, что такой нет. Боюсь, что для этого придётся применить костыль и я буду очень признателен, если кто-то предложит решение более красивое, чем вот это:
Теперь лишь осталось расширить Test от Eventable и насладиться результатом:
Кстати, в качестве callback'ов можно указывать не только анонимные функции, но и объявленные нормально и даже методы классов!
Новое в PHP5.3
Анонимные функции.
Анонимные функции позволяют нам создавать callback'и, фактически на месте, не объявляя никаких функций в основном коде.
__invoke()
Благодаря новому магическому методу, мы можем переопределять событие, происходящее при вызове объекта. Т.е., фактически, к объекту можно обращаться как к функции.
Класс Event
Начнём с, собственно, класса Event. Чтение чужих исходников не могло пройти в пустую и единственный способ управления событиями, который я по началу видел, состоял в реализации кучи методов типа connectEvent и т.п.
В силу же своей природной лени, не хотелось лишний раз заниматься этой рутиной и тогда я вспомнил про замечательный класс ArrayObject, позволяющий хранить данные прямо в объекте и работать с ними напрямую, как с элементами массива. Вот что получилось:
<?php class Event extends ArrayObject { public function __invoke() { foreach($this as $callback) call_user_func_array($callback, func_get_args()); } }
Объект данного класса будет хранить в себе callback'и, а при обращении к объекту как к функции, он будет вызывать по очереди все callback'и.
Пример:
<?php $test = new Event(); /* Setting up callbacks */ $test[] = function($msg, $txt) { echo "This is the event! <br />"; }; $test[] = function($msg, $txt) { echo "<b>Message</b>: $msg. <b>Text</b>: $txt <br />"; }; $test[] = function($msg, $txt) { echo "Works great! <br />"; }; /* call */ $test("Some message", "Some text");
Теперь, казалось бы, осталось самую малость — прикрутить это к какому-нибудь классу. Но всё не так просто.
Класс Eventable
Дело в том, что при попытки обращения к property, как к функции, PHP попытается найти такую функцию в классе и выдаст ошибку, вместо того, чтобы вызывать __invoke для property.
Можно убедиться на этом примере:
class Test { public $onA; public function __construct() { $this->onA = new Event(); } public function A($txt) { $this->onA("This is A.", $txt); } } $test = new Test(); $test->onA[] = function($msg, $txt) { echo "This is the event! <br />"; }; $test->A("Le Test");
Это значит, что нам придется переубедить класс, что он хочет вызывать функцию, тем более, что такой нет. Боюсь, что для этого придётся применить костыль и я буду очень признателен, если кто-то предложит решение более красивое, чем вот это:
<?php class Eventable { public function __call($name, $args) { if( method_exists($this, $name) ) call_user_func_array(array(&$this, $name), $args); else if( isset($this->{$name}) && is_callable($this->{$name}) ) call_user_func_array($this->{$name}, $args); } }
Теперь лишь осталось расширить Test от Eventable и насладиться результатом:
class Test extends Eventable { public $onA; public function __construct() { $this->onA = new Event(); } public function A($txt) { $this->onA("This is A.", $txt); } } $test = new Test(); /* setting up callbacks */ $test->onA[] = function($msg, $txt) { echo "This is the event! <br />"; }; $test->onA[] = function($msg, $txt) { echo "<b>Message</b>: $msg. <b>Text</b>: $txt <br />"; }; $test->onA[] = function($msg, $txt) { echo "Works great! <br />"; }; /* call */ $test->A("Le Test");
Кстати, в качестве callback'ов можно указывать не только анонимные функции, но и объявленные нормально и даже методы классов!
$test->onA[] = "some_function"; $test->onA[] = array(&$some_object, "some_method");