#はじめに
HTTPベースのアプリケーションはステートレスであるため、セッションは複数のリクエストにわたってユーザー情報を保存する方法を提供します。そのユーザー情報は通常、後続のリクエストからアクセスできる永続的なストアやバックエンドに保存されます。
Laravelは表現力豊かで統一されたAPIを通じてアクセスできるさまざまなセッションバックエンドを標準で備えています。人気のあるバックエンドであるMemcached、Redis、データベースのサポートも含まれています。
#設定
アプリケーションのセッション設定ファイルは config/session.php にあります。このファイルで利用可能なオプションを必ず確認してください。デフォルトでは、Laravelは多くのアプリケーションで問題なく動作する file セッションドライバーを使用するよう設定されています。アプリケーションが複数のウェブサーバーでロードバランスされる場合は、すべてのサーバーがアクセスできる集中型ストア(Redisやデータベースなど)を選択すべきです。
セッションの driver 設定オプションは、各リクエストでセッションデータがどこに保存されるかを定義します。Laravelは複数の優れたドライバーを標準で提供しています:
file- セッションはstorage/framework/sessionsに保存されます。cookie- セッションは安全に暗号化されたクッキーに保存されます。database- セッションはリレーショナルデータベースに保存されます。memcached/redis- セッションはこれらの高速なキャッシュベースのストアに保存されます。dynamodb- セッションはAWS DynamoDBに保存されます。array- セッションはPHPの配列に保存され、永続化されません。
arrayドライバーは主にテスト時に使用され、セッションに保存されたデータが永続化されないようにします。
#ドライバーの前提条件
#データベース
database セッションドライバーを使用する場合、セッションレコードを格納するテーブルを作成する必要があります。以下はテーブルの Schema 宣言の例です:
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
Schema::create('sessions', function (Blueprint $table) {
$table->string('id')->primary();
$table->foreignId('user_id')->nullable()->index();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->text('payload');
$table->integer('last_activity')->index();
});
このマイグレーションは session:table Artisanコマンドで生成できます。データベースマイグレーションの詳細は完全なmigrationドキュメントをご覧ください。
php artisan session:table
php artisan migrate
#Redis
LaravelでRedisセッションを使用する前に、PECL経由でPhpRedis PHP拡張をインストールするか、Composerで predis/predis パッケージ(~1.0)をインストールする必要があります。Redisの設定についてはLaravelのRedisドキュメントを参照してください。
session 設定ファイルの connection オプションで、セッションに使用するRedis接続を指定できます。
#セッションとのやり取り
#データの取得
Laravelでセッションデータを扱う主な方法は、グローバルな session ヘルパーと Request インスタンス経由の2つです。まず、ルートクロージャやコントローラーメソッドで型宣言できる Request インスタンスを使ったセッションアクセスを見てみましょう。コントローラーメソッドの依存はLaravelのサービスコンテナによって自動的に注入されます:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\View\View;
class UserController extends Controller
{
/**
* 指定されたユーザーのプロフィールを表示します。
*/
public function show(Request $request, string $id): View
{
$value = $request->session()->get('key');
// ...
$user = $this->users->find($id);
return view('user.profile', ['user' => $user]);
}
}
セッションから値を取得する際、get メソッドの第2引数としてデフォルト値を渡すことができます。指定したキーがセッションに存在しない場合は、このデフォルト値が返されます。get メソッドにデフォルト値としてクロージャを渡し、要求されたキーが存在しない場合、クロージャが実行され、その結果が返されます:
$value = $request->session()->get('key', 'default');
$value = $request->session()->get('key', function () {
return 'default';
});
#グローバルセッションヘルパー
グローバルなPHP関数 session を使ってセッションのデータを取得・保存することもできます。session ヘルパーに文字列の引数を1つ渡すと、そのキーの値を返します。配列でキーと値のペアを渡すと、その値がセッションに保存されます:
Route::get('/home', function () {
// セッションからデータを取得...
$value = session('key');
// デフォルト値を指定...
$value = session('key', 'default');
// セッションにデータを保存...
session(['key' => 'value']);
});
HTTPリクエストインスタンス経由でセッションを使う場合とグローバルな session ヘルパーを使う場合で実質的な違いはほとんどありません。どちらもすべてのテストケースで利用可能な assertSessionHas メソッドを使ってテストできます。
#セッション内のすべてのデータを取得
セッション内のすべてのデータを取得したい場合は、all メソッドを使います:
$data = $request->session()->all();
#セッションデータの一部を取得
only と except メソッドを使ってセッションデータの一部を取得できます:
$data = $request->session()->only(['username', 'email']);
$data = $request->session()->except(['username', 'email']);
#セッションにアイテムが存在するか確認
アイテムがセッションに存在し、かつ null でないかを確認するには has メソッドを使います。has は存在すれば true を返します:
if ($request->session()->has('users')) {
// ...
}
値が null でもアイテムが存在するかを確認するには exists メソッドを使います:
if ($request->session()->exists('users')) {
// ...
}
セッションに項目が存在しないかどうかを判定するには、missing メソッドを使用できます。missing メソッドは項目が存在しない場合に true を返します:
if ($request->session()->missing('users')) {
// ...
}
#データの保存
セッションにデータを保存するには、通常リクエストインスタンスの put メソッドかグローバルな session ヘルパーを使います:
// リクエストインスタンス経由...
$request->session()->put('key', 'value');
// グローバルな "session" ヘルパー経由...
session(['key' => 'value']);
#配列セッション値への追加
push メソッドは、配列であるセッション値に新しい値を追加できます。例えば、user.teams キーがチーム名の配列を持つ場合、次のように新しい値を追加できます:
$request->session()->push('user.teams', 'developers');
#アイテムの取得と削除
pull メソッドは、セッションからアイテムを取得しつつ削除します:
$value = $request->session()->pull('key', 'default');
#セッション値のインクリメントとデクリメント
セッションデータに整数が含まれている場合、increment と decrement メソッドで増減できます:
$request->session()->increment('count');
$request->session()->increment('count', $incrementBy = 2);
$request->session()->decrement('count');
$request->session()->decrement('count', $decrementBy = 2);
#フラッシュデータ
次のリクエストのために一時的にセッションにアイテムを保存したい場合は、flash メソッドを使います。この方法で保存されたデータは即座に利用可能で、次のHTTPリクエスト中も利用できます。次のリクエスト後にフラッシュデータは削除されます。主に短期間のステータスメッセージに便利です:
$request->session()->flash('status', 'Task was successful!');
フラッシュデータを複数リクエストにわたって保持したい場合は、reflash メソッドを使います。特定のフラッシュデータだけを保持したい場合は、keep メソッドを使います:
$request->session()->reflash();
$request->session()->keep(['username', 'email']);
現在のリクエストだけフラッシュデータを保持したい場合は、now メソッドを使います:
$request->session()->now('status', 'Task was successful!');
#データの削除
forget メソッドはセッションから特定のデータを削除します。すべてのデータを削除したい場合は flush メソッドを使います:
// 単一のキーを削除...
$request->session()->forget('name');
// 複数のキーを削除...
$request->session()->forget(['name', 'status']);
$request->session()->flush();
#セッションIDの再生成
セッションIDの再生成は、悪意のあるユーザーによるセッション固定攻撃を防ぐためによく行われます。
LaravelはLaravelのアプリケーションスターターキットやLaravel Fortifyを使っている場合、認証時に自動でセッションIDを再生成します。ただし、手動で再生成したい場合は regenerate メソッドを使えます:
$request->session()->regenerate();
セッションIDを再生成し、同時にセッション内のすべてのデータを削除したい場合は、invalidate メソッドを使います:
$request->session()->invalidate();
#セッションのブロッキング
セッションブロッキングを利用するには、アプリケーションがアトミックロックをサポートするキャッシュドライバーを使っている必要があります。現在対応しているドライバーは memcached、dynamodb、redis、database、file、array です。さらに、cookie セッションドライバーは使用できません。
デフォルトでは、Laravelは同じセッションを使うリクエストを同時に実行できます。例えば、JavaScriptのHTTPライブラリで2つのHTTPリクエストを同時に送ると、両方が同時に実行されます。多くのアプリケーションでは問題ありませんが、セッションに書き込みを行う2つの異なるエンドポイントに対して同時リクエストを送る一部のアプリケーションでは、セッションデータの損失が起こる可能性があります。
これを防ぐために、Laravelは特定のセッションに対する同時リクエストを制限する機能を提供します。始めるには、ルート定義に単純に block メソッドをチェーンしてください。以下の例では、/profile エンドポイントへのリクエストがセッションロックを取得します。このロックが保持されている間、同じセッションIDを共有する /profile または /order エンドポイントへのリクエストは、最初のリクエストの処理が終わるまで待機します:
Route::post('/profile', function () {
// ...
})->block($lockSeconds = 10, $waitSeconds = 10)
Route::post('/order', function () {
// ...
})->block($lockSeconds = 10, $waitSeconds = 10)
block メソッドはオプションの引数を2つ受け取ります。block メソッドの最初の引数は、セッションロックが解放されるまで保持する最大秒数を指定します。もちろん、リクエストがこの時間より前に完了した場合は、ロックはそれより前に解放されます。
block メソッドが受け取る2番目の引数は、リクエストがセッションロックを取得しようとする間に待機する秒数です。指定した秒数内にセッションロックを取得できない場合、Illuminate\Contracts\Cache\LockTimeoutException がスローされます。
これらの引数を渡さない場合、ロックは最大10秒間保持され、リクエストは最大10秒間ロック取得を待機します:
Route::post('/profile', function () {
// ...
})->block()
#カスタムセッションドライバーの追加
#ドライバーの実装
既存のセッションドライバーがアプリケーションの要件に合わない場合、独自のセッションハンドラーを作成できます。カスタムセッションドライバーはPHPの組み込み SessionHandlerInterface を実装する必要があります。このインターフェイスはシンプルなメソッドのみを含みます。MongoDBのスタブ実装は以下のようになります:
<?php
namespace App\Extensions;
class MongoSessionHandler implements \SessionHandlerInterface
{
public function open($savePath, $sessionName) {}
public function close() {}
public function read($sessionId) {}
public function write($sessionId, $data) {}
public function destroy($sessionId) {}
public function gc($lifetime) {}
}
Laravelは拡張機能を格納するディレクトリを標準で用意していません。好きな場所に配置できます。この例では Extensions ディレクトリを作成し、MongoSessionHandler を格納しています。
これらのメソッドの目的がわかりにくいため、簡単に説明します:
openメソッドは通常ファイルベースのセッションストアで使います。Laravelはfileセッションドライバーを備えているため、このメソッドに何か書く必要はほとんどありません。空のままで構いません。closeメソッドもopenと同様に、ほとんどのドライバーで無視できます。readメソッドは、指定された$sessionIdに関連付けられたセッションデータの文字列を返すべきです。シリアライズやエンコードは不要で、Laravelが自動で処理します。writeメソッドは、与えられた$data文字列を$sessionIdに関連付けてMongoDBなどの永続ストレージに書き込みます。シリアライズは不要です。destroyメソッドは、指定された$sessionIdに関連するデータを永続ストレージから削除します。gcメソッドは、指定された$lifetime(UNIXタイムスタンプ)より古いすべてのセッションデータを破棄します。MemcachedやRedisのような自己期限切れシステムでは空のままで構いません。
#ドライバーの登録
ドライバーを実装したら、Laravelに登録できます。Laravelのセッションバックエンドに追加ドライバーを登録するには、Session ファサードの extend メソッドを使います。extend メソッドはサービスプロバイダーの boot メソッド内で呼び出すべきです。既存の App\Providers\AppServiceProvider で行うか、新しいプロバイダーを作成しても構いません:
<?php
namespace App\Providers;
use App\Extensions\MongoSessionHandler;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\ServiceProvider;
class SessionServiceProvider extends ServiceProvider
{
/**
* アプリケーションサービスの登録。
*/
public function register(): void
{
// ...
}
/**
* アプリケーションサービスの起動処理。
*/
public function boot(): void
{
Session::extend('mongo', function (Application $app) {
// SessionHandlerInterfaceの実装を返す...
return new MongoSessionHandler;
});
}
}
ドライバーを登録したら、config/session.php の設定ファイルで mongo ドライバーを使用できます。