Несколько способов обратного вызова в 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 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.