👻

Laravelがセッションを作成してから保存するまでの流れ

に公開

はじめに

Laravelのセッションが作成されてから保存されるまでの過程を確認した時のメモです。
(忘れたときに見返す用なのでざっくりです)

シリーズ

諸々

ミドルウェアの確認

セッションの確認・判定・新規作成は主に\Illuminate\Session\Middleware\StartSession::classを起点に行われるため流れを見ていきます。

https://github.com/laravel/framework/blob/c7a87ae20172593f7dd498d9b7779b56e051fa82/src/Illuminate/Foundation/Http/Kernel.php#L96-L115

1.セッションマネージャーを作成

まずは__constructで、セッションマネージャーのインスタンスが作成されます。

https://github.com/laravel/framework/blob/c7a87ae20172593f7dd498d9b7779b56e051fa82/src/Illuminate/Session/Middleware/StartSession.php#L31-L41

2.handleメソッドが実行

handleメソッドが自動で実行されます。

https://github.com/laravel/framework/blob/c7a87ae20172593f7dd498d9b7779b56e051fa82/src/Illuminate/Session/Middleware/StartSession.php#L43-L64

3.セッションインスタンスを作成

まずはgetSessionメソッドの$this->manager->driver()でプロジェクトで指定したセッションドライバーのセッションインスタンスを作成しています。

https://github.com/laravel/framework/blob/c7a87ae20172593f7dd498d9b7779b56e051fa82/src/Illuminate/Session/Middleware/StartSession.php#L150-L161

driver()の補足

driver()メソッドは/Illuminate/Session/SessionManager.phpの継承元のIlluminate\Support\Managerのメソッドなので中身を確認。

https://github.com/laravel/framework/blob/c7a87ae20172593f7dd498d9b7779b56e051fa82/src/Illuminate/Support/Manager.php#L57-L83

$this->getDefaultDriver()で継承先のSessionManagerに移譲しているので確認。
getDefaultDriver()では開発者側で設定できるconfig/session.phpdriverの設定の文字列を見ています。

https://github.com/laravel/framework/blob/c7a87ae20172593f7dd498d9b7779b56e051fa82/src/Illuminate/Session/SessionManager.php#L269-L277

https://github.com/laravel/laravel/blob/047ed5add8cea8d36147b10cdc02a1a59c5e3495/config/session.php#L7-L21

Illuminate\Support\Manager$driverメソッドにはドライバーの文字列がセットされ、最終的にdriver()メソッドは設定したドライバーのインスタンス(/Illuminate/Session/Store.php)を返します。

(再掲)
https://github.com/laravel/framework/blob/c7a87ae20172593f7dd498d9b7779b56e051fa82/src/Illuminate/Support/Manager.php#L57-L83

4.セッションインスタンスのidプロパティをセット

getSession()に戻ります。

前のセクションで取得したセッションインスタンスはIlluminate\Session\Store型です。
(Illuminate\Contracts\Session\Sessionインターフェースを実装しているため、getSession()の戻り値のアノテーションの記載はIlluminate\Contracts\Session\Sessionになっている)

$session変数はセッションインスタンスのため、StoreクラスのsetId()メソッドを実行して自身のセッションインスタンスのidプロパティにセッションIDをセットしている。

(再掲)
https://github.com/laravel/framework/blob/c7a87ae20172593f7dd498d9b7779b56e051fa82/src/Illuminate/Session/Middleware/StartSession.php#L150-L161

※Laravelのヘルパ関数であるtap関数は第一引数を返すので、そのままセッションインスタンスを返します。

https://readouble.com/laravel/12.x/ja/helpers.html#method-tap

setId()の補足

引数で取っている$request->cookies->get($session->getName())に値がある場合、つまりブラウザからCookieで送信されたセッションIDのキーに値がある場合はその値をセットします。

nullの場合だったとしてもgenerateSessionId()メソッドで、ここで40文字のセッションIDが作成されてセットされます。

https://github.com/laravel/framework/blob/c7a87ae20172593f7dd498d9b7779b56e051fa82/src/Illuminate/Session/Store.php#L669-L699

現状の整理

続いてhandele()メソッド最後のreturn $this->handleStatefulRequest($request, $session, $next);リクエストをステートフルになる工程を確認していきます。

以降の工程は基本的にはhandleStatefulRequestメソッド内で実行される処理です。

(再掲)

https://github.com/laravel/framework/blob/c7a87ae20172593f7dd498d9b7779b56e051fa82/src/Illuminate/Session/Middleware/StartSession.php#L43-L64

https://github.com/laravel/framework/blob/c7a87ae20172593f7dd498d9b7779b56e051fa82/src/Illuminate/Session/Middleware/StartSession.php#L101-L132

5.既存のセッションデータを取得する

$sessionのidプロパティにはクッキーで送られてきたセッションIDか新規作成したセッションIDがセットされていることがわかりました。

続いて、$this->startSession($request, $session)の処理ではドライバー(DBのsessionsテーブルなど)からセッションデータを取得し、セッションインスタンスのattributesプロパティに上書きしてセットしています。

https://github.com/laravel/framework/blob/c7a87ae20172593f7dd498d9b7779b56e051fa82/src/Illuminate/Session/Middleware/StartSession.php#L134-L148

https://github.com/laravel/framework/blob/c7a87ae20172593f7dd498d9b7779b56e051fa82/src/Illuminate/Session/Store.php#L81-L95

https://github.com/laravel/framework/blob/c7a87ae20172593f7dd498d9b7779b56e051fa82/src/Illuminate/Session/Store.php#L97-L129

readFromHandler()の補足[⭐️重要]

loadSession()メソッド内のreadFromHandler()メソッドのif ($data = $this->handler->read($this->getId()))readメソッドでは、sessionsテーブルのpayloadカラムの値を取得しattributesプロパティにセットしています。

ただし、有効期限切れ、もしくはsessionsテーブルに検索対象のセッションIDが見つからない場合は''空文字が返る。
(有効期限の判定基準はlast_activityカラムが「現在時刻 - セッション有効期間(分)」よりも古いかどうか)

https://github.com/laravel/framework/blob/c7a87ae20172593f7dd498d9b7779b56e051fa82/src/Illuminate/Session/DatabaseSessionHandler.php#L89-L123

start()の補足

  • 取得してattributesプロパティにセットされたセッションデータに_tokenというキーがなければ新しくトークンを生成(CSRF対策などセキュリティ)。
  • セッションが開始されたことを示すフラグをtrueにする。

6.リクエストインスタンスにセッションデータがセットされる

5の工程のstartSession()の戻り値のセッションインスタンスを、Requestインスタンス(/Illuminate/Http/Request.php)のsessionプロパティにセットします。

https://github.com/laravel/framework/blob/c7a87ae20172593f7dd498d9b7779b56e051fa82/src/Illuminate/Http/Request.php#L587-L596

7.不要なセッションを削除

$this->collectGarbage($session);で毎回ではなく一定確率で期限切れセッションを削除して掃除される。

https://github.com/laravel/framework/blob/c7a87ae20172593f7dd498d9b7779b56e051fa82/src/Illuminate/Session/Middleware/StartSession.php#L163-L179

8.現在のURLをセッションデータに保存

$this->storeCurrentUrl($request, $session);で実施されている。
リダイレクト後の戻り先などに利用されます。

9.リクエスト処理を実行

$nextで「次のミドルウェアやコントローラーの処理が実行」されます。
($responseにはリクエスト処理を終えた結果が入ってきます)

https://zenn.dev/shun_nakamura/articles/0d3bf38cb80cf5

10.セッションを保存する

ここまでで、リクエストを受け取ってからセッションインスタンスを作成し、idをセット、既存のセッションデータがあればその値でセッションデータを上書き、現在のURLもセットし、リクエストの処理(Controller/Action)が実行までされてきましたが、まだDBに更新(または新規保存)していないので、この時点で$this->saveSession($request);の実行で保存する。

https://github.com/laravel/framework/blob/c7a87ae20172593f7dd498d9b7779b56e051fa82/src/Illuminate/Session/Middleware/StartSession.php#L235-L246

セッションID($this->getId())既存のセッションデータのpayload($this->attributes)を引数に渡してwrite()メソッドを実行している。

https://github.com/laravel/framework/blob/c7a87ae20172593f7dd498d9b7779b56e051fa82/src/Illuminate/Session/Store.php#L164-L180

write()の補足[⭐️重要]

以下がメソッドです。もう少し詳しく見ていきます。

https://github.com/laravel/framework/blob/c7a87ae20172593f7dd498d9b7779b56e051fa82/src/Illuminate/Session/DatabaseSessionHandler.php#L125-L145

getDefaultPayload()

$payloadはDBに保存する用の連想配列データです。
そのため後続の処理では$payloadのキーと値を、sessionsテーブルの構造に揃う形式にしていきます。

sessionsテーブルのカラム

説明 カラム
セッションID id
ユーザーID user_id
IPアドレス ip_address
ユーザーエージェント user_agent
セッションデータ payload
タイムスタンプ last_activity

$payload連想配列の中身

キー 呼び出し 詳細
id 引数($sessionId) $sessionId
user_id $this->userId() $this->container->make(Guard::class)->id()
ip_address $this->ipAddress() $this->container->make('request')->ip()
user_agent $this->userAgent() substr((string) $this->container->make('request')->header('User-Agent'), 0, 500)
payload 引数($data) base64_encode($data)
last_activity $this->currentTime() Carbon::now()->getTimestamp()

※payloadはDB文脈だとセッションデータを入れるカラムを指しますが、ここのアプリのコードの文脈だとDBに保存する連想配列データというもっと大きな括りであることに注意。

(アプリのコードの文脈での$payloadのpayloadキーにセットする値の変数名は$dataであり、中身はStoreクラスのインスタンスの$this->attributesの値)

https://github.com/laravel/framework/blob/c7a87ae20172593f7dd498d9b7779b56e051fa82/src/Illuminate/Session/DatabaseSessionHandler.php#L175-L259

performUpdate() または performInsert()

すでにセッションが存在している場合は更新し、そうでない場合は新規作成する。

https://github.com/laravel/framework/blob/c7a87ae20172593f7dd498d9b7779b56e051fa82/src/Illuminate/Session/DatabaseSessionHandler.php#L147-L173

シリーズ

Discussion