Eloquent:入門
- イントロダクション
- モデルクラスの生成
- Eloquentモデルの規約
- モデルの取得
- 単一モデル/集計の取得
- モデルの挿入と更新
- モデルの削除
- モデルのプルーニング
- モデルの複製
- クエリスコープ
- モデルの比較
- イベント
イントロダクション
Laravelには、データベースとの対話を楽しくするオブジェクトリレーショナルマッパー(ORM)であるEloquentが含まれています。Eloquentを使用する場合、各データベーステーブルには、そのテーブルとの対話に使用される対応する「モデル」があります。データベーステーブルからレコードを取得することに加えて、Eloquentモデルでは、テーブルからレコードを挿入、更新、および削除することもできます。
開始する前に、アプリケーションの `config/database.php` 構成ファイルでデータベース接続を設定してください。データベースの設定の詳細については、データベース設定ドキュメントをご覧ください。
Laravelブートキャンプ
Laravelを初めて使用する場合は、Laravelブートキャンプに進んでください。Laravelブートキャンプでは、Eloquentを使用して最初のLaravelアプリケーションを構築する手順を説明します。LaravelとEloquentが提供するすべての機能を知るための素晴らしい方法です。
モデルクラスの生成
まず、Eloquentモデルを作成しましょう。モデルは通常、 `app\Models` ディレクトリにあり、 `Illuminate\Database\Eloquent\Model` クラスを拡張します。 `make:model` Artisanコマンドを使用して、新しいモデルを生成できます
php artisan make:model Flight
モデルを生成するときにデータベースマイグレーションを生成する場合は、 `--migration` または `-m` オプションを使用できます
php artisan make:model Flight --migration
モデルを生成するときに、ファクトリ、シーダー、ポリシー、コントローラー、フォームリクエストなど、さまざまな種類のクラスを生成できます。さらに、これらのオプションを組み合わせて、一度に複数のクラスを作成できます
# Generate a model and a FlightFactory class...php artisan make:model Flight --factoryphp artisan make:model Flight -f # Generate a model and a FlightSeeder class...php artisan make:model Flight --seedphp artisan make:model Flight -s # Generate a model and a FlightController class...php artisan make:model Flight --controllerphp artisan make:model Flight -c # Generate a model, FlightController resource class, and form request classes...php artisan make:model Flight --controller --resource --requestsphp artisan make:model Flight -crR # Generate a model and a FlightPolicy class...php artisan make:model Flight --policy # Generate a model and a migration, factory, seeder, and controller...php artisan make:model Flight -mfsc # Shortcut to generate a model, migration, factory, seeder, policy, controller, and form requests...php artisan make:model Flight --allphp artisan make:model Flight -a # Generate a pivot model...php artisan make:model Member --pivotphp artisan make:model Member -p
モデルの検査
モデルのコードをざっと見ただけでは、モデルのすべての利用可能な属性とリレーションシップを判断するのが難しい場合があります。代わりに、 `model:show` Artisanコマンドを試してください。これは、すべてのモデルの属性とリレーションの便利な概要を提供します
php artisan model:show Flight
Eloquentモデルの規約
`make:model` コマンドによって生成されたモデルは、 `app/Models` ディレクトリに配置されます。基本的なモデルクラスを調べて、Eloquentの主要な規約について説明しましょう
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Flight extends Model{ // ...}
テーブル名
上記の例を一目見て、 `Flight` モデルに対応するデータベーステーブルをEloquentに指示していないことに気付いたかもしれません。慣例により、明示的に別の名前が指定されていない限り、クラスの「スネークケース」、複数形の名前がテーブル名として使用されます。したがって、この場合、Eloquentは `Flight` モデルが `flights` テーブルにレコードを格納すると想定しますが、 `AirTrafficController` モデルは `air_traffic_controllers` テーブルにレコードを格納します。
モデルの対応するデータベーステーブルがこの規則に適合しない場合は、モデルで `table` プロパティを定義することにより、モデルのテーブル名を手動で指定できます
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Flight extends Model{ /** * The table associated with the model. * * @var string */ protected $table = 'my_flights';}
プライマリキー
Eloquentはまた、各モデルの対応するデータベーステーブルに `id` という名前のプライマリキカラムがあると想定しています。必要に応じて、モデルのプライマリキーとして機能する別のカラムを指定するために、モデルで保護された `$primaryKey` プロパティを定義できます
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Flight extends Model{ /** * The primary key associated with the table. * * @var string */ protected $primaryKey = 'flight_id';}
さらに、Eloquentはプライマリキーが増分する整数値であると想定しているため、Eloquentはプライマリキーを自動的に整数にキャストします。増分しない、または数値以外のプライマリキーを使用する場合は、 `false` に設定されたモデルにパブリック `$incrementing` プロパティを定義する必要があります
<?php class Flight extends Model{ /** * Indicates if the model's ID is auto-incrementing. * * @var bool */ public $incrementing = false;}
モデルのプライマリキーが整数でない場合は、モデルで保護された `$keyType` プロパティを定義する必要があります。このプロパティの値は `string` である必要があります
<?php class Flight extends Model{ /** * The data type of the primary key ID. * * @var string */ protected $keyType = 'string';}
「複合」プライマリキー
Eloquentでは、各モデルにプライマリキーとして機能できる一意に識別する「ID」が少なくとも1つ必要です。「複合」プライマリキーは、Eloquentモデルではサポートされていません。ただし、テーブルの一意に識別するプライマリキーに加えて、データベーステーブルに追加の複数列の一意のインデックスを自由に
UUIDとULIDキー
Eloquentモデルのプライマリキーとして自動増分整数を使用する代わりに、UUIDを使用することを選択できます。UUIDは、36文字の長さの一意の英数字識別子です。
モデルで自動増分整数キーの代わりにUUIDキーを使用する場合は、モデルで `Illuminate\Database\Eloquent\Concerns\HasUuids` トレイトを使用できます。もちろん、モデルにUUIDと同等のプライマリキカラムがあることを確認する必要があります
use Illuminate\Database\Eloquent\Concerns\HasUuids;use Illuminate\Database\Eloquent\Model; class Article extends Model{ use HasUuids; // ...} $article = Article::create(['title' => 'Traveling to Europe']); $article->id; // "8f8e8478-9035-4d23-b9a7-62f4d2612ce5"
デフォルトでは、 `HasUuids` トレイトはモデルに「順序付けられた」UUIDを生成します。これらのUUIDは、辞書順にソートできるため、インデックス付きデータベースストレージの効率が向上します。
モデルで `newUniqueId` メソッドを定義することにより、特定のモデルのUUID生成プロセスをオーバーライドできます。さらに、モデルで `uniqueIds` メソッドを定義することにより、UUIDを受け取るカラムを指定できます
use Ramsey\Uuid\Uuid; /** * Generate a new UUID for the model. */public function newUniqueId(): string{ return (string) Uuid::uuid4();} /** * Get the columns that should receive a unique identifier. * * @return array<int, string> */public function uniqueIds(): array{ return ['id', 'discount_code'];}
必要に応じて、UUIDの代わりに「ULID」を使用することを選択できます。ULIDはUUIDに似ています。ただし、長さは26文字だけです。順序付けられたUUIDと同様に、ULIDは効率的なデータベースインデックス作成のために辞書順にソート可能です。ULIDを利用するには、モデルで `Illuminate\Database\Eloquent\Concerns\HasUlids` トレイトを使用する必要があります。また、モデルにULIDと同等のプライマリキカラムがあることを確認する必要があります
use Illuminate\Database\Eloquent\Concerns\HasUlids;use Illuminate\Database\Eloquent\Model; class Article extends Model{ use HasUlids; // ...} $article = Article::create(['title' => 'Traveling to Asia']); $article->id; // "01gd4d3tgrrfqeda94gdbtdk5c"
タイムスタンプ
デフォルトでは、Eloquentはモデルの対応するデータベーステーブルに `created_at` と `updated_at` カラムが存在することを想定しています。Eloquentは、モデルの作成または更新時にこれらのカラムの値を自動的に設定します。これらのカラムをEloquentによって自動的に管理したくない場合は、値が `false` のモデルで `$timestamps` プロパティを定義する必要があります
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Flight extends Model{ /** * Indicates if the model should be timestamped. * * @var bool */ public $timestamps = false;}
モデルのタイムスタンプの形式をカスタマイズする必要がある場合は、モデルで `$dateFormat` プロパティを設定します。このプロパティは、日付属性がデータベースに格納される方法と、モデルが配列またはJSONにシリアル化されたときの形式を決定します
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Flight extends Model{ /** * The storage format of the model's date columns. * * @var string */ protected $dateFormat = 'U';}
タイムスタンプの格納に使用されるカラムの名前をカスタマイズする必要がある場合は、モデルで `CREATED_AT` および `UPDATED_AT` 定数を定義できます
<?php class Flight extends Model{ const CREATED_AT = 'creation_date'; const UPDATED_AT = 'updated_date';}
モデルの `updated_at` タイムスタンプが変更されずにモデル操作を実行する場合は、 `withoutTimestamps` メソッドに指定されたクロージャ内でモデルを操作できます
Model::withoutTimestamps(fn () => $post->increment('reads'));
データベース接続
デフォルトでは、すべてのEloquentモデルは、アプリケーションに設定されているデフォルトのデータベース接続を使用します。特定のモデルと対話するときに異なる接続を指定したい場合は、モデルに$connection
プロパティを定義する必要があります。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Flight extends Model{ /** * The database connection that should be used by the model. * * @var string */ protected $connection = 'mysql';}
デフォルト属性値
デフォルトでは、新しくインスタンス化されたモデルインスタンスには属性値が含まれていません。モデルの属性にデフォルト値を定義したい場合は、モデルに$attributes
プロパティを定義できます。$attributes
配列に配置される属性値は、データベースから読み取られたかのように、生の「保存可能な」形式である必要があります。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Flight extends Model{ /** * The model's default values for attributes. * * @var array */ protected $attributes = [ 'options' => '[]', 'delayed' => false, ];}
Eloquentの厳密性の設定
Laravelは、さまざまな状況でEloquentの動作と「厳密さ」を設定できるいくつかのメソッドを提供しています。
まず、preventLazyLoading
メソッドは、遅延読み込みを防止するかどうかを示すオプションのブール値引数を受け入れます。たとえば、本番環境以外の環境でのみ遅延読み込みを無効にして、本番コードに遅延読み込みされたリレーションシップが誤って存在する場合でも、本番環境が正常に機能し続けるようにすることができます。通常、このメソッドは、アプリケーションのAppServiceProvider
のboot
メソッドで呼び出す必要があります。
use Illuminate\Database\Eloquent\Model; /** * Bootstrap any application services. */public function boot(): void{ Model::preventLazyLoading(! $this->app->isProduction());}
また、preventSilentlyDiscardingAttributes
メソッドを呼び出すことで、fillableでない属性に値を設定しようとすると例外をスローするようにLaravelに指示できます。これは、モデルのfillable
配列に追加されていない属性を設定しようとした場合のローカル開発中の予期しないエラーを防ぐのに役立ちます。
Model::preventSilentlyDiscardingAttributes(! $this->app->isProduction());
モデルの取得
モデルとそれに関連付けられたデータベーステーブルを作成したら、データベースからデータの取得を開始する準備が整います。各Eloquentモデルは、モデルに関連付けられたデータベーステーブルを流暢にクエリできる強力なクエリビルダと考えることができます。モデルのall
メソッドは、モデルに関連付けられたデータベーステーブルからすべてのレコードを取得します。
use App\Models\Flight; foreach (Flight::all() as $flight) { echo $flight->name;}
クエリの構築
Eloquentのall
メソッドは、モデルのテーブル内のすべての結果を返します。ただし、各Eloquentモデルはクエリビルダとして機能するため、クエリに追加の制約を追加してから、get
メソッドを呼び出して結果を取得できます。
$flights = Flight::where('active', 1) ->orderBy('name') ->take(10) ->get();
Eloquentモデルはクエリビルダであるため、Laravelのクエリビルダによって提供されるすべてのメソッドを確認する必要があります。Eloquentクエリを作成するときに、これらのメソッドのいずれかを使用できます。
モデルの更新
データベースから取得したEloquentモデルのインスタンスが既にある場合は、fresh
メソッドとrefresh
メソッドを使用してモデルを「更新」できます。fresh
メソッドは、データベースからモデルを再取得します。既存のモデルインスタンスは影響を受けません。
$flight = Flight::where('number', 'FR 900')->first(); $freshFlight = $flight->fresh();
refresh
メソッドは、データベースからの新しいデータを使用して既存のモデルを再ハイドレートします。さらに、ロードされたすべてのリレーションシップも更新されます。
$flight = Flight::where('number', 'FR 900')->first(); $flight->number = 'FR 456'; $flight->refresh(); $flight->number; // "FR 900"
コレクション
見てきたように、all
やget
などのEloquentメソッドは、データベースから複数のレコードを取得します。ただし、これらのメソッドはプレーンなPHP配列を返しません。代わりに、Illuminate\Database\Eloquent\Collection
のインスタンスが返されます。
EloquentのCollection
クラスは、LaravelのベースIlluminate\Support\Collection
クラスを拡張したもので、データコレクションの操作に役立つさまざまなメソッドを提供します。たとえば、reject
メソッドを使用して、呼び出されたクロージャの結果に基づいてコレクションからモデルを削除できます。
$flights = Flight::where('destination', 'Paris')->get(); $flights = $flights->reject(function (Flight $flight) { return $flight->cancelled;});
Laravelのベースコレクションクラスによって提供されるメソッドに加えて、Eloquentコレクションクラスは、Eloquentモデルのコレクションとの対話を specifically意図したいくつかの追加メソッドを提供します。
LaravelのコレクションはすべてPHPの反復可能インターフェースを実装しているため、コレクションを配列のようにループできます。
foreach ($flights as $flight) { echo $flight->name;}
結果のチャンク化
all
メソッドまたはget
メソッドを使用して数万のEloquentレコードをロードしようとすると、アプリケーションのメモリが不足する可能性があります。これらのメソッドを使用する代わりに、chunk
メソッドを使用して、大量のモデルをより効率的に処理できます。
chunk
メソッドは、Eloquentモデルのサブセットを取得し、処理のためにクロージャに渡します。Eloquentモデルの現在のチャンクのみが一度に取得されるため、chunk
メソッドは、多数のモデルを操作する場合にメモリ使用量を大幅に削減します。
use App\Models\Flight;use Illuminate\Database\Eloquent\Collection; Flight::chunk(200, function (Collection $flights) { foreach ($flights as $flight) { // ... }});
chunk
メソッドに渡される最初の引数は、「チャンク」ごとに受信するレコードの数です。2番目の引数として渡されるクロージャは、データベースから取得された各チャンクに対して呼び出されます。クロージャに渡されるレコードの各チャンクを取得するために、データベースクエリが実行されます。
結果の反復中に更新する列に基づいてchunk
メソッドの結果をフィルタリングする場合は、chunkById
メソッドを使用する必要があります。このようなシナリオでchunk
メソッドを使用すると、予期しない結果や一貫性のない結果が生じる可能性があります。内部的には、chunkById
メソッドは常に、前のチャンクの最後のモデルよりも大きいid
列を持つモデルを取得します。
Flight::where('departed', true) ->chunkById(200, function (Collection $flights) { $flights->each->update(['departed' => false]); }, column: 'id');
chunkById
メソッドとlazyById
メソッドは、実行されるクエリに独自の「where」条件を追加するため、通常は独自の条件をクロージャ内で論理的にグループ化する必要があります。
Flight::where(function ($query) { $query->where('delayed', true)->orWhere('cancelled', true);})->chunkById(200, function (Collection $flights) { $flights->each->update([ 'departed' => false, 'cancelled' => true ]);}, column: 'id');
遅延コレクションを使用したチャンキング
lazy
メソッドは、背後でクエリをチャンクで実行するという点で、chunk
メソッドと同様に機能します。ただし、lazy
メソッドは、各チャンクをコールバックに直接渡すのではなく、Eloquentモデルのフラット化されたLazyCollection
を返します。これにより、結果を単一のストリームとして操作できます。
use App\Models\Flight; foreach (Flight::lazy() as $flight) { // ...}
結果の反復中に更新する列に基づいてlazy
メソッドの結果をフィルタリングする場合は、lazyById
メソッドを使用する必要があります。内部的には、lazyById
メソッドは常に、前のチャンクの最後のモデルよりも大きいid
列を持つモデルを取得します。
Flight::where('departed', true) ->lazyById(200, column: 'id') ->each->update(['departed' => false]);
lazyByIdDesc
メソッドを使用して、 `id`の降順に基づいて結果をフィルタリングできます。
カーソル
lazy
メソッドと同様に、cursor
メソッドを使用して、数万のEloquentモデルレコードを反復処理する際のアプリケーションのメモリ消費量を大幅に削減できます。
cursor
メソッドは単一のデータベースクエリのみを実行します。ただし、個々のEloquentモデルは、実際に反復処理されるまでハイドレートされません。したがって、カーソルを反復処理している間、一度にメモリに保持されるEloquentモデルは1つだけです。
cursor
メソッドは、一度にメモリに単一のEloquentモデルのみを保持するため、リレーションシップを事前にロードできません。リレーションシップを事前にロードする必要がある場合は、代わりにlazy
メソッドを使用することを検討してください。
内部的には、cursor
メソッドはPHP ジェネレーターを使用してこの機能を実装します。
use App\Models\Flight; foreach (Flight::where('destination', 'Zurich')->cursor() as $flight) { // ...}
cursor
はIlluminate\Support\LazyCollection
インスタンスを返します。遅延コレクションを使用すると、一般的なLaravelコレクションで使用可能な多くのコレクションメソッドを使用できますが、一度に1つのモデルのみをメモリにロードします。
use App\Models\User; $users = User::cursor()->filter(function (User $user) { return $user->id > 500;}); foreach ($users as $user) { echo $user->id;}
cursor
メソッドは、通常のクエリよりもはるかに少ないメモリを使用しますが(一度に1つのEloquentモデルのみをメモリに保持することにより)、最終的にはメモリが不足します。これは、PHPのPDOドライバーがすべての生のクエリ結果をバッファに内部的にキャッシュするためです。非常に多数のEloquentレコードを処理する場合は、代わりにlazy
メソッドを使用することを検討してください。
高度なサブクエリ
サブクエリ選択
Eloquentは、高度なサブクエリサポートも提供しており、単一のクエリで関連テーブルから情報を取得できます。たとえば、フライトのdestinations
テーブルと、目的地へのflights
テーブルがあるとします。flights
テーブルには、フライトが目的地に到着した時刻を示すarrived_at
列が含まれています。
クエリビルダのselect
メソッドとaddSelect
メソッドで使用可能なサブクエリ機能を使用すると、単一のクエリを使用して、すべてのdestinations
と、その目的地に最後に到着したフライトの名前を選択できます。
use App\Models\Destination;use App\Models\Flight; return Destination::addSelect(['last_flight' => Flight::select('name') ->whereColumn('destination_id', 'destinations.id') ->orderByDesc('arrived_at') ->limit(1)])->get();
サブクエリの順序付け
さらに、クエリビルダのorderBy
関数はサブクエリをサポートしています。フライトの例を引き続き使用すると、この機能を使用して、最後のフライトがその目的地に到着した時刻に基づいてすべての目的地をソートできます。繰り返しますが、これは単一のデータベースクエリを実行している間に行うことができます。
return Destination::orderByDesc( Flight::select('arrived_at') ->whereColumn('destination_id', 'destinations.id') ->orderByDesc('arrived_at') ->limit(1))->get();
単一モデル/集計の取得
特定のクエリに一致するすべてのレコードを取得することに加えて、find
、first
、またはfirstWhere
メソッドを使用して単一のレコードを取得することもできます。モデルのコレクションを返す代わりに、これらのメソッドは単一のモデルインスタンスを返します。
use App\Models\Flight; // Retrieve a model by its primary key...$flight = Flight::find(1); // Retrieve the first model matching the query constraints...$flight = Flight::where('active', 1)->first(); // Alternative to retrieving the first model matching the query constraints...$flight = Flight::firstWhere('active', 1);
結果が見つからない場合に他のアクションを実行したい場合があります。findOr
メソッドとfirstOr
メソッドは、単一のモデルインスタンスを返します。または、結果が見つからない場合は、指定されたクロージャを実行します。クロージャによって返される値は、メソッドの結果と見なされます。
$flight = Flight::findOr(1, function () { // ...}); $flight = Flight::where('legs', '>', 3)->firstOr(function () { // ...});
見つからない例外
モデルが見つからない場合に例外をスローしたい場合があります。これは、ルートまたはコントローラーで特に便利です。findOrFail
メソッドとfirstOrFail
メソッドは、クエリの最初の結果を取得します。ただし、結果が見つからない場合は、Illuminate\Database\Eloquent\ModelNotFoundException
がスローされます。
$flight = Flight::findOrFail(1); $flight = Flight::where('legs', '>', 3)->firstOrFail();
ModelNotFoundException
がキャッチされない場合、404 HTTPレスポンスがクライアントに自動的に送り返されます。
use App\Models\Flight; Route::get('/api/flights/{id}', function (string $id) { return Flight::findOrFail($id);});
モデルの取得または作成
firstOrCreate
メソッドは、指定された列/値のペアを使用してデータベースレコードを見つけようとします。データベースでモデルが見つからない場合、最初の配列引数とオプションの2番目の配列引数をマージした結果の属性を持つレコードが挿入されます。
firstOrNew
メソッドは、firstOrCreate
と同様に、指定された属性に一致するレコードをデータベースで見つけようとします。ただし、モデルが見つからない場合は、新しいモデルインスタンスが返されます。 firstOrNew
によって返されるモデルはまだデータベースに保存されていないことに注意してください。保存するには、手動でsave
メソッドを呼び出す必要があります。
use App\Models\Flight; // Retrieve flight by name or create it if it doesn't exist...$flight = Flight::firstOrCreate([ 'name' => 'London to Paris']); // Retrieve flight by name or create it with the name, delayed, and arrival_time attributes...$flight = Flight::firstOrCreate( ['name' => 'London to Paris'], ['delayed' => 1, 'arrival_time' => '11:30']); // Retrieve flight by name or instantiate a new Flight instance...$flight = Flight::firstOrNew([ 'name' => 'London to Paris']); // Retrieve flight by name or instantiate with the name, delayed, and arrival_time attributes...$flight = Flight::firstOrNew( ['name' => 'Tokyo to Sydney'], ['delayed' => 1, 'arrival_time' => '11:30']);
集計の取得
Eloquentモデルを操作する場合、Laravel クエリビルダによって提供されるcount
、sum
、max
、およびその他の集計メソッドを使用することもできます。ご想像のとおり、これらのメソッドはEloquentモデルインスタンスではなくスカラー値を返します。
$count = Flight::where('active', 1)->count(); $max = Flight::where('active', 1)->max('price');
モデルの挿入と更新
挿入
もちろん、Eloquentを使用する場合、データベースからモデルを取得するだけではありません。新しいレコードを挿入する必要もあります。ありがたいことに、Eloquentはそれを簡単にします。データベースに新しいレコードを挿入するには、新しいモデルインスタンスをインスタンス化し、モデルに属性を設定する必要があります。次に、モデルインスタンスでsave
メソッドを呼び出します。
<?php namespace App\Http\Controllers; use App\Http\Controllers\Controller;use App\Models\Flight;use Illuminate\Http\RedirectResponse;use Illuminate\Http\Request; class FlightController extends Controller{ /** * Store a new flight in the database. */ public function store(Request $request): RedirectResponse { // Validate the request... $flight = new Flight; $flight->name = $request->name; $flight->save(); return redirect('/flights'); }}
この例では、受信HTTPリクエストのname
フィールドをApp\Models\Flight
モデルインスタンスのname
属性に割り当てます。 save
メソッドを呼び出すと、レコードがデータベースに挿入されます。モデルのcreated_at
およびupdated_at
タイムスタンプは、save
メソッドが呼び出されると自動的に設定されるため、手動で設定する必要はありません。
または、create
メソッドを使用して、単一のPHPステートメントで新しいモデルを「保存」することもできます。挿入されたモデルインスタンスは、create
メソッドによって返されます。
use App\Models\Flight; $flight = Flight::create([ 'name' => 'London to Paris',]);
ただし、create
メソッドを使用する前に、モデルクラスでfillable
またはguarded
プロパティを指定する必要があります。すべてのEloquentモデルは、デフォルトでマスアサインメントの脆弱性から保護されているため、これらのプロパティが必要です。マスアサインメントの詳細については、マスアサインメントのドキュメントを参照してください。
更新
save
メソッドは、データベースに既に存在するモデルの更新にも使用できます。モデルを更新するには、モデルを取得して更新したい属性を設定します。その後、モデルの save
メソッドを呼び出します。ここでも、updated_at
タイムスタンプは自動的に更新されるため、手動で値を設定する必要はありません。
use App\Models\Flight; $flight = Flight::find(1); $flight->name = 'Paris to London'; $flight->save();
場合によっては、既存のモデルを更新するか、一致するモデルが存在しない場合は新しいモデルを作成する必要があります。 firstOrCreate
メソッドと同様に、updateOrCreate
メソッドはモデルを永続化するため、save
メソッドを手動で呼び出す必要はありません。
以下の例では、departure
が Oakland
で destination
が San Diego
のフライトが存在する場合、その price
と discounted
カラムが更新されます。そのようなフライトが存在しない場合は、最初の引数の配列と2番目の引数の配列をマージした結果の属性を持つ新しいフライトが作成されます。
$flight = Flight::updateOrCreate( ['departure' => 'Oakland', 'destination' => 'San Diego'], ['price' => 99, 'discounted' => 1]);
一括更新
更新は、特定のクエリに一致するモデルに対しても実行できます。この例では、active
で destination
が San Diego
のすべてのフライトが遅延としてマークされます。
Flight::where('active', 1) ->where('destination', 'San Diego') ->update(['delayed' => 1]);
update
メソッドは、更新する必要があるカラムと値のペアの配列を想定しています。 update
メソッドは、影響を受けた行数を返します。
Eloquent を介して一括更新を実行する場合、更新されたモデルに対して saving
、saved
、updating
、および updated
モデルイベントは発生しません。これは、一括更新を実行する際にモデルが実際に取得されないためです。
属性の変更の確認
Eloquent は、isDirty
、isClean
、および wasChanged
メソッドを提供して、モデルの内部状態を調べ、モデルが最初に取得されたときから属性がどのように変更されたかを判断します。
isDirty
メソッドは、モデルが取得されてからモデルの属性が変更されたかどうかを判断します。特定の属性名または属性の配列を isDirty
メソッドに渡して、いずれかの属性が「ダーティ」かどうかを判断できます。 isClean
メソッドは、モデルが取得されてから属性が変更されていないかどうかを判断します。このメソッドもオプションの属性引数を受け入れます。
use App\Models\User; $user = User::create([ 'first_name' => 'Taylor', 'last_name' => 'Otwell', 'title' => 'Developer',]); $user->title = 'Painter'; $user->isDirty(); // true$user->isDirty('title'); // true$user->isDirty('first_name'); // false$user->isDirty(['first_name', 'title']); // true $user->isClean(); // false$user->isClean('title'); // false$user->isClean('first_name'); // true$user->isClean(['first_name', 'title']); // false $user->save(); $user->isDirty(); // false$user->isClean(); // true
wasChanged
メソッドは、現在のリクエストサイクル内でモデルが最後に保存されたときに属性が変更されたかどうかを判断します。必要に応じて、属性名を渡して、特定の属性が変更されたかどうかを確認できます。
$user = User::create([ 'first_name' => 'Taylor', 'last_name' => 'Otwell', 'title' => 'Developer',]); $user->title = 'Painter'; $user->save(); $user->wasChanged(); // true$user->wasChanged('title'); // true$user->wasChanged(['title', 'slug']); // true$user->wasChanged('first_name'); // false$user->wasChanged(['first_name', 'title']); // true
getOriginal
メソッドは、モデルが取得されてからの変更に関係なく、モデルの元の属性を含む配列を返します。必要に応じて、特定の属性名を渡して、特定の属性の元の値を取得できます。
$user = User::find(1); $user->name; // John $user->name = "Jack";$user->name; // Jack $user->getOriginal('name'); // John$user->getOriginal(); // Array of original attributes...
一括代入
create
メソッドを使用して、単一の PHP ステートメントで新しいモデルを「保存」できます。挿入されたモデルインスタンスは、メソッドによって返されます。
use App\Models\Flight; $flight = Flight::create([ 'name' => 'London to Paris',]);
ただし、create
メソッドを使用する前に、モデルクラスで fillable
または guarded
プロパティを指定する必要があります。これらのプロパティは、すべての Eloquent モデルがデフォルトでマスアサインメントの脆弱性から保護されているため、必須です。
マスアサインメントの脆弱性は、ユーザーが予期しない HTTP リクエストフィールドを渡し、そのフィールドが予期しないデータベースのカラムを変更した場合に発生します。たとえば、悪意のあるユーザーが HTTP リクエストを介して is_admin
パラメータを送信し、それがモデルの create
メソッドに渡されることで、ユーザーが自身を管理者に昇格させる可能性があります。
そのため、まず、マスアサインメント可能にするモデル属性を定義する必要があります。これは、モデルの $fillable
プロパティを使用して行うことができます。たとえば、Flight
モデルの name
属性をマスアサインメント可能にしましょう。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Flight extends Model{ /** * The attributes that are mass assignable. * * @var array<int, string> */ protected $fillable = ['name'];}
マスアサインメント可能な属性を指定したら、create
メソッドを使用してデータベースに新しいレコードを挿入できます。 create
メソッドは、新しく作成されたモデルインスタンスを返します。
$flight = Flight::create(['name' => 'London to Paris']);
既にモデルインスタンスがある場合は、fill
メソッドを使用して属性の配列を入力できます。
$flight->fill(['name' => 'Amsterdam to Frankfurt']);
マスアサインメントと JSON カラム
JSON カラムを割り当てる場合、各カラムのマスアサインメント可能なキーをモデルの $fillable
配列で指定する必要があります。セキュリティ上の理由から、Laravel は guarded
プロパティを使用する場合、ネストされた JSON 属性の更新をサポートしていません。
/** * The attributes that are mass assignable. * * @var array<int, string> */protected $fillable = [ 'options->enabled',];
マスアサインメントの許可
すべての属性をマスアサインメント可能にする場合は、モデルの $guarded
プロパティを空の配列として定義できます。モデルの保護を解除する場合は、Eloquent の fill
、create
、および update
メソッドに渡される配列を常に手動で作成するように特に注意する必要があります。
/** * The attributes that aren't mass assignable. * * @var array<string>|bool */protected $guarded = [];
マスアサインメントの例外
デフォルトでは、$fillable
配列に含まれていない属性は、マスアサインメント操作を実行するときにサイレントに破棄されます。本番環境では、これは予期された動作です。ただし、ローカル開発中は、モデルの変更が有効にならない理由について混乱を招く可能性があります。
必要に応じて、preventSilentlyDiscardingAttributes
メソッドを呼び出すことによって、fillable でない属性を入力しようとするときに例外をスローするように Laravel に指示できます。通常、このメソッドは、アプリケーションの AppServiceProvider
クラスの boot
メソッドで呼び出す必要があります。
use Illuminate\Database\Eloquent\Model; /** * Bootstrap any application services. */public function boot(): void{ Model::preventSilentlyDiscardingAttributes($this->app->isLocal());}
アップサート
Eloquent の upsert
メソッドは、単一のアトミック操作でレコードを更新または作成するために使用できます。メソッドの最初の引数は、挿入または更新する値で構成され、2 番目の引数は、関連付けられたテーブル内でレコードを一意に識別するカラムをリストします。メソッドの 3 番目と最後の引数は、一致するレコードがデータベースに既に存在する場合に更新する必要があるカラムの配列です。 upsert
メソッドは、モデルでタイムスタンプが有効になっている場合、created_at
および updated_at
タイムスタンプを自動的に設定します。
Flight::upsert([ ['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99], ['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150]], uniqueBy: ['departure', 'destination'], update: ['price']);
SQL Server を除くすべてのデータベースでは、upsert
メソッドの 2 番目の引数にあるカラムに「プライマリ」または「一意」のインデックスが必要です。さらに、MariaDB および MySQL データベースドライバは、upsert
メソッドの 2 番目の引数を無視し、常にテーブルの「プライマリ」および「一意」のインデックスを使用して既存のレコードを検出します。
モデルの削除
モデルを削除するには、モデルインスタンスで delete
メソッドを呼び出すことができます。
use App\Models\Flight; $flight = Flight::find(1); $flight->delete();
truncate
メソッドを呼び出して、モデルに関連付けられているすべてのデータベースレコードを削除できます。 truncate
操作は、モデルに関連付けられたテーブルの自動インクリメント ID もリセットします。
Flight::truncate();
主キーによる既存のモデルの削除
上記の例では、delete
メソッドを呼び出す前にデータベースからモデルを取得しています。ただし、モデルの主キーがわかっている場合は、destroy
メソッドを呼び出すことによって、明示的に取得せずにモデルを削除できます。単一の主キーを受け入れることに加えて、destroy
メソッドは複数の主キー、主キーの配列、または主キーのコレクションを受け入れます。
Flight::destroy(1); Flight::destroy(1, 2, 3); Flight::destroy([1, 2, 3]); Flight::destroy(collect([1, 2, 3]));
論理削除モデルを利用している場合は、forceDestroy
メソッドを使用してモデルを完全に削除できます。
Flight::forceDestroy(1);
destroy
メソッドは各モデルを個別にロードし、delete
メソッドを呼び出すため、deleting
および deleted
イベントが各モデルに対して適切にディスパッチされます。
クエリを使用したモデルの削除
もちろん、Eloquent クエリを構築して、クエリの条件に一致するすべてのモデルを削除できます。この例では、非アクティブとしてマークされているすべてのフライトを削除します。一括更新と同様に、一括削除は、削除されたモデルのモデルイベントをディスパッチしません。
$deleted = Flight::where('active', 0)->delete();
Eloquent を介して一括削除ステートメントを実行する場合、削除されたモデルに対して deleting
および deleted
モデルイベントはディスパッチされません。これは、削除ステートメントを実行するときにモデルが実際に取得されないためです。
ソフトデリート
データベースからレコードを実際に削除することに加えて、Eloquent はモデルを「論理削除」することもできます。モデルが論理削除されると、実際にはデータベースから削除されません。代わりに、モデルが「削除」された日時を示す deleted_at
属性がモデルに設定されます。モデルの論理削除を有効にするには、Illuminate\Database\Eloquent\SoftDeletes
トレイトをモデルに追加します。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\SoftDeletes; class Flight extends Model{ use SoftDeletes;}
SoftDeletes
トレイトは、deleted_at
属性を DateTime
/ Carbon
インスタンスに自動的にキャストします。
データベーステーブルに deleted_at
カラムも追加する必要があります。Laravel スキーマビルダーには、このカラムを作成するためのヘルパーメソッドが含まれています。
use Illuminate\Database\Schema\Blueprint;use Illuminate\Support\Facades\Schema; Schema::table('flights', function (Blueprint $table) { $table->softDeletes();}); Schema::table('flights', function (Blueprint $table) { $table->dropSoftDeletes();});
これで、モデルで delete
メソッドを呼び出すと、deleted_at
カラムが現在の日時に設定されます。ただし、モデルのデータベースレコードはテーブルに残ります。論理削除を使用するモデルをクエリする場合、論理削除されたモデルはすべてのクエリ結果から自動的に除外されます。
特定のモデルインスタンスが論理削除されているかどうかを判断するには、trashed
メソッドを使用できます。
if ($flight->trashed()) { // ...}
論理削除されたモデルの復元
論理削除されたモデルを「削除解除」したい場合があります。論理削除されたモデルを復元するには、モデルインスタンスで restore
メソッドを呼び出すことができます。 restore
メソッドは、モデルの deleted_at
カラムを null
に設定します。
$flight->restore();
クエリで restore
メソッドを使用して、複数のモデルを復元することもできます。繰り返しますが、他の「一括」操作と同様に、これは復元されたモデルのモデルイベントをディスパッチしません。
Flight::withTrashed() ->where('airline_id', 1) ->restore();
restore
メソッドは、リレーションシップクエリを構築するときにも使用できます。
$flight->history()->restore();
モデルの完全な削除
データベースからモデルを完全に削除する必要がある場合があります。 forceDelete
メソッドを使用して、論理削除されたモデルをデータベーステーブルから完全に削除できます。
$flight->forceDelete();
Eloquent リレーションシップクエリを構築するときにも、forceDelete
メソッドを使用できます。
$flight->history()->forceDelete();
ソフトデリートされたモデルのクエリ
論理削除されたモデルを含める
上記のように、論理削除されたモデルはクエリ結果から自動的に除外されます。ただし、クエリで withTrashed
メソッドを呼び出すことによって、論理削除されたモデルをクエリの結果に強制的に含めることができます。
use App\Models\Flight; $flights = Flight::withTrashed() ->where('account_id', 1) ->get();
withTrashed
メソッドは、リレーションシップクエリを構築するときにも呼び出すことができます。
$flight->history()->withTrashed()->get();
論理削除されたモデルのみを取得する
onlyTrashed
メソッドは、論理削除されたモデル**のみ**を取得します。
$flights = Flight::onlyTrashed() ->where('airline_id', 1) ->get();
モデルのプルーニング
不要になったモデルを定期的に削除したい場合があります。これを実現するには、定期的にプルーニングするモデルに Illuminate\Database\Eloquent\Prunable
または Illuminate\Database\Eloquent\MassPrunable
トレイトを追加します。トレイトのいずれかをモデルに追加した後、不要になったモデルを解決する Eloquent クエリビルダーを返す prunable
メソッドを実装します。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Builder;use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Prunable; class Flight extends Model{ use Prunable; /** * Get the prunable model query. */ public function prunable(): Builder { return static::where('created_at', '<=', now()->subMonth()); }}
モデルを Prunable
としてマークする場合、モデルに pruning
メソッドを定義することもできます。このメソッドは、モデルが削除される前に呼び出されます。このメソッドは、モデルがデータベースから完全に削除される前に、保存されたファイルなど、モデルに関連付けられた追加のリソースを削除するのに役立ちます。
/** * Prepare the model for pruning. */protected function pruning(): void{ // ...}
プルーニング可能なモデルを構成した後、アプリケーションの routes/console.php
ファイルで model:prune
Artisan コマンドをスケジュールする必要があります。このコマンドを実行する適切な間隔を自由に選択できます。
use Illuminate\Support\Facades\Schedule; Schedule::command('model:prune')->daily();
舞台裏では、model:prune
コマンドは、アプリケーションの app/Models
ディレクトリ内の「Prunable」モデルを自動的に検出します。モデルが別の場所にある場合は、--model
オプションを使用してモデルクラス名を指定できます。
Schedule::command('model:prune', [ '--model' => [Address::class, Flight::class],])->daily();
特定のモデルを除外して、他の検出されたすべてのモデルをプルーニングする場合は、--except
オプションを使用できます。
Schedule::command('model:prune', [ '--except' => [Address::class, Flight::class],])->daily();
--pretend
オプションを付けて model:prune
コマンドを実行することで、prunable
クエリをテストできます。プレテンドモードでは、model:prune
コマンドは、コマンドが実際に実行された場合にプルーニングされるレコード数を報告するだけです。
php artisan model:prune --pretend
ソフトデリートされたモデルは、`prunable` クエリに一致する場合、完全に削除(`forceDelete`)されます。
一括プルーニング
モデルに Illuminate\Database\Eloquent\MassPrunable
トレイトがマークされている場合、モデルは一括削除クエリを使用してデータベースから削除されます。そのため、pruning
メソッドは呼び出されず、deleting
および deleted
モデルイベントもディスパッチされません。これは、削除前にモデルが実際に取得されないため、プルーニングプロセスがはるかに効率的になるためです。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Builder;use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\MassPrunable; class Flight extends Model{ use MassPrunable; /** * Get the prunable model query. */ public function prunable(): Builder { return static::where('created_at', '<=', now()->subMonth()); }}
モデルの複製
replicate
メソッドを使用して、既存のモデルインスタンスの未保存コピーを作成できます。このメソッドは、多くの属性を共有するモデルインスタンスがある場合に特に役立ちます。
use App\Models\Address; $shipping = Address::create([ 'type' => 'shipping', 'line_1' => '123 Example Street', 'city' => 'Victorville', 'state' => 'CA', 'postcode' => '90001',]); $billing = $shipping->replicate()->fill([ 'type' => 'billing']); $billing->save();
新しいモデルに複製されないように1つ以上の属性を除外するには、replicate
メソッドに配列を渡します。
$flight = Flight::create([ 'destination' => 'LAX', 'origin' => 'LHR', 'last_flown' => '2020-03-04 11:00:00', 'last_pilot_id' => 747,]); $flight = $flight->replicate([ 'last_flown', 'last_pilot_id']);
クエリスコープ
グローバルスコープ
グローバルスコープを使用すると、特定のモデルに対するすべてのクエリに制約を追加できます。Laravel独自のソフトデリート機能は、グローバルスコープを利用して、データベースから「削除されていない」モデルのみを取得します。独自のグローバルスコープを作成すると、特定のモデルに対するすべてのクエリが特定の制約を受け取るようにするための便利で簡単な方法を提供できます。
スコープの生成
新しいグローバルスコープを生成するには、make:scope
Artisanコマンドを呼び出すことができます。これにより、生成されたスコープがアプリケーションのapp/Models/Scopes
ディレクトリに配置されます。
php artisan make:scope AncientScope
グローバルスコープの作成
グローバルスコープの作成は簡単です。まず、make:scope
コマンドを使用して、Illuminate\Database\Eloquent\Scope
インターフェースを実装するクラスを生成します。Scope
インターフェースでは、apply
メソッドを実装する必要があります。apply
メソッドは、必要に応じてクエリにwhere
制約や他のタイプの句を追加できます。
<?php namespace App\Models\Scopes; use Illuminate\Database\Eloquent\Builder;use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Scope; class AncientScope implements Scope{ /** * Apply the scope to a given Eloquent query builder. */ public function apply(Builder $builder, Model $model): void { $builder->where('created_at', '<', now()->subYears(2000)); }}
グローバルスコープがクエリのselect句に列を追加する場合は、select
の代わりに addSelect
メソッドを使用する必要があります。これにより、クエリの既存のselect句が意図せずに置き換えられるのを防ぎます。
グローバルスコープの適用
モデルにグローバルスコープを割り当てるには、モデルにScopedBy
属性を配置するだけです。
<?php namespace App\Models; use App\Models\Scopes\AncientScope;use Illuminate\Database\Eloquent\Attributes\ScopedBy; #[ScopedBy([AncientScope::class])]class User extends Model{ //}
または、モデルのbooted
メソッドをオーバーライドし、モデルのaddGlobalScope
メソッドを呼び出すことによって、グローバルスコープを手動で登録できます。addGlobalScope
メソッドは、スコープのインスタンスを唯一の引数として受け入れます。
<?php namespace App\Models; use App\Models\Scopes\AncientScope;use Illuminate\Database\Eloquent\Model; class User extends Model{ /** * The "booted" method of the model. */ protected static function booted(): void { static::addGlobalScope(new AncientScope); }}
上記の例でスコープをApp\Models\User
モデルに追加した後、User::all()
メソッドを呼び出すと、次のSQLクエリが実行されます。
select * from `users` where `created_at` < 0021-02-18 00:00:00
匿名グローバルスコープ
Eloquentでは、クロージャを使用してグローバルスコープを定義することもできます。これは、独自のクラスを必要としない単純なスコープに特に役立ちます。クロージャを使用してグローバルスコープを定義する場合は、addGlobalScope
メソッドの最初の引数として、自分で選択したスコープ名を提供する必要があります。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Builder;use Illuminate\Database\Eloquent\Model; class User extends Model{ /** * The "booted" method of the model. */ protected static function booted(): void { static::addGlobalScope('ancient', function (Builder $builder) { $builder->where('created_at', '<', now()->subYears(2000)); }); }}
グローバルスコープの削除
特定のクエリに対してグローバルスコープを削除する場合は、withoutGlobalScope
メソッドを使用できます。このメソッドは、グローバルスコープのクラス名を唯一の引数として受け入れます。
User::withoutGlobalScope(AncientScope::class)->get();
または、クロージャを使用してグローバルスコープを定義した場合は、グローバルスコープに割り当てた文字列名を渡す必要があります。
User::withoutGlobalScope('ancient')->get();
クエリのグローバルスコープを複数、またはすべて削除する場合は、withoutGlobalScopes
メソッドを使用できます。
// Remove all of the global scopes...User::withoutGlobalScopes()->get(); // Remove some of the global scopes...User::withoutGlobalScopes([ FirstScope::class, SecondScope::class])->get();
ローカルスコープ
ローカルスコープを使用すると、アプリケーション全体で簡単に再利用できる一般的なクエリ制約のセットを定義できます。たとえば、「人気のある」と見なされるすべてのユーザーを頻繁に取得する必要がある場合があります。スコープを定義するには、Eloquentモデルメソッドの前にscope
を付けます。
スコープは常に同じクエリビルダインスタンスまたは`void`を返す必要があります。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Builder;use Illuminate\Database\Eloquent\Model; class User extends Model{ /** * Scope a query to only include popular users. */ public function scopePopular(Builder $query): void { $query->where('votes', '>', 100); } /** * Scope a query to only include active users. */ public function scopeActive(Builder $query): void { $query->where('active', 1); }}
ローカルスコープの活用
スコープが定義されると、モデルをクエリするときにスコープメソッドを呼び出すことができます。ただし、メソッドを呼び出すときに`scope`プレフィックスを含めるべきではありません。さまざまなスコープへの呼び出しをチェーンすることもできます。
use App\Models\User; $users = User::popular()->active()->orderBy('created_at')->get();
`or` クエリ演算子を使用して複数のEloquentモデルスコープを組み合わせるには、正しい論理グループ化を実現するためにクロージャを使用する必要がある場合があります。
$users = User::popular()->orWhere(function (Builder $query) { $query->active();})->get();
ただし、これは面倒な場合があるため、Laravelはクロージャを使用せずにスコープを流暢にチェーンできる「高階」`orWhere`メソッドを提供しています。
$users = User::popular()->orWhere->active()->get();
動的スコープ
パラメータを受け入れるスコープを定義したい場合があります。始めるには、スコープメソッドのシグネチャに追加パラメータを追加するだけです。スコープパラメータは、`$query`パラメータの後に定義する必要があります。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Builder;use Illuminate\Database\Eloquent\Model; class User extends Model{ /** * Scope a query to only include users of a given type. */ public function scopeOfType(Builder $query, string $type): void { $query->where('type', $type); }}
必要な引数がスコープメソッドのシグネチャに追加されると、スコープを呼び出すときに引数を渡すことができます。
$users = User::ofType('admin')->get();
モデルの比較
2つのモデルが「同じ」かどうかを判断する必要がある場合があります。 `is` および `isNot` メソッドを使用して、2つのモデルが同じ主キー、テーブル、およびデータベース接続を持っているかどうかをすばやく確認できます。
if ($post->is($anotherPost)) { // ...} if ($post->isNot($anotherPost)) { // ...}
`is` および `isNot` メソッドは、`belongsTo`、`hasOne`、`morphTo`、および `morphOne` リレーションシップを使用する場合にも使用できます。このメソッドは、そのモデルを取得するためのクエリを発行せずに、関連するモデルを比較したい場合に特に役立ちます。
if ($post->author()->is($user)) { // ...}
イベント
Eloquentイベントをクライアントサイドアプリケーションに直接ブロードキャストしますか? Laravelのモデルイベントブロードキャストをご覧ください。
Eloquentモデルはいくつかのイベントをディスパッチし、モデルのライフサイクルの次の瞬間にフックすることができます:`retrieved`、`creating`、`created`、`updating`、`updated`、`saving`、`saved`、`deleting`、`deleted`、`trashed`、`forceDeleting`、`forceDeleted`、`restoring`、`restored`、および`replicating`。
`retrieved`イベントは、既存のモデルがデータベースから取得されたときにディスパッチされます。新しいモデルが初めて保存されると、`creating`および`created`イベントがディスパッチされます。既存のモデルが変更され、`save`メソッドが呼び出されると、`updating` / `updated`イベントがディスパッチされます。 `saving` / `saved`イベントは、モデルの属性が変更されていない場合でも、モデルが作成または更新されたときにディスパッチされます。 `-ing`で終わるイベント名は、モデルへの変更が永続化される前にディスパッチされ、`-ed`で終わるイベント名は、モデルへの変更が永続化された後にディスパッチされます。
モデルイベントのリスニングを開始するには、Eloquentモデルに`$dispatchesEvents`プロパティを定義します。このプロパティは、Eloquentモデルのライフサイクルのさまざまなポイントを独自のイベントクラスにマップします。各モデルイベントクラスは、コンストラクタを介して影響を受けるモデルのインスタンスを受け取ることを期待する必要があります。
<?php namespace App\Models; use App\Events\UserDeleted;use App\Events\UserSaved;use Illuminate\Foundation\Auth\User as Authenticatable;use Illuminate\Notifications\Notifiable; class User extends Authenticatable{ use Notifiable; /** * The event map for the model. * * @var array<string, string> */ protected $dispatchesEvents = [ 'saved' => UserSaved::class, 'deleted' => UserDeleted::class, ];}
Eloquentイベントを定義してマッピングした後、イベントリスナーを使用してイベントを処理できます。
Eloquentを介して一括更新または削除クエリを発行する場合、影響を受けるモデルに対して`saved`、`updated`、`deleting`、および`deleted`モデルイベントはディスパッチされません。これは、一括更新または削除を実行するときにモデルが実際に取得されないためです。
クロージャの使用
カスタムイベントクラスを使用する代わりに、さまざまなモデルイベントがディスパッチされたときに実行されるクロージャを登録できます。通常、これらのクロージャはモデルの`booted`メソッドに登録する必要があります。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class User extends Model{ /** * The "booted" method of the model. */ protected static function booted(): void { static::created(function (User $user) { // ... }); }}
必要に応じて、モデルイベントを登録するときにキュー可能な匿名イベントリスナーを使用できます。これにより、Laravelはアプリケーションのキューを使用してバックグラウンドでモデルイベントリスナーを実行するように指示します。
use function Illuminate\Events\queueable; static::created(queueable(function (User $user) { // ...}));
オブザーバー
オブザーバーの定義
特定のモデルで多くのイベントをリッスンしている場合は、オブザーバーを使用してすべてのリスナーを単一のクラスにグループ化できます。オブザーバークラスには、リッスンするEloquentイベントを反映したメソッド名があります。これらの各メソッドは、影響を受けるモデルを唯一の引数として受け取ります。 `make:observer` Artisanコマンドは、新しいオブザーバークラスを作成する最も簡単な方法です。
php artisan make:observer UserObserver --model=User
このコマンドは、新しいオブザーバーを`app/Observers`ディレクトリに配置します。このディレクトリが存在しない場合、Artisanはそれを自動的に作成します。新しいオブザーバーは次のようになります。
<?php namespace App\Observers; use App\Models\User; class UserObserver{ /** * Handle the User "created" event. */ public function created(User $user): void { // ... } /** * Handle the User "updated" event. */ public function updated(User $user): void { // ... } /** * Handle the User "deleted" event. */ public function deleted(User $user): void { // ... } /** * Handle the User "restored" event. */ public function restored(User $user): void { // ... } /** * Handle the User "forceDeleted" event. */ public function forceDeleted(User $user): void { // ... }}
オブザーバーを登録するには、対応するモデルに`ObservedBy`属性を配置します。
use App\Observers\UserObserver;use Illuminate\Database\Eloquent\Attributes\ObservedBy; #[ObservedBy([UserObserver::class])]class User extends Authenticatable{ //}
または、監視するモデルで`observe`メソッドを呼び出すことによって、オブザーバーを手動で登録できます。アプリケーションの`AppServiceProvider`クラスの`boot`メソッドにオブザーバーを登録できます。
use App\Models\User;use App\Observers\UserObserver; /** * Bootstrap any application services. */public function boot(): void{ User::observe(UserObserver::class);}
オブザーバーがリッスンできる追加のイベント(`saving`や`retrieved`など)があります。これらのイベントについては、イベントドキュメントで説明されています。
オブザーバーとデータベーストランザクション
データベーストランザクション内でモデルが作成されている場合、データベーストランザクションがコミットされた後にのみイベントハンドラーを実行するようにオブザーバーに指示することができます。これは、オブザーバーに`ShouldHandleEventsAfterCommit`インターフェースを実装することで実現できます。データベーストランザクションが進行中でない場合、イベントハンドラーはすぐに実行されます。
<?php namespace App\Observers; use App\Models\User;use Illuminate\Contracts\Events\ShouldHandleEventsAfterCommit; class UserObserver implements ShouldHandleEventsAfterCommit{ /** * Handle the User "created" event. */ public function created(User $user): void { // ... }}
イベントのミュート
モデルによって発生したすべてのイベントを一時的に「ミュート」する必要がある場合があります。これは、`withoutEvents`メソッドを使用して実現できます。 `withoutEvents`メソッドは、クロージャを唯一の引数として受け入れます。このクロージャ内で実行されたコードはモデルイベントをディスパッチせず、クロージャによって返された値は`withoutEvents`メソッドによって返されます。
use App\Models\User; $user = User::withoutEvents(function () { User::findOrFail(1)->delete(); return User::find(2);});
イベントなしで単一モデルの保存
イベントをディスパッチせずに特定のモデルを「保存」したい場合があります。これは、`saveQuietly`メソッドを使用して実現できます。
$user = User::findOrFail(1); $user->name = 'Victoria Faith'; $user->saveQuietly();
また、イベントをディスパッチせずに、特定のモデルを「更新」、「削除」、「ソフトデリート」、「復元」、および「複製」することもできます。
$user->deleteQuietly();$user->forceDeleteQuietly();$user->restoreQuietly();