Pull to refresh

Реализация паттерна Observer средствами PHP 5.3

Reading time 3 min
Views 11K
Прочитав недавно нововведения PHP 5.3, я обратил внимание на несколько интересных особенностей, скомпоновав которые можно получить реализацию шаблона проектирования Observer, гораздо красивее, чем имеющиеся в pear и symfony, причём вся реализация займёт всего несколько строк кода.

Новое в 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");
Tags:
Hubs:
+42
Comments 28
Comments Comments 28

Articles