Несколько способов обратных вызовов в PHP

Самый удобный способ реализации обратных вызовов в PHP — это использование анонимных функций, но помимо анонимных функций, в PHP есть и другие способы написания обратных вызовов. Вот несколько примеров различных способов применения обратных вызовов в PHP.

1. Обратный вызов с анонимной функцией

<?php
use Workerman\Worker;
use Workerman\Connection\TcpConnection;
use Workerman\Protocols\Http\Request;
require_once __DIR__ . '/vendor/autoload.php';

$http_worker = new Worker("http://0.0.0.0:2345");

// Обратный вызов с анонимной функцией
$http_worker->onMessage = function(TcpConnection $connection, Request $data)
{
    // Отправка 'hello world' в браузер
    $connection->send('hello world');
};

Worker::runAll();

2. Обратный вызов с обычной функцией

<?php
use Workerman\Worker;
use Workerman\Connection\TcpConnection;
use Workerman\Protocols\Http\Request;
require_once __DIR__ . '/vendor/autoload.php';

$http_worker = new Worker("http://0.0.0.0:2345");

// Обратный вызов с обычной функцией
$http_worker->onMessage = 'on_message';

// Обычная функция
function on_message(TcpConnection $connection, Request $request)
{
    // Отправка 'hello world' в браузер
    $connection->send('hello world');
}

Worker::runAll();

3. Метод класса как обратный вызов

MyClass.php

use Workerman\Worker;
use Workerman\Connection\TcpConnection;
class MyClass{
    public function __construct(){}
    public function onWorkerStart(Worker $worker){}
    public function onConnect(TcpConnection $connection){}
    public function onMessage(TcpConnection $connection, $message) {}
    public function onClose(TcpConnection $connection){}
    public function onWorkerStop(Worker $worker){}
}

Скрипт запуска start.php

<?php
use Workerman\Worker;
require_once __DIR__ . '/vendor/autoload.php';

// Подключение MyClass
require_once __DIR__.'/MyClass.php';

$worker = new Worker("websocket://0.0.0.0:2346");

// Создание объекта
$my_object = new MyClass();

// Вызов методов класса
$worker->onWorkerStart = array($my_object, 'onWorkerStart');
$worker->onConnect     = array($my_object, 'onConnect');
$worker->onMessage     = array($my_object, 'onMessage');
$worker->onClose       = array($my_object, 'onClose');
$worker->onWorkerStop  = array($my_object, 'onWorkerStop');

Worker::runAll();

Примечание:
Структура кода выше не позволяет инициализировать ресурсы (например, соединения с MySQL, Redis, Memcache и т.д.) в конструкторе, потому что $my_object = new MyClass(); выполняется в основном процессе. Например, в случае с MySQL, если соединение с MySQL и другие ресурсы инициализируются в основном процессе, они будут унаследованы дочерними процессами, каждый из которых сможет работать с этим соединением к базе данных, но эти соединения на стороне сервера MySQL соответствуют одному соединению, что может привести к непредсказуемым ошибкам, таким как ошибка mysql gone away.

Если в классе необходимо инициализировать ресурсы в конструкторе, можно использовать следующий способ.
MyClass.php

use Workerman\Worker;
use Workerman\Connection\TcpConnection;
class MyClass{
    protected $db = null;
    public function __construct(){
        // Предполагаем, что класс соединения с базой данных называется MyDbClass
        $db = new MyDbClass();
    }
    public function onWorkerStart(Worker $worker){}
    public function onConnect(TcpConnection $connection){}
    public function onMessage(TcpConnection $connection, $message) {}
    public function onClose(TcpConnection $connection){}
    public function onWorkerStop(Worker $worker){}
}

Скрипт запуска start.php

<?php
use Workerman\Worker;
require_once __DIR__ . '/vendor/autoload.php';

$worker = new Worker("websocket://0.0.0.0:2346");

// Инициализация класса в onWorkerStart
$worker->onWorkerStart = function($worker) {
    // Подключение MyClass
    require_once __DIR__.'/MyClass.php';

    // Создание объекта
    $my_object = new MyClass();

    // Вызов методов класса
    $worker->onConnect    = array($my_object, 'onConnect');
    $worker->onMessage    = array($my_object, 'onMessage');
    $worker->onClose      = array($my_object, 'onClose');
    $worker->onWorkerStop = array($my_object, 'onWorkerStop');
};

Worker::runAll();

В данной структуре кода onWorkerStart выполняется уже в дочернем процессе, что позволяет каждому дочернему процессу устанавливать свое собственное соединение с MySQL, поэтому проблемы с совместным использованием соединений не возникает. Также это дает возможность поддерживать перезагрузку бизнес-кода. Поскольку MyClass.php загружается в дочернем процессе, изменения, внесенные в MyClass.php, могут быть легко применены при перезагрузке.

4. Статический метод класса как обратный вызов

Статический класс MyClass.php

use Workerman\Worker;
use Workerman\Connection\TcpConnection;
class MyClass{
    public static function onWorkerStart(Worker $worker){}
    public static function onConnect(TcpConnection $connection){}
    public static function onMessage(TcpConnection $connection, $message) {}
    public static function onClose(TcpConnection $connection){}
    public static function onWorkerStop(Worker $worker){}
}

Скрипт запуска start.php

<?php
use Workerman\Worker;
require_once __DIR__ . '/vendor/autoload.php';

// Подключение MyClass
require_once __DIR__.'/MyClass.php';

$worker = new Worker("websocket://0.0.0.0:2346");

// Вызов статических методов класса.
$worker->onWorkerStart = array('MyClass', 'onWorkerStart');
$worker->onConnect     = array('MyClass', 'onConnect');
$worker->onMessage     = array('MyClass', 'onMessage');
$worker->onClose       = array('MyClass', 'onClose');
$worker->onWorkerStop  = array('MyClass', 'onWorkerStop');

// Если класс имеет пространство имен, используется подобный синтаксис
// $worker->onWorkerStart = array('your\namesapce\MyClass', 'onWorkerStart');
// $worker->onConnect     = array('your\namesapce\MyClass', 'onConnect');
// $worker->onMessage     = array('your\namesapce\MyClass', 'onMessage');
// $worker->onClose       = array('your\namesapce\MyClass', 'onClose');
// $worker->onWorkerStop  = array('your\namesapce\MyClass', 'onWorkerStop');

Worker::runAll();

Примечание: В соответствии с механизмом выполнения PHP, если не использовать вызов new, конструктор не будет вызван, кроме того, в статических методах класса нельзя использовать $this.