Прочитав недавно нововведения 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");