SOCKET-MANAGERを使った自作サーバー開発の手引き＜初期化クラスの実装＞

# はじめに

初期化クラスは、サーバー起動時に必要なデータ（UNITパラメータインスタンス）や処理（ログライター、シリアライザー／アンシリアライザー、コマンドディスパッチャー、緊急停止コールバック）を読み込むための実装を行うところで、専用のコマンドを使ってスキャフォールディングできます。

初期化クラスを生成するコマンドは以下の通り。

```terminal:コマンド実行（InitForTestというクラス名を指定する場合）
> php worker craft:init InitForTest

[success] 初期化クラスの生成に成功しました (InitForTest)
```

コマンドを実行する事で`app/InitClass`の場所に`InitForTest`というクラス名で`IInitSocketManager`インターフェイスをimplementsした初期化クラスが生成されます。

以降では生成されたクラス内で実装が必要なインターフェイスメソッドの内訳を見ていきます。


# ➤ログライター

error_logの標準関数やLoggerなどの有名なライブラリを使ってログを出力する事ができます。
イベントハンドラ（UNIT）内ではパラメータ経由で使いますので
`$param->logWriter('debug', ['メッセージ受信' => $message])`
のように記述します。

```text:シグネチャ
【メソッド】getLogWriter(): Closure|string|null

【パラメータ】なし

【戻り値】
    Closure|string|null
    - ログ出力を行う場合: Closure
        パラメータ:
            $p_level - string - 必須 - ログレベル
                現状では以下のレベルが定義されています。
                debug
                info
                notice
                warning
                error
            $p_param - array - 必須 - 出力内容
                連想配列の形式で以下のように指定します。
                ['メッセージ受信' => $message]
        戻り値: void
    - ログ出力を行う場合: string
        ヘルパー関数などの関数名
    - ログ出力を行わない場合: null
```

```php:チャットログ保存の実装例（Closureタイプ）
public function getLogWriter()
{
    return function(string $p_level, array $p_param)
    {
        // ファイル名を設定
        $filename = date('Ymd');
        
        // 現在日付を含むログ内容を作成
        $now = date('Y-m-d H:i:s');
        $log = $now." {$p_level} ".print_r($p_param, true)."\n";

        // ログ出力（カレントパスはプロジェクトルートになる）
        error_log($log, 3, "./logs/socket-manager-log/{$filename}.log");
    };
}
```

:::note info
カレントパスはプロジェクトルートになるので`./logs/debug.log`のように指定します。
このメソッドはフレームワークのライブラリからもコールされますので、イベントハンドラのログも含めて時系列で出力されます。
:::


# ➤シリアライザー

送信するデータをシリアライズするためのメソッドを実装します。

```text:シグネチャ
【メソッド】getSerializer(): Closure|string|null

【パラメータ】なし

【戻り値】
    Closure|string|null
    - シリアライズを行う場合: Closure
        パラメータ:
            $p_data - mixed - 必須 - 送信データ
                イベントハンドラで設定された送信データがそのまま渡されます。
        戻り値: mixed
            シリアライズ後のデータ
    - シリアライズを行う場合: string
        ヘルパー関数などの関数名
    - シリアライズを行わない場合: null
```

```php:JSONデータへシリアライズする例（Closureタイプ）
public function getSerializer()
{
    return function($p_data)
    {
        return json_encode($p_data);
    };
}
```

:::note info
シリアライザーはデータ送信時にライブラリから自動でコールされます。
Websocketサーバーとして使う場合はJSON形式で使うのがいいでしょう。
通信データ量を減らすためにバイナリデータを扱う事も可能です。
:::


# ➤アンシリアライザー

受信データを取得する時にアンシリアライズするためのメソッドを実装します。

```text:シグネチャ
【メソッド】getUnserializer(): Closure|string|null

【パラメータ】なし

【戻り値】
    Closure|string|null
    - アンシリアライズを行う場合: Closure
        パラメータ:
            $p_data - mixed - 必須 - 受信データ
                シリアライズされている受信データがそのまま渡されます。
        戻り値: mixed
            アンシリアライズ後のデータ
    - アンシリアライズを行う場合: string
        ヘルパー関数などの関数名
    - アンシリアライズを行わない場合: null
```

```php:JSONデータを連想配列へ展開する例（Closureタイプ）
public function getUnserializer()
{
    return function($p_data)
    {
        return json_decode($p_data, true);
    };
}
```

:::note info
アンシリアライザーは受信データ取得時にライブラリから自動でコールされます。
Websocketサーバーとして使う場合はJSON形式で使うのがいいでしょう。
通信データ量を減らすためにバイナリデータを扱う事も可能です。
:::


# ➤コマンドディスパッチャー

受信データを受け取った時に呼び出され、そのデータを解析した上でサーバーコンテンツ（コマンドUNIT）上の適切なイベントハンドラを呼び出すためのルーティングの役割を果たします。

コマンドディスパッチャーからは、ルーティングするキュー名を返す事でイベントハンドラを呼び出す事ができます。

```text:シグネチャ
【メソッド】getCommandDispatcher(): Closure|string|null

【パラメータ】なし

【戻り値】
    Closure|string|null
    - コマンドディスパッチャーを使う場合: Closure
        パラメータ:
            $p_param - SocketManagerParameter - 必須 - UNITパラメータクラスのインスタンス
                各イベントハンドラで共通の引数として使用されるインスタンス。
                SocketManagerParameterクラスを継承した拡張クラスを指定する事も可能。
            $p_dat - mixed - 必須 - 受信データ
                アンシリアライズ化された受信データがそのまま渡されます。
        戻り値: string
            ルーティングするキュー名
    - コマンドディスパッチャーを使う場合: string
        ヘルパー関数などの関数名
    - コマンドディスパッチャーを使わない場合: null
```

```php:受信データ内のコマンド名を使う例（Closureタイプ）
public function getCommandDispatcher()
{
    return function(SocketManagerParameter $p_param, $p_dat): ?string
    {
        // コマンド部のキュー名と一致している必要があります
        return $p_dat['command'];
    };
}
```


# ➤緊急切断コールバック

クライアントとの接続が維持できなくなった時に呼び出されるコールバックメソッドを実装します。

例えば以下の場面で呼び出されます。

<ul>
    <li>アライブチェック処理のタイムアウト</li>
    <li>コマンドディスパッチャーでの例外発生</li>
    <li>相手先による強制切断</li>
</ul>

```text:シグネチャ
【メソッド】getEmergencyCallback(): Closure|string|null

【パラメータ】なし

【戻り値】
    Closure|string|null
    - 緊急切断コールバックを使う場合: Closure
        パラメータ:
            $p_param - SocketManagerParameter - 必須 - UNITパラメータクラスのインスタンス
                各イベントハンドラで共通の引数として使用されるインスタンス。
                SocketManagerParameterクラスを継承した拡張クラスを指定する事も可能。
        戻り値: mixed
            シリアライズ後のデータ
    - 緊急切断コールバックを使う場合: string
        ヘルパー関数などの関数名
    - 緊急切断コールバックを使わない場合: null
```

```php:チャットが切断された時の例（Closureタイプ）
public function getEmergencyCallback()
{
    return function(SocketManagerParameter $p_param)
    {
        // 送信データを作成
        $send_data = [];
        $send_data['command'] = 'close';
        $send_data['message'] = '切断されました';
        $send_data['connection_id'] = $p_param->getConnectionId(); // 接続IDを取得

        // 全員へ配信（第二パラメータがtrueの場合は自身を除く）
        $p_param->setSendStackAll($send_data, true);
    };
}
```


# ➤UNITパラメータインスタンス

UNITとしてのイベントハンドラを実行する時に渡されるUNITパラメータクラスのインスタンスを指定します。

基底クラスである`SocketManagerParameter`クラスのインスタンスがデフォルトで指定されていますが、このクラスを継承した拡張クラスを使用する場合はそのインスタンスを戻り値に指定する必要があります。

ここで指定したインスタンスはそのままUNITの引数として使われますので、動的に増殖してメモリを圧迫する心配はありません。

```text:シグネチャ
【メソッド】getUnitParameter(): SocketManagerParameter|null

【パラメータ】なし

【戻り値】SocketManagerParameter|null
    - UNITパラメータクラスを使う場合: SocketManagerParameter
        各イベントハンドラで共通の引数として使用されるインスタンス。
        SocketManagerParameterクラスを継承した拡張クラスを指定する事も可能。
    - UNITパラメータクラスを使わない場合: null
```

```php:基底のUNITパラメータクラスを使う例
public function getUnitParameter(): ?SocketManagerParameter
{
    return new SocketManagerParameter();
}
```
<br />
▼UNITパラメータクラスについての詳細は以下の記事をご覧ください。

https://qiita.com/glasses_seven/items/42f8175aec51fe632d52


# おわりに

生成されたクラスのインスタンスは、メイン処理クラス内で以下のメソッドに引き渡す事で適用されます。
`$manager->setInitSocketManager()`

複数の初期化クラスやメイン処理クラスを用意している場合は、サーバーの実装内容によって最適なインスタンスを動的、あるいは静的に適用する事で柔軟なサーバー構築が可能になります。