
В качестве примера, я использую класс подключения к базе данных - class Db и создам в нем два события:
- BeforeConnect (перед подключением);
- AfterConnect (после подключения).
Код нашего примера:
class Callback { static public $callbacks = array(); //Регистрирует события (функции обратного вызова). static function add_action($action, Closure $fun){ self::$callbacks[] = [ $action => $fun ]; } static function doCallback($name, array $arg = null) { //В цикле вызов переданных функций обратного вызова foreach (self::$callbacks as $callback) { /* * Проверяем существование функций повешенных на событие * Ключ массива ($callback) должен совпадать с названием события ($name) */ if (array_key_exists($name, $callback)) { //Проверка возможности вызова анонимной функции if (!is_callable ($callback[$name]) ) { throw new Exception ( "Функция обратного вызова - невызываемая ! " ) ; } call_user_func($callback[$name], $arg); } } } } class Db { private $dbName = 'localhost'; function connect() { //Событие перед соединением с БД Callback::doCallback('BeforeConnect', ['dbName' =>$this->dbName]); echo '...Соединение с базой данных...<br>'; //Событие после соединения с БД Callback::doCallback('AfterConnect'); } } $my_fun1 = function ($arg){ echo 'Сработало событие "BeforeConnect". Хост: '.$arg['dbName'] .'.<br>'; }; Callback::add_action( 'BeforeConnect', $my_fun1 ); $my_fun2 = function (){ echo 'Сработало событие "AfterConnect"<br>'; }; Callback::add_action( 'AfterConnect', $my_fun2 ); $my_fun3 = function (){ echo 'Еще одно событие "BeforeConnect"<br>'; }; Callback::add_action( 'BeforeConnect', $my_fun3 ); $db = new Db(); $db->connect();Результат:
Сработало событие "BeforeConnect". Хост: localhost. Еще одно событие "BeforeConnect" ...Соединение с базой данных... Сработало событие "AfterConnect"
В общих чертах процесс создания и использования событий в php можно описать так:
- код, который должен выполниться при наступлении определенного события (или вызов другой функции/метода), помещается в анонимную функцию, которая присваивается какой-либо переменной;
- далее эта
переменная передается в метод add_action() класса Callback, ответственного за
использование (хранение/выполнение) анонимных функций. Вместе с анонимной
функцией передается название события, по которому она должна выполняться. Анонимные функции c PHP5.3 являются экземплярами класса Closure, поэтому при приеме аргумента, можем ограничить его тип:
static function add_action($action, Closure $fun) { ... - в классе Callback, анонимная функция добавляется в ассоциативный массив для хранения, где ключом массива становится название действия.
- для вызова события, в приложении, в нужных местах, размещается вызов метода класса Callback который ответственен за выполнение анонимных функций - doCallback(). Аргументом передается идентификатор события.
- массив анонимных функций перебирается в цикле, и если названию ключа массива совпадает с названием события – оно выполняется.
Прежде всего создаем отдельный класс, ответственный за прием, хранение и выполнение анонимных функций, которые будут выполняться при наступлении определенных действий. В этих анонимных функциях, мы можем разместить как весь выполняемый код, так и предусмотреть выполнение других функций и методов классов(объектов). Если это предусмотрено кодом события, из его области видимости в анонимную функцию можно передать нужные параметры. Так же, используя технологию замыканий, в анонимной функции можно получить доступ к переменным/свойствам в которых она была объявлена.
Если нужно вызвать по событию метод какого-либо класса:
/* * Класс для тестирования * будем выполнять его метод при наступлении события. */ class TestAction{ public function getText(){ echo 'И еще одно событие "BeforeConnect" из метода класса.<br>'; } } $test = new TestAction(); Callback::add_action( 'BeforeConnect', function() use ($test){ $test->getText(); });
А если класс используется только при наступлении события, то лучше так:
class TestAction{ static public function getText(){ echo 'И еще одно событие "BeforeConnect" из метода класса.<br>'; } } Callback::add_action( 'BeforeConnect', function(){ TestAction::getText(); });или так:
class TestAction{ static function getAction(){ Callback::add_action( 'BeforeConnect', function(){ echo 'И еще одно событие "BeforeConnect" из метода класса.<br>'; }); } } TestAction::getAction();
Передавая, при наступлении события, в качестве аргумента для анонимной функции значение по ссылке
Callback::doCallback('BeforeConnect', ['dbName' =>&$this->dbName]);
можно вносить изменения в код, меняя свойства/методы.
Так же можно сделать фильтр, похожий на тот, что используется в CMS WordPress. Фильтр это тоже самое событие, которое получает в качестве аргумента данные из кода, в анонимной функции эти данные обрабатывает и возвращает в измененном виде.
Только нужно проставить «return» в функциях, а результат сохранить. Данный код можно использовать и при вызове обычных событий, для этого анонимная функция просто не должна ничего возвращать (не использовать оператор return или return = null).
class Callback { static public $callbacks = array(); //Регистрирует события (функции обратного вызова). static function add_action($action, $fun){ self::$callbacks[] = [ $action => $fun ]; } static function doCallback($name, array $arg = null) { //В цикле вызов переданных функций обратного вызова foreach (self::$callbacks as $callback) { /* * Проверяем существование функций повешенных на событие * Ключ массива ($callback) должен совпадать с названием события ($name) */ if (array_key_exists($name, $callback)) { //Проверка возможности вызова анонимной функции if (!is_callable ($callback[$name]) ) { throw new Exception ( "Функция обратного вызова - невызываемая ! " ) ; } $result = call_user_func($callback[$name], $arg); if (isset($result))return $result; } } } } class Db { public $dbName = 'localhost'; function connect() { //Событие перед соединением с БД $result = Callback::doCallback('BeforeConnect', ['dbName' =>$this->dbName]); if($result) $this->dbName = $result; echo 'Измененное значение свойства: '.$this->dbName.'<br>'; echo '...Соединение с базой данных...<br>'; //Событие после соединения с БД Callback::doCallback('AfterConnect'); } } /* * Класс для тестирования * будем выполнять его метод при наступлении события. */ class TestAction{ static public function getText($arg){ echo 'Значение свойства по-умолчанию: '. $arg['dbName']. '<br>'; return 'myHost'; } } Callback::add_action( 'BeforeConnect', function($arg = null){ $result = TestAction::getText($arg); return $result; }); $db = new Db(); $db->connect();
Значение свойства по-умолчанию: localhost Измененное значение свойства: myHost ...Соединение с базой данных...
ответ на комментарий Дим от 06.10.2017