CakePHP4 で Session 情報を DB で管理するようにする

CakePHP4 プロジェクトを作った状態だと、Session 情報の格納は php.ini の標準設定に従うことになっているので、ファイルで管理することになっていると思います。

ロードバランサー配下で負荷分散してサービス提供することを考えると、Session 情報がサーバー単位で管理されることを避ける必要があります。

memcached 等のキャッシュサーバーを使う方法もありますが、大規模なサービスでなければそこまでする必要はないと思っています。
まずは DB で Session 情報を管理して、レスポンス等の問題が発生したらキャッシュサーバーへ変更するのが、程よい妥協点です。

僕の経験上ですが DB Session 情報管理でレスポンスの問題が発生するのはかなりレアです。
ステートレスを意識している現状だと、Session に格納する情報なんてログインユーザーや CSRF とかのセキュリティに関する情報程度ですしね。


# この記事でわかること

- CakePHP4 で Session 情報を DB へ格納するようにします。
- この記事内のソースは以下で公開しています。
    - https://github.com/katsuhiko/cakephp-vue-study


# 参考

- https://book.cakephp.org/4/en/development/sessions.html#database-sessions


# 事前準備

- CakePHP4 プロジェクトは以下の記事で作成して、Docker で動くようにしています。
    - [CakePHP4 を docker-compose で動くようにする](https://qiita.com/katsuhiko/items/f18c488457af33abdf66)
- docker-compose を実行し、コンテナを立ち上げます。

```sh
docker-compose up -d
```

|  | バージョン |
|:-----------------|:------------------|
| docker           | Docker version 19.03.8, build afacb8b |
| docker-compose   | docker-compose version 1.25.4, build 8d51620a |
| CakePHP4         | 4.0.4 |


# マイグレーションファイルの作成

CakeBook では、用意している SQL ファイルを実行して ` sessions ` テーブルを作成することになっていますが 、これだと開発者への作業手順が増えてしまうのでマイグレーションを使って ` sessions ` テーブルを作成します。

コンテナを経由して bake を実行し、マイグレーションファイルのテンプレートを作成します。

```sh
docker exec -it app bin/cake bake migration CreateSessions
```

change メソッドを ` ./config/schema/sessions.sql ` と同じ内容を記載します。

```php:config/Migrations/yyyymmddhhmiss_CreateSessions.php
class CreateSessions extends AbstractMigration
{
    /**
     * Change Method.
     *
     * More information on this method is available here:
     * http://docs.phinx.org/en/latest/migrations.html#the-change-method
     * @return void
     */
    public function change()
    {
        $this->table('sessions', ['id' => false, 'primary_key' => ['id']])
            ->addColumn('id', 'char', [
                'default' => null,
                'limit' => 40,
                'null' => false,
            ])
            ->addColumn('data', 'blob', [
                'default' => null,
                'limit' => null,
                'null' => true,
            ])
            ->addColumn('expires', 'integer', [
                'default' => null,
                'limit' => 10,
                'null' => false,
                'signed' => false,
            ])
            ->addColumn('created', 'datetime', [
                'default' => 'CURRENT_TIMESTAMP',
                'limit' => null,
                'null' => true,
            ])
            ->addColumn('modified', 'datetime', [
                'default' => 'CURRENT_TIMESTAMP',
                'update' => 'CURRENT_TIMESTAMP',
                'limit' => null,
                'null' => true,
            ])
            ->create();
    }
}
```


# マイグレーションの実行

Sessions テーブルのマイグレーションファイルを DB へ適用します。

```sh
docker exec -it app bin/cake migrations migrate
```


# Session 管理方法を DB へ変更

` ./config/app.php ` の Session 設定の記述を変更します。

```php:config/app.php
    'Session' => [
        'defaults' => 'database',
        'cookie' => env('APP_NAME'),
        'timeout' => 1440, // 1day(単位：分)
        'ini' => [
            'session.cookie_secure' => filter_var(env('SERVER', false), FILTER_VALIDATE_BOOLEAN),
            'session.cookie_path' => '/',
        ]
    ],
```

本当は、defaults のみの変更で良いのですが、他も変更しています。
この Session の設定って意外とややこしい印象があります。毎回、ググって最適な設定を探しているイメージです。
今回は最低限として、上記のような設定にしました。

` cookie ` で cookie 名を指定しています。自分のアプリの Cookie だっということを明示したいです。

` timeout ` は、アプリの要件次第ですが、これが一番クセモノだと思っています。これだけで本当に効いているのか毎回うなりながら確認している気がします。

` session.cookie_secure ` は必須です。https 環境下では、必ず Secure フラグを ON にする必要があります。今どきであれば、Secure フラグが OFF なのはローカル環境ぐらいだと思います。ローカル環境も https 経由にすれば、常に ON で良いと思います。

` session.cookie_path ` はデフォルトが "/" なので指定不要な気もしますが、明示したくて記載しています。アプリをパスで切り分けて提供する環境ではとても重要な設定です。


# 動作確認

Session を使うと sessions テーブルへレコードが追加れます。
暫定で、PagesController へ Session への書き込みの記述を追加して確認します。

```php:src/Controller/PagesController.php
    public function index(): ?Response
    {
        // 試しに Session へ書き込む
        $this->getRequest()->getSession()->write('key', 'value');

        return $this->render();
    }
```

これで http://localhost へアクセスすると sessions テーブルへレコードが追加されていることを確認できます。


# 補足) schema-dump-default.lock の管理について

schema-dump-default.lock はコンフリクトの原因になるので、コミット対象から外しています。
` bin/cake bake migration_diff ` とかを使ったことがないので、コミット対象から外したことで困ったことはありません。

```:.gitignore
# ... いろいろな記述
# Migration
schema-dump-default.lock
# ... いろいろな記述
```


# 補足) マイグレーションファイル名について

極力 CakePHP のルールに従えば良いと思うのですが、まぁ、あくまで「極力」だと思います。
作っているといろいろあるので、従わないほうがわかりやすいときもあると思います。

- マイグレーションファイル名のパターン
    - https://book.cakephp.org/migrations/3/en/index.html#migrations-file-name

| パターン | 説明 | 例 |
| --- | --- | --- |
| (/^(Create)(.*)/)           | テーブル作成 | CreateTasks                |
| (/^(Drop)(.*)/)             | テーブル削除 | DropTasks                  |
| (/^(Add).*(?:To)(.*)/)      | カラム追加   | AddDescriptionToTasks      |
| (/^(Remove).*(?:From)(.*)/) | カラム削除   | RemoveDescriptionFromTasks |
| (/^(Alter)(.*)/)            | テーブル変更 | AlterTasks                 |
| (/^(Alter).*(?:On)(.*)/)    | カラム変更   | AlterDescriptionOnTasks    |


# 補足) change メソッドで使えるコマンド

change / up / down のどのメソッドを使えば良いか悩むときは多々あると思うので、参考をのせます。

- change メソッドで使えるコマンド
    - https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method

```
createTable
renameTable
addColumn
renameColumn
addIndex
addForeignKey
```

僕自身があまり ` bin/cake migrations rollback ` を使わなくて、 ` bin/cake migrations rollback ` で正しく戻ることの検証もあんまりしないんですよね、、、
なので、 down メソッドが空っというのは結構見ます。

Migration のテストって書いている人いるんでしょうか！？
しっかりした開発だとそこもきっちり検証するんでしょう！！