วิธีการเขียน Callback ใน PHP

การเขียน Callback โดยใช้ฟังก์ชันนิรนามใน PHP นั้นสะดวกที่สุด แต่ใน PHP ยังมีรูปแบบการเขียน Callback อื่น ๆ อีกมากมาย ด้านล่างนี้เป็นตัวอย่างวิธีการเขียน Callback ใน PHP หลายวิธี

1. Callback ด้วยฟังก์ชันนิรนาม

<?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");

// Callback ด้วยฟังก์ชันนิรนาม
$http_worker->onMessage = function(TcpConnection $connection, Request $data)
{
    // ส่ง hello world ไปที่เบราว์เซอร์
    $connection->send('hello world');
};

Worker::runAll();

2. Callback ด้วยฟังก์ชันปกติ

<?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");

// Callback ด้วยฟังก์ชันปกติ
$http_worker->onMessage = 'on_message';

// ฟังก์ชันปกติ
function on_message(TcpConnection $connection, Request $request)
{
    // ส่ง hello world ไปที่เบราว์เซอร์
    $connection->send('hello world');
}

Worker::runAll();

3. วิธีการเรียกใช้เมธอดของคลาสเป็น Callback

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) ใน constructor เนื่องจาก $my_object = new MyClass(); ทำงานในกระบวนการหลัก ในตัวอย่างการเชื่อมต่อ MySQL การเริ่มต้นในกระบวนการหลักจะถูกสืบทอดไปยัง subprocess แต่ละตัว ซึ่งทำให้ subprocess แต่ละตัวสามารถจัดการกับการเชื่อมต่อนี้ได้ แต่การเชื่อมต่อนี้ในฝั่งบริการ MySQL จะเป็นการเชื่อมต่อเดียวกัน ส่งผลให้เกิดข้อผิดพลาดเช่น mysql gone away

หากต้องการเริ่มต้นทรัพยากรใน constructor ของคลาสตามโครงสร้างโค้ดข้างต้น สามารถใช้วิธีการดังต่อไปนี้
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 ทำงานอยู่ใน subprocess แล้ว ซึ่งหมายความว่ากระบวนการแต่ละตัวจะสร้างการเชื่อมต่อ MySQL ของตัวเองด้วย ทำให้ไม่มีปัญหาเรื่องการแชร์การเชื่อมต่อ ข้อดีอีกอย่างของวิธีนี้คือสนับสนุนการโหลดโค้ดธุรกิจใหม่ เนื่องจาก MyClass.php จะถูกโหลดใน subprocess ดังนั้นตามกฎการโหลดใหม่ หากมีการเปลี่ยนแปลงใน MyClass.php ก็สามารถโหลดใหม่ได้ทันที

4. เมธอดสถิตของคลาสเป็น Callback

คลาสสถิติ 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');

// หากคลาสมี namespace จะเขียนแบบนี้
// $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 จะไม่เรียกใช้ constructor และไม่อนุญาตให้ใช้ $this ในเมธอดของคลาสสถิติ