【新規プロジェクト開発環境】

はじめに

新規プロジェクトで開発を行う場合は、ある程度のソケット通信の知識が必要になりますのであらかじめご了承ください。
また▶アーキテクチャのページをご覧になった上で進めていただいた方が用語の理解も含めてより効率的に進められます。

ここではデモ版のソースを例に挙げながらphp workerコマンドを使って新規のクラスを作っていきます。
デモ版のソースを参考にしつつ、ある程度処理の流れを掴んでから進めていくのがスムーズかと思います。

インストール

以下のコマンドでインストールできます。
※GitHubから直接ダウンロードする場合は>> こちらからどうぞ。
新規プロジェクト開発環境のインストール
> composer create-project socket-manager/new-project <インストール先のディレクトリ名>
                    

インストールが完了すると以下のディレクトリ構成になります。
プロジェクトの構成
/app
    /InitClass          初期化クラス
    /UnitParameter      UNITパラメータクラス
    /ProtocolUnits      プロトコルUNIT定義クラス
    /CommandUnits       コマンドUNIT定義クラス
    /MainClass          メイン処理クラス
/logs                   ログ出力用
/setting                設定ファイル用
                    

php workerコマンドを実行すると以下のようなUsageが表示されます。
Usage表示
> php worker

SOCKET-MANAGER Framework 1.0.0

Usage:
  command [arguments]

 main
  Empty...
 craft
  craft:init <初期化クラス名>                     初期化クラスの生成
  craft:parameter <UNITパラメータクラス名>        UNITパラメータクラスの生成
  craft:protocol <プロトコルUNIT定義のクラス名>   プロトコルUNIT定義のクラスとステータス名Enumの生成
  craft:command <コマンドUNIT定義のクラス名>      コマンドUNIT定義のクラスとキュー/ステータス名Enumの生成
  craft:main <メイン処理のクラス名>               メイン処理クラスの生成
  craft:setting <設定ファイル名>                  設定ファイルの生成
  craft:locale <メッセージファイル名>             メッセージファイルの生成
                    
laravel:commandコマンドに関してはLaravel環境でしか表示されません。

それでは動作確認のため、以下のコマンドを実行してメイン処理クラスを作成します。
今回はMainForTestという名前で作成します。

以下のように表示されれば成功です。
MainForTestクラス作成
> php worker craft:main MainForTest

[success] メイン処理クラスの生成に成功しました (MainForTest)
                    

再度php workerを実行してみます。
Usage表示
> php worker

SOCKET-MANAGER Framework 1.0.0

Usage:
  command [arguments]

 main
  app:main-for-test                              Command description
 craft
  craft:init <初期化クラス名>                     初期化クラスの生成
  craft:parameter <UNITパラメータクラス名>        UNITパラメータクラスの生成
  craft:protocol <プロトコルUNIT定義のクラス名>   プロトコルUNIT定義のクラスとステータス名Enumの生成
  craft:command <コマンドUNIT定義のクラス名>      コマンドUNIT定義のクラスとキュー/ステータス名Enumの生成
  craft:main <メイン処理のクラス名>               メイン処理クラスの生成
  craft:setting <設定ファイル名>                  設定ファイルの生成
  craft:locale <メッセージファイル名>             メッセージファイルの生成
                    

アプリ名main-for-testという名前で登録されている事が確認できます。
まずは以下のコマンドを実行して今回作成したサーバーを起動した状態にしてください。
サーバー起動
> php worker app:main-for-test 10000
                    

その後PowerShellなどで以下のようにnetstatコマンドを実行して10000ポートがListenされている事が確認できれば正常にインストールされています。
Listenポート確認
> netstat -ano | Select-String -Pattern "127.0.0.1:10000"

  TCP         127.0.0.1:10000        0.0.0.0:0              LISTENING       21536
                    

以降の説明ではデモの中で一番オーソドックスなWebsocket版チャットサーバーを例に取り上げて話を進めます。

初期化クラスの実装

初期化クラスのソースは以下のコマンドで作成できます。
InitForTestという名前で作成する場合、以下のように表示されれば成功です。
初期化クラスの作成
> php worker craft:init InitForTest

[success] 初期化クラスの生成に成功しました (InitForTest)
                    
生成されるファイル
/app
    /InitClass
        InitForTest.php
                    

それでは以下のWebsocket版チャットサーバーのソースをご覧ください。
app/InitClass/InitForWebsocket.php
class InitForWebsocket implements IInitSocketManager
{
    .
    .
    .
    /**
     * ログライターの取得
     * 
     * nullを返す場合は無効化(但し、ライブラリ内部で出力されているエラーメッセージも出力されない)
     * 
     * @return mixed "function(string $p_level, array $p_param): void" or null(ログ出力なし)
     */
    public function getLogWriter()
    {
        return function(string $p_level, array $p_param)
        {
            .
            .
            .
        };
    }

    /**
     * シリアライザーの取得
     * 
     * nullを返す場合は無効化となる。
     * エラー発生時はUnitExceptionクラスで例外をスローして切断する。
     * 
     * @return mixed "function(mixed $p_data): mixed" or null(変更なし)
     */
    public function getSerializer()
    {
        return function($p_data)
        {
            .
            .
            .
        };
    }

    /**
     * アンシリアライザーの取得
     * 
     * nullを返す場合は無効化となる。
     * エラー発生時はUnitExceptionクラスで例外をスローして切断する。
     * 
     * @return mixed "function(mixed $p_data): mixed" or null(変更なし)
     */
    public function getUnserializer()
    {
        return function($p_data)
        {
            .
            .
            .
        };
    }

    /**
     * コマンドディスパッチャーの取得
     * 
     * 受信データからコマンドを解析して返す
     * 
     * コマンドUNIT実行中に受信データが溜まっていた場合でもコマンドUNITの処理が完了するまで
     * 待ってから起動されるため処理競合の調停役を兼ねる
     * 
     * nullを返す場合は無効化となる。エラー発生時はUnitExceptionクラスで例外をスローして切断する。
     * 
     * @return mixed "function(SocketManagerParameter $p_param, mixed $p_dat): ?string" or null(変更なし)
     */
    public function getCommandDispatcher()
    {
        return function(ParameterForWebsocket $p_param, $p_dat): ?string
        {
            .
            .
            .
        };
    }

    /**
     * 緊急停止時のコールバックの取得
     * 
     * 例外等の緊急切断時に実行される。nullを返す場合は無効化となる。
     * 
     * @return mixed "function(SocketManagerParameter $p_param)"
     */
    public function getEmergencyCallback()
    {
        return function(ParameterForWebsocket $p_param)
        {
            .
            .
            .
        };
    }

    /**
     * UNITパラメータインスタンスの取得
     * 
     * nullの場合はSocketManagerParameterのインスタンスが適用される
     * 
     * @return ?SocketManagerParameter SocketManagerParameterクラスのインスタンス(※1)
     * @see:RETURN (※1)当該クラス、あるいは当該クラスを継承したクラスも指定可
     */
    public function getUnitParameter(): ?SocketManagerParameter
    {
        .
        .
        .
    }
}
                    

ここで必須になる実装は大きく分けると以下の7点になります。
各々のメソッドはnullを返す事で無効化できます。
■IInitSocketManagerのインプリメント
ライブラリが以下に記載のハンドラーの取り込み時に利用する。
■getLogWriterメソッドの実装
ライブラリ内でのログ出力に利用。
ハンドラーにする事でerror_log関数やフレームワークのLogger等の利用が可能。
■getSerializerメソッドの実装
送受信スタック内ペイロードデータのシリアライズ時に利用する。
今回のデモではJSONエンコード時に利用。
■getUnserializerメソッドの実装
送受信スタック内ペイロードデータのアンシリアライズ時に利用する。
今回のデモではJSONデコード時に利用。
■getCommandDispatcherメソッドの実装
クライアントから受信したペイロード部を解析して対応するコマンドキュー名を返す。
■getEmergencyCallbackメソッドの実装
相手先による切断・コマンドディスパッチャーでの例外発生・アライブチェックタイムアウトなどの緊急停止時にコールされる。
■getUnitParameterメソッドの実装
UNITパラメータとして利用される。グローバルエリアの管理が必要ないのであればSocketManagerParameterクラスをそのまま使っても構わない。

プロトコル部の実装

プロトコル部のソースは以下のコマンドで作成できます。
ProtocolForTestという名前で作成する場合、以下のように表示されれば成功です。
プロトコル部の生成
> php worker craft:protocol ProtocolForTest

[success] プロトコルUNITクラスの生成に成功しました (ProtocolForTest)
[success] プロトコルUNITのキュー名Enumの生成に成功しました (ProtocolForTestQueueEnum)
[success] プロトコルUNITのステータス名Enumの生成に成功しました (ProtocolForTestStatusEnum)
                    
生成されるファイル
/app
    /ProtocolUnits
        ProtocolForTest.php
        ProtocolForTestQueueEnum.php
        ProtocolForTestStatusEnum.php
                    

この部分の実装は主にソケットプロトコルに関係する送受信の処理を行うところで、実際の通信処理はこのプロトコル部で行います。
それでは以下のWebsocket版チャットサーバーのソースをご覧ください。
Websocket版チャットサーバーのソース:app/ProtocolUnits/ProtocolForWebsocket.php
class ProtocolForWebsocket implements IEntryUnits
{
    // キューリスト
    protected const QUEUE_LIST = [
        ProtocolQueueEnum::ACCEPT->value,   // アクセプトを処理するキュー
        ProtocolQueueEnum::RECV->value,     // 受信処理のキュー
        ProtocolQueueEnum::SEND->value,     // 送信処理のキュー
        ProtocolQueueEnum::CLOSE->value,    // 切断処理のキュー
        ProtocolQueueEnum::ALIVE->value     // アライブチェック処理のキュー
    ];

    /**
     * キューリストの取得
     * 
     * @return array キュー名のリスト
     */
    public function getQueueList(): array
    {
        return self::QUEUE_LIST;
    }

    /**
     * ステータスUNITリストの取得
     * 
     * @param string $p_que キュー名
     * @return array キュー名に対応するUNITリスト
     */
    public function getUnitList(string $p_que): array
    {
        $ret = [];

        if($p_que === ProtocolQueueEnum::ACCEPT->value)
        {
            $ret[] = [
                'status' => ProtocolStatusEnumForWebsocket::START->value,
                'unit' => $this->getAcceptStart()
            ];
            $ret[] = [
                'status' => ProtocolStatusEnumForWebsocket::CREATE->value,
                'unit' => $this->getAcceptCreate()
            ];
            $ret[] = [
                'status' => ProtocolStatusEnumForWebsocket::SEND->value,
                'unit' => $this->getAcceptSend()
            ];
        }
        .
        .
        .
        return $ret;
    }
    .
    .
    .
    /**
     * ステータス名: START
     * 
     * 処理名:受信
     * 
     * @param ParameterForWebsocket $p_param UNITパラメータ
     * @return ?string 遷移先のステータス名
     */
    protected function getAcceptStart()
    {
        return function(ParameterForWebsocket $p_param): ?string
        {
            .
            .
            .
        };
    }
    .
    .
    .
}
                    

ここで必須になる実装は大きく分けると以下の4点になります。
■IEntryUnitsのインプリメント
ライブラリがプロトコル部の取り込み時に利用する
■getQueueListメソッドの実装
プロトコル部で利用するキューのリストを返す
■getUnitListメソッドの実装
引数のキュー名に対応するUNITのリストを返す
■各UNITの実装
getUnitListメソッドで返している定義済みのUNIT処理を実装
UNIT本体の処理は上記の場合getAcceptStartメソッドでクロージャとして返していますが、グローバル関数名を指定する事も可能です。

※プロトコル部で利用するキュー名は全てライブラリ側のProtocolQueueEnumクラスで予約されています。
プロトコル部の予約済Enum定義:ProtocolQueueEnum.php
enum ProtocolQueueEnum: string
{
    /**
     * @var アクセプト時のキュー名
     */
    case ACCEPT = 'accept';

    /**
     * @var コネクション時のキュー名
     */
    case CONNECT = 'connect';

    /**
     * @var 受信時のキュー名
     */
    case RECV = 'recv';

    /**
     * @var 送信時のキュー名
     */
    case SEND = 'send';

    /**
     * @var 切断時のキュー名
     */
    case CLOSE = 'close';

    /**
     * @var アライブチェック時のキュー名
     */
    case ALIVE = 'alive';
}
                    

必ずしも全てのキュー名を使用する必要はありませんがサーバーサイドのWebsocketの規約ではCONNECTを除いて全てルール化されています。

コマンド部の実装

コマンド部のソースは以下のコマンドで作成できます。
CommandForTestという名前で作成する場合、以下のように表示されれば成功です。
コマンド部の生成
> php worker craft:command CommandForTest

[success] コマンドUNITクラスの生成に成功しました (CommandForTest)
[success] コマンドUNITのキュー名Enumの生成に成功しました (CommandForTestQueueEnum)     
[success] コマンドUNITのステータス名Enumの生成に成功しました (CommandForTestStatusEnum)
                    
生成されるファイル
/app
    /CommandUnits
        CommandForTest.php
        CommandForTestQueueEnum.php
        CommandForTestStatusEnum.php
                    

プロトコル部が通信を担う部分であるのに対して、コマンド部ではクライアント側とコマンド単位でのデータのやり取りを行って、コマンドの解釈とその内容に応じたレスポンスを返す部分になります。

まずは以下のWebsocket版チャットサーバーのソースをご覧ください。
Websocket版チャットサーバーのソース:app/CommandUnits/CommandForWebsocket.php
/**
 * コマンドUNIT登録クラス
 * 
 * IEntryUnitsインタフェースをインプリメントする
 */
class CommandForWebsocket implements IEntryUnits
{
    // キューリスト
    protected const QUEUE_LIST = [
        CommandQueueEnumForWebsocket::ENTRANCE->value,         // entranceコマンドを処理するキュー
        CommandQueueEnumForWebsocket::MESSAGE->value,          // messageコマンドを処理するキュー
        CommandQueueEnumForWebsocket::EXIT->value,             // exitコマンドを処理するキュー
        CommandQueueEnumForWebsocket::CLOSE->value,            // closeコマンドを処理するキュー
        CommandQueueEnumForWebsocket::PRIVATE->value,          // privateコマンドを処理するキュー
        CommandQueueEnumForWebsocket::PRIVATE_RESULT->value,   // private-resultコマンドを処理するキュー
        CommandQueueEnumForWebsocket::USERSEARCH_RESULT->value // usersearch-resultコマンドを処理するキュー
    ];

    /**
     * キューリストの取得
     * 
     * @return array キュー名のリスト
     */
    public function getQueueList(): array
    {
        return static::QUEUE_LIST;
    }

    /**
     * ステータスUNITリストの取得
     * 
     * @param string $p_que キュー名
     * @return array キュー名に対応するUNITリスト
     */
    public function getUnitList(string $p_que): array
    {
        $ret = [];

        if($p_que === CommandQueueEnumForWebsocket::ENTRANCE->value)
        {
            $ret[] = [
                'status' => CommandStatusEnumForWebsocket::START->value,
                'unit' => $this->getEntranceStart()
            ];
        }
        .
        .
        .
    }
    .
    .
    .
    /**
     * ステータス名: START
     * 
     * 処理名:入室処理開始
     * 
     * @param ParameterForWebsocket $p_param UNITパラメータ
     * @return ?string 遷移先のステータス名
     */
    protected function getEntranceStart()
    {
        return function(ParameterForWebsocket $p_param): ?string
        {
            .
            .
            .
        }
    }
    .
    .
    .
}
                    

構成内容はプロトコル部とほぼ同じで、必須になる実装も大きく分けると以下の4点になります。
■IEntryUnitsのインプリメント
ライブラリがコマンド部の取り込み時に利用する
■getQueueListメソッドの実装
コマンド部で利用するキューのリストを返す
■getUnitListメソッドの実装
引数のキュー名に対応するUNITのリストを返す
■各UNITの実装
getUnitListメソッドで返している定義済みのUNIT処理を実装
UNIT本体の処理は上記の場合getEntranceStartメソッドでクロージャとして返していますが、グローバル関数名を指定する事も可能です。

※コマンド部で利用するキュー名は自由定義です。特に予約されているものもありません。

予約済ステータス

予約されているものは以下ライブラリ側のEnum定義のみです。
予約済Enum定義:StatusEnum.php
enum StatusEnum: string
{
    /**
     * @var UNITの処理開始時のステータス名
     */
    case START = 'start';
}
                    

プロトコル/コマンド部共通で利用するSTART(処理開始)のみになります。

UNITパラメータの実装

UNITパラメータクラスのソースは以下のコマンドで作成できます。
ParameterForTestという名前で作成する場合、以下のように表示されれば成功です。
UNITパラメータクラスの作成
> php worker craft:parameter ParameterForTest

[success] UNITパラメータクラスの生成に成功しました (ParameterForTest)
                    
生成されるファイル
/app
    /UnitParameter
        ParameterForTest.php
                    

ここで述べているUNITパラメータというのはプロトコル部やコマンド部で定義されているUNITの引数の部分です。
上記のプロトコル部やコマンド部の例でいえばParameterForWebsocket $p_paramの部分になります。
この引数にはSocketManagerParameterクラスを継承しているものであれば何を指定しても構いません。

グローバルエリアを使う必要がなければ特に実装する事は何もありませんが、新規で作成した場合は以下のようにUNIT処理の引数の部分を必要に応じて置き替えてください。
修正前:黄色部分(プロトコル部のデフォルト)
protected function getAcceptStart()
{
    return function(SocketManagerParameter $p_param): ?string
    {
        $p_param->logWriter('debug', ['ACCEPT' => 'START']);

        return null;
    };
}
                    
修正後:赤色部分(新規で作成したクラス)
protected function getAcceptStart()
{
    return function(ParameterForTest $p_param): ?string
    {
        $p_param->logWriter('debug', ['ACCEPT' => 'START']);

        return null;
    };
}
                    

メイン処理クラスの実装

メイン処理クラスのソースは以下のコマンドで作成できます。
MainForTestという名前で作成する場合、以下のように表示されれば成功です。
メイン処理クラスの作成
> php worker craft:main MainForTest

[success] メイン処理クラスの生成に成功しました (MainForTest)
                    
生成されるファイル
/app
    /MainClass
        MainForTest.php
                    

作成されたソースは次の通り。
app/MainClass/MainForTest.php
class MainForTest extends Console
{
    /**
     * @var string $identifer サーバー識別子
     */
    protected string $identifer = 'app:main-for-test {port_no?}';

    /**
     * @var string $description コマンド説明
     */
    protected string $description = 'Command description';


    /**
     * サーバー起動
     * 
     */
    public function exec()
    {
        // 引数の取得
        $port_no = $this->getParameter('port_no');

        // ソケットマネージャーのインスタンス設定
        $manager = new SocketManager('localhost', $port_no);

        /***********************************************************************
         * ソケットマネージャーの初期設定
         * 
         * プロトコル/コマンド部等で実装したクラスのインスタンスをここで設定します
         **********************************************************************/

        /**
         * 初期化クラスの設定
         * 
         * $manager->setInitSocketManager()メソッドで初期化クラスを設定します
         */

        /**
         * プロトコルUNITの設定
         * 
         * $manager->setProtocolUnits()メソッドでプロトコルUNITクラスを設定します
         */

        /**
         * コマンドUNITの設定
         * 
         * $manager->setCommandUnits()メソッドでコマンドUNITクラスを設定します
         */

        /***********************************************************************
         * ソケットマネージャーの実行
         * 
         * ポートの待ち受け処理や周期ドリブン処理を実行します
         **********************************************************************/

        // リッスンポートで待ち受ける
        $ret = $manager->listen();
        if($ret === false)
        {
            goto finish;   // リッスン失敗
        }

        // ノンブロッキングループ
        while(true)
        {
            // 周期ドリブン
            $ret = $manager->cycleDriven();
            if($ret === false)
            {
                goto finish;
            }
        }

finish:
        // 全接続クローズ
        $manager->shutdownAll();
    }
}
                    

ここで必須になる実装は初期設定ブロックである以下の3点になります。
■初期化クラスの設定
初期化クラスのインスタンスを生成し、SocketManagerクラスのsetInitSocketManagerメソッドにインスタンスを引き渡す
■プロトコルUNITの設定
プロトコルUNITクラスのインスタンスを生成し、SocketManagerクラスのsetProtocolUnitsメソッドにインスタンスを引き渡す
■コマンドUNITの設定
コマンドUNITクラスのインスタンスを生成し、SocketManagerクラスのsetCommandUnitsメソッドにインスタンスを引き渡す

設定ファイルの作成

設定ファイルは以下のコマンドで作成できます。
testという名前で作成する場合、以下のように表示されれば成功です。
設定ファイルの作成
> php worker craft:setting test

[success] 設定ファイルの生成に成功しました (test)
                    
生成されるファイル
/setting
    test.php
                    

作成されたソースは次の通り、空の配列のリターン値になります。
setting/test.php
return [

];
                    

使い方

設定ファイル内の設定値を取得するヘルパー関数はLaravelと同様に使えるようにしています。
ここではtest_keyという名前の設定名を使って値を取得する場合を例に挙げます。

設定ファイルで以下のように連想配列を定義していたとします。
setting/test.php
return [
    'test_key' => 100
];
                    

あとはプログラムの方でconfigヘルパー関数を使って以下のように取得するだけです。
configヘルパー関数で値を取得
$value = config('test.test_key', null);
                    

変数$valueには100を返します。
関数の第一引数にはファイル名を含めたキー名をピリオド区切りで指定します。第二引数には値が取得できなかった場合のデフォルト値を指定します。

メッセージファイルの作成

メッセージ管理のコマンドはsetting/app.php内のlocale設定項目と連動します。
例えばlocaleの項目がjaの場合の挙動は以下の通りです。
メッセージファイルの作成
> php worker craft:locale test

[success] メッセージファイルの生成に成功しました (test)
                    
生成されるファイル
/locale
    /ja
        test.php
                    

test.phpのファイルはjaのサブディレクトリに格納されます。
作成されたソースは次の通り、空の配列のリターン値になります。
locale/ja/test.php
return [

];
                    

使い方

メッセージファイル内のメッセージを取得するヘルパー関数はLaravelと同様に使えるようにしています。
ここではtest_keyという名前の設定名を使って値を取得する場合を例に挙げます。

メッセージファイルで以下のように連想配列を定義していたとします。
locale/ja/test.php
return [
    'test_key' => 'テストメッセージ'
];
                    

あとはプログラムの方で__ヘルパー関数を使って以下のように取得するだけです。
__ヘルパー関数でメッセージを取得
$value = __('test.test_key');
                    

変数$valueには「テストメッセージ」という文字列を返します。

また、以下のようにプレースホルダも使えます。
locale/ja/test.php
return [
    'test_key' => '私の名前は:nameです。年齢は:age歳です。'
];
                    
プレースホルダを使ってメッセージを取得
$value = __('test.test_key', ['name' => '山田太郎', 'age' => 20]);
                    

変数$valueには「私の名前は山田太郎です。年齢は20歳です。」という文字列を返します。

おわりに

動作確認をしながら進めるのであれば以下の順に進めるのが効率的かと思います。
1.初期化クラスの実装
2.メイン処理クラスの実装
3.プロトコル部の実装
4.コマンド部の実装
最初に接続先とやり取りする通信データの内容さえ決まってしまえば、シリアライザーやコマンドディスパッチャー等の作成後、初期化クラスを触る事はほとんどないと思います。
そしてプロトコル部とコマンド部のクラスは初期状態のままでいいのでメイン処理クラスで設定だけ済ませておいた上でプロトコル部、あるいはコマンド部のUNITを作成するたびに動作確認していくのが順当かと思います。

UNITパラメータクラスの方はUNIT処理の作成中にグローバルエリアの管理が必要になった時に実装を進める形でよいかと思います。