События широко используются в WEB разработке. Касательно PHP - это способ внедрения своего функционала в сторонний код на определенном этапе его выполнения. Одновременно можно предусмотреть возможность изменения нужных значений переменных и свойств. Большинство CMS (например WordPress), php-фреймворки (например Yii2) используют их.

В качестве примера, я использую класс подключения к базе данных - 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
 ...Соединение с базой данных...