【コマンドUNITクラスの実装】

はじめに

このクラスでは、クライアントからのリクエストやサーバーの通知から始まる一連の流れを1つのコマンド処理とみなし、それをサーバーコンテンツを構成する1つの要素としてイベントハンドラを構成していきます。
そしてプロトコルUNITクラスと同様に、イベントハンドラをキューとUNITという単位に分けたモデルで専用のコマンドを使ってスキャルフォールディングできます。
プロトコルUNITクラスとは異なり、予約されているキューはありませんので、実装するイベントに合わせてキューとUNITを自由に構成できます。

※キューとUNITの詳細については▶イベントハンドラについてのページを参照。

コマンドUNITクラスを生成するコマンドは以下の通り。
> php worker craft:command CommandForTest

[success] コマンドUNITクラスの生成に成功しました (CommandForTest)
[success] コマンドUNITのキュー名Enumの生成に成功しました (CommandForTestQueueEnum)     
[success] コマンドUNITのステータス名Enumの生成に成功しました (CommandForTestStatusEnum)
                    

コマンドを実行する事でapp/CommandUnitsの場所に以下3つのファイルが生成されます。
・CommandForTest
IEntryUnitsインターフェイスがimplementsされたコマンドUNITクラスです。
ここにコマンド処理のイベントハンドラを実装します。
・CommandForTestQueueEnum
ここにコマンド処理のキュー(イベント)名を定義します。
・CommandForTestStatusEnum
ここにコマンド処理のステータス(UNIT)名を定義します。
このフレームワーク環境では、キュー名とステータス名をEnumで定義する事を推奨しているので、それぞれのEnumファイルに分けて出力されます。

以降では生成されたファイルの内容を見ていきます。

キュー名の定義

CommandForTestQueueEnum.phpのファイルは、イベントに対応するキュー名を定義するEnumファイルです。
これらのキュー名はコマンドUNITクラスで定義されるイベントごとのUNITの集合を特定するためのものです。
今回のケースではCommandForTestクラス内でキューとUNITの紐づけを行います。
紐づけの方法は以下の>> コマンドUNITクラスの実装の項で説明しています。

ステータス名の定義

CommandForTestStatusEnum.phpのファイルは、コマンドUNITのステータス名を定義するEnumファイルです。
コマンドUNITのステータス名であらかじめ予約されているものはStatusEnum::STARTだけで、生成されたEnumファイルにはその内容が代入されています。
それ以外のステータス名は自由に定義する事ができます。
定義済みのステータス名は以下の通り。
CommandForTestStatusEnum::START
同じキューに登録されているUNITの集合のうち一番最初に実行されるステータスです。
UNITは必ずSTARTステータスから始まるルールになっています。
これらのステータス名はコマンドUNITクラスで定義したUNITに紐づくものなので、今回のケースでは先ほど生成したCommandForTestクラス内でUNITとステータス名の紐づけを行います。
紐づけの方法は以下の>> コマンドUNITクラスの実装の項で説明しています。

※ここで定義したステータス名は異なるキューで再利用が可能です。

コマンドUNITクラスの実装

CommandForTest.phpのファイルにはUNIT定義を含めた各種メソッドが実装されています。
各メソッドの仕様と実装例は以下の通り。

➤キューリストの取得

フレームワークは以下のメソッドをコールして必要なキューのリストを登録します。
【メソッド】getQueueList(): array

【パラメータ】なし

【戻り値】array - キュー名のリスト
                    

例えば▶イベントハンドラについてのページでご紹介したチャットサーバーを例に挙げると、以下のように実装します。
protected const QUEUE_LIST = [
    CommandForTestQueueEnum::CHAT_MESSAGE->value,       // チャットメッセージを処理するキュー
    CommandForTestQueueEnum::PRIVATE_MESSAGE->value     // プライベートメッセージを処理するキュー
];
public function getQueueList(): array
{
    return (array)static::QUEUE_LIST;
}
                    


➤ステータスUNITリストの取得

以下のメソッドがフレームワーク内部でコールされる事によって、キューとUNITの紐づけが登録されます。
【メソッド】getUnitList(string $p_que): array

【パラメータ】
    $p_que - string - 必須 - キュー名

【戻り値】array - キューごとのUNIT集合のリスト(連想配列)
                    

フレームワーク内部ではgetQueueListメソッドで取得したキュー名を元に、getUnitListメソッドがコールされるため、引数にはキュー名が渡されます。
例えばCHAT_MESSAGEキューとPRIVATE_MESSAGEキューにそれぞれ2つずつUNITの集合を登録する場合、以下のように引数で与えられたキュー名に対応するリストを返す必要があります。
public function getUnitList(string $p_que): array
{
    $ret = [];

    // CHAT_MESSAGEキューのUNIT集合を登録
    if($p_que === CommandForTestQueueEnum::CHAT_MESSAGE->value)
    {
        // STARTステータスとUNIT(getLogWrite)の紐づけ
        $ret[] = [
            'status' => CommandForTestStatusEnum::START->value,
            'unit' => $this->getLogWrite()
        ];
        // SENDステータスとUNIT(getChatSend)の紐づけ
        $ret[] = [
            'status' => CommandForTestStatusEnum::SEND->value,
            'unit' => $this->getChatSend()
        ];
    }

    // PRIVATE_MESSAGEキューのUNIT集合を登録
    if($p_que === CommandForTestQueueEnum::PRIVATE_MESSAGE->value)
    {
        // STARTステータスとUNIT(getLogWrite)の紐づけ
        $ret[] = [
            'status' => CommandForTestStatusEnum::START->value,
            'unit' => $this->getLogWrite()
        ];
        // SENDステータスとUNIT(getPrivateSend)の紐づけ
        $ret[] = [
            'status' => CommandForTestStatusEnum::SEND->value,
            'unit' => $this->getPrivateSend()
        ];
    }
    .
    .
    .
    return $ret;
}
                    

※STARTステータスで紐づけを行っているUNIT(getLogWrite)は再利用UNITとして登録しているので内容は同じものです。


➤ステータスUNITの実装

ここではgetUnitListメソッド内で紐づけたUNITを定義します。
【メソッド】<任意のメソッド名>(): Closure|string|null

【パラメータ】なし

【戻り値】
    Closure|string
    - ステータスUNITの定義: Closure
        パラメータ:
            $p_param - SocketManagerParameter - 必須 - UNITパラメータクラスのインスタンス
                各イベントハンドラで共通の引数として使用されるインスタンス。
                SocketManagerParameterクラスを継承した拡張クラスを指定する事も可能。
        戻り値: string|null
            - 遷移先がある場合: string
                遷移先のキュー名
            - 遷移先がない(終了する)場合: null
    - ステータスUNITの定義: string
        ヘルパー関数などの関数名
                    

以下ではCHAT_MESSAGEキューのUNIT集合とPRIVATE_MESSAGEキューのUNIT集合を再利用UNITを使って実装した例をご紹介します。
再利用UNITの詳細については▶イベントハンドラについての説明をご覧ください。

【チャットメッセージ】
{ "message" => <メッセージ> }

【プライベートメッセージ】
{ "message" => <メッセージ>, "cid" => <相手先クライアントの接続ID> }
                    

【CHAT_MESSAGEキューのUNIT集合】
ここでは、受信したデータをディスクリプタ(クライアント接続子)へ保存し、メッセージをログファイルへ記録する再利用UNITを経由してから自身を除く全員へ配信する例をご紹介しています。
protected function getLogWrite()
{
    return function(ParameterForTest $p_param): ?string
    {
        // 現在のキューを取得
        $que = $p_param->getQueueName();

        // 受信データを取得
        $recv_data = $p_param->getRecvData();

        // メッセージをディスクリプタへ保存
        $p_param->setTempBuff(['message_data' => $recv_data]);

        // 自身のキューがCHAT_MESSAGEかどうかを判定してからログファイルへ記録
        $key = 'private_message';   // プライベートメッセージ
        if($que === CommandForTestQueueEnum::CHAT_MESSAGE->value)
        {
            $key = 'chat_message';  // チャットメッセージ
        }
        $p_param->logWriter('info', [$key => $recv_data['message']]);

        // SENDステータス(getChatSend or getPrivateSend)のUNITへ遷移
        return CommandForTestStatusEnum::SEND->value;
    };
}
protected function getChatSend()
{
    return function(ParameterForTest $p_param): ?string
    {
        // ディスクリプタからメッセージを取得
        $message_data = $p_param->getTempBuff(['message_data']);

        // 自身を除く全員へメッセージを配信
        $p_param->setSendStackAll($message_data['message'], true);

        // nullを返して終了する
        return null;
    };
}
                            


【PRIVATE_MESSAGEキューのUNIT集合】
ここではCHAT_MESSAGEキューに登録されている再利用UNIT(getLogWrite)と同じものを使うので、プライベートメッセージを送信するUNITだけを実装しています。
protected function getPrivateSend()
{
    return function(ParameterForTest $p_param): ?string
    {
        // ディスクリプタからメッセージを取得
        $message_data = $p_param->getTempBuff(['message_data']);

        // 宛先を指定してメッセージを配信
        $p_param->setSendStack($message_data['message'], $message_data['cid']);

        // nullを返して終了する
        return null;
    };
}
                            


今回使用した$p_param->getTempBuff()$p_param->setTempBuff()のメソッドはディスクリプタ(クライアント接続子)へ対する読み込み/書き込みを行います。
つまりUNITを跨ったグローバルデータではあるものの、各クライアント個別のデータとして利用できます。

メソッド(UNIT)内で使用している$p_param->setSendStack()$p_param->setSendStackAll()を使って送信データを設定すると、送信スタックエリアを通じてプロトコルUNITに引き渡され、自動で送信されます。

※UNITパラメータクラスには▶UNITパラメータクラスのページで使用したParameterForTestを指定しています。

おわりに

生成されたクラスのインスタンスは、メイン処理クラス内で$manager->setCommandUnits()メソッドに引き渡す事で適用されます。
複数のコマンドUNITクラスやメイン処理クラスを用意している場合は、サーバーの実装内容によって最適なインスタンスを動的、あるいは静的に適用する事で柔軟なサーバー構築が可能になります。