コンテンツへスキップ

Eloquent: 利用の開始

イントロダクション

Laravelは、データベースとの対話を楽しくするオブジェクトリレーショナルマッパー(ORM)であるEloquentを搭載しています。Eloquentを使用する場合、各データベーステーブルには対応する「モデル」があり、そのテーブルを操作するために使用します。データベーステーブルからレコードを取得することに加えて、Eloquentモデルでは、テーブルへのレコードの挿入、更新、削除も可能です。

利用を開始する前に、アプリケーションのconfig/database.php設定ファイルでデータベース接続を設定してください。データベースの設定に関する詳細は、データベース設定のドキュメントを確認してください。

モデルクラスの生成

それでは、Eloquentモデルを作成してみましょう。モデルは通常app\Modelsディレクトリにあり、Illuminate\Database\Eloquent\Modelクラスを継承します。make:model Artisanコマンドを使用して新しいモデルを生成できます。

1php artisan make:model Flight

モデルを生成するときにデータベースマイグレーションを生成したい場合は、--migrationまたは-mオプションを使用します。

1php artisan make:model Flight --migration

モデルを生成する際に、ファクトリ、シーダ、ポリシー、コントローラ、フォームリクエストなど、他のさまざまなタイプのクラスを生成できます。さらに、これらのオプションを組み合わせて、一度に複数のクラスを作成することもできます。

1# Generate a model and a FlightFactory class...
2php artisan make:model Flight --factory
3php artisan make:model Flight -f
4 
5# Generate a model and a FlightSeeder class...
6php artisan make:model Flight --seed
7php artisan make:model Flight -s
8 
9# Generate a model and a FlightController class...
10php artisan make:model Flight --controller
11php artisan make:model Flight -c
12 
13# Generate a model, FlightController resource class, and form request classes...
14php artisan make:model Flight --controller --resource --requests
15php artisan make:model Flight -crR
16 
17# Generate a model and a FlightPolicy class...
18php artisan make:model Flight --policy
19 
20# Generate a model and a migration, factory, seeder, and controller...
21php artisan make:model Flight -mfsc
22 
23# Shortcut to generate a model, migration, factory, seeder, policy, controller, and form requests...
24php artisan make:model Flight --all
25php artisan make:model Flight -a
26 
27# Generate a pivot model...
28php artisan make:model Member --pivot
29php artisan make:model Member -p

モデルの調査

モデルのコードをざっと見ただけでは、利用可能なすべての属性とリレーションシップを判断するのが難しい場合があります。代わりに、model:show Artisanコマンドを試してみてください。このコマンドは、モデルのすべての属性とリレーションの便利な概要を提供します。

1php artisan model:show Flight

Eloquentモデルの規約

make:modelコマンドによって生成されたモデルは、app/Modelsディレクトリに配置されます。基本的なモデルクラスを調べて、Eloquentの主要な規約のいくつかを説明しましょう。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6 
7class Flight extends Model
8{
9 // ...
10}

テーブル名

上記の例を一瞥して、Flightモデルに対応するデータベーステーブルをEloquentに指示していないことに気づいたかもしれません。規約により、別の名前が明示的に指定されない限り、クラスの「スネークケース」の複数形がテーブル名として使用されます。したがって、この場合、EloquentはFlightモデルがflightsテーブルにレコードを保存すると想定し、AirTrafficControllerモデルはair_traffic_controllersテーブルにレコードを保存します。

モデルに対応するデータベーステーブルがこの規約に合わない場合は、モデルにtableプロパティを定義することで、モデルのテーブル名を手動で指定できます。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6 
7class Flight extends Model
8{
9 /**
10 * The table associated with the model.
11 *
12 * @var string
13 */
14 protected $table = 'my_flights';
15}

主キー

Eloquentは、各モデルに対応するデータベーステーブルにidという名前の主キーカラムがあると想定します。必要に応じて、モデルでprotectedな$primaryKeyプロパティを定義して、モデルの主キーとして機能する別のカラムを指定できます。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6 
7class Flight extends Model
8{
9 /**
10 * The primary key associated with the table.
11 *
12 * @var string
13 */
14 protected $primaryKey = 'flight_id';
15}

さらに、Eloquentは主キーがインクリメントする整数値であると想定しています。つまり、Eloquentは主キーを自動的に整数にキャストします。インクリメントしない、または数値以外の主キーを使用する場合は、モデルでpublicな$incrementingプロパティをfalseに設定して定義する必要があります。

1<?php
2 
3class Flight extends Model
4{
5 /**
6 * Indicates if the model's ID is auto-incrementing.
7 *
8 * @var bool
9 */
10 public $incrementing = false;
11}

モデルの主キーが整数でない場合は、モデルでprotectedな$keyTypeプロパティを定義する必要があります。このプロパティにはstringの値を設定する必要があります。

1<?php
2 
3class Flight extends Model
4{
5 /**
6 * The data type of the primary key ID.
7 *
8 * @var string
9 */
10 protected $keyType = 'string';
11}

「複合」主キー

Eloquentでは、各モデルに主キーとして機能できる一意に識別する「ID」が少なくとも1つ必要です。Eloquentモデルでは「複合」主キーはサポートされていません。ただし、テーブルの一意に識別する主キーに加えて、データベーステーブルに追加の複数カラムの一意なインデックスを自由に追加できます。

UUIDとULIDキー

Eloquentモデルの主キーとして自動インクリメント整数を使用する代わりに、UUIDを使用することを選択できます。UUIDは、長さ36文字の汎用一意識別子です。

モデルで自動インクリメント整数キーの代わりにUUIDキーを使用したい場合は、モデルでIlluminate\Database\Eloquent\Concerns\HasUuidsトレイトを使用できます。もちろん、モデルにUUID相当の主キーカラムがあることを確認する必要があります。

1use Illuminate\Database\Eloquent\Concerns\HasUuids;
2use Illuminate\Database\Eloquent\Model;
3 
4class Article extends Model
5{
6 use HasUuids;
7 
8 // ...
9}
10 
11$article = Article::create(['title' => 'Traveling to Europe']);
12 
13$article->id; // "8f8e8478-9035-4d23-b9a7-62f4d2612ce5"

デフォルトで、HasUuidsトレイトはモデルに「順序付き」UUIDを生成します。これらのUUIDは、辞書式にソートできるため、インデックス付きデータベースストレージにとってより効率的です。

特定のモデルのUUID生成プロセスをオーバーライドするには、モデルにnewUniqueIdメソッドを定義します。さらに、モデルにuniqueIdsメソッドを定義することで、UUIDを受け取るカラムを指定できます。

1use Ramsey\Uuid\Uuid;
2 
3/**
4 * Generate a new UUID for the model.
5 */
6public function newUniqueId(): string
7{
8 return (string) Uuid::uuid4();
9}
10 
11/**
12 * Get the columns that should receive a unique identifier.
13 *
14 * @return array<int, string>
15 */
16public function uniqueIds(): array
17{
18 return ['id', 'discount_code'];
19}

必要に応じて、UUIDの代わりに「ULID」を利用することもできます。ULIDはUUIDに似ていますが、長さは26文字しかありません。順序付きUUIDと同様に、ULIDは効率的なデータベースインデックス作成のために辞書式にソートできます。ULIDを利用するには、モデルでIlluminate\Database\Eloquent\Concerns\HasUlidsトレイトを使用する必要があります。また、モデルにULID相当の主キーカラムがあることも確認する必要があります。

1use Illuminate\Database\Eloquent\Concerns\HasUlids;
2use Illuminate\Database\Eloquent\Model;
3 
4class Article extends Model
5{
6 use HasUlids;
7 
8 // ...
9}
10 
11$article = Article::create(['title' => 'Traveling to Asia']);
12 
13$article->id; // "01gd4d3tgrrfqeda94gdbtdk5c"

タイムスタンプ

デフォルトで、Eloquentはモデルに対応するデータベーステーブルにcreated_atカラムとupdated_atカラムが存在することを期待します。Eloquentは、モデルが作成または更新されるときに、これらのカラムの値を自動的に設定します。Eloquentにこれらのカラムを自動的に管理させたくない場合は、モデルで$timestampsプロパティをfalseの値で定義する必要があります。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6 
7class Flight extends Model
8{
9 /**
10 * Indicates if the model should be timestamped.
11 *
12 * @var bool
13 */
14 public $timestamps = false;
15}

モデルのタイムスタンプの形式をカスタマイズする必要がある場合は、モデルで$dateFormatプロパティを設定します。このプロパティは、日付属性がデータベースにどのように保存されるか、およびモデルが配列またはJSONにシリアライズされるときの形式を決定します。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6 
7class Flight extends Model
8{
9 /**
10 * The storage format of the model's date columns.
11 *
12 * @var string
13 */
14 protected $dateFormat = 'U';
15}

タイムスタンプを保存するために使用するカラムの名前をカスタマイズする必要がある場合は、モデルでCREATED_AT定数とUPDATED_AT定数を定義できます。

1<?php
2 
3class Flight extends Model
4{
5 const CREATED_AT = 'creation_date';
6 const UPDATED_AT = 'updated_date';
7}

モデルのupdated_atタイムスタンプを変更せずにモデル操作を実行したい場合は、withoutTimestampsメソッドに渡されたクロージャ内でモデルを操作できます。

1Model::withoutTimestamps(fn () => $post->increment('reads'));

データベース接続

デフォルトでは、すべてのEloquentモデルは、アプリケーション用に設定されたデフォルトのデータベース接続を使用します。特定のモデルを操作するときに使用する別の接続を指定したい場合は、モデルで$connectionプロパティを定義する必要があります。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6 
7class Flight extends Model
8{
9 /**
10 * The database connection that should be used by the model.
11 *
12 * @var string
13 */
14 protected $connection = 'mysql';
15}

属性のデフォルト値

デフォルトでは、新しくインスタンス化されたモデルインスタンスには属性値が含まれていません。モデルの一部の属性のデフォルト値を定義したい場合は、モデルで$attributesプロパティを定義できます。$attributes配列に配置される属性値は、データベースから読み取られたばかりであるかのように、生の「保存可能」な形式である必要があります。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6 
7class Flight extends Model
8{
9 /**
10 * The model's default values for attributes.
11 *
12 * @var array
13 */
14 protected $attributes = [
15 'options' => '[]',
16 'delayed' => false,
17 ];
18}

Eloquentの厳格性の設定

Laravelは、さまざまな状況でEloquentの動作と「厳格性」を設定できるいくつかのメソッドを提供しています。

まず、preventLazyLoadingメソッドは、遅延読み込みを防止するかどうかを示すオプションのブール引数を受け入れます。たとえば、本番環境では遅延読み込みされたリレーションシップが誤って本番コードに存在していても正常に機能し続けるように、非本番環境でのみ遅延読み込みを無効にしたい場合があります。通常、このメソッドはアプリケーションのAppServiceProviderbootメソッドで呼び出す必要があります。

1use Illuminate\Database\Eloquent\Model;
2 
3/**
4 * Bootstrap any application services.
5 */
6public function boot(): void
7{
8 Model::preventLazyLoading(! $this->app->isProduction());
9}

また、preventSilentlyDiscardingAttributesメソッドを呼び出すことで、fillableでない属性を埋めようとしたときにLaravelに例外をスローさせることができます。これにより、モデルのfillable配列に追加されていない属性を設定しようとしたときのローカル開発中の予期しないエラーを防ぐのに役立ちます。

1Model::preventSilentlyDiscardingAttributes(! $this->app->isProduction());

モデルの取得

モデルとそれに関連するデータベーステーブルを作成したら、データベースからデータを取得する準備が整いました。各Eloquentモデルは、モデルに関連付けられたデータベーステーブルを流暢にクエリできる強力なクエリビルダと考えることができます。モデルのallメソッドは、モデルに関連付けられたデータベーステーブルからすべてのレコードを取得します。

1use App\Models\Flight;
2 
3foreach (Flight::all() as $flight) {
4 echo $flight->name;
5}

クエリの構築

Eloquentのallメソッドは、モデルのテーブルのすべての結果を返します。ただし、各Eloquentモデルはクエリビルダとして機能するため、クエリに追加の制約を追加し、getメソッドを呼び出して結果を取得できます。

1$flights = Flight::where('active', 1)
2 ->orderBy('name')
3 ->take(10)
4 ->get();

Eloquentモデルはクエリビルダであるため、Laravelのクエリビルダが提供するすべてのメソッドを確認する必要があります。Eloquentクエリを作成する際に、これらのメソッドのいずれかを使用できます。

モデルの再取得

データベースから取得したEloquentモデルのインスタンスが既にある場合は、freshメソッドとrefreshメソッドを使用してモデルを「再取得」できます。freshメソッドは、データベースからモデルを再取得します。既存のモデルインスタンスは影響を受けません。

1$flight = Flight::where('number', 'FR 900')->first();
2 
3$freshFlight = $flight->fresh();

refreshメソッドは、データベースからの新しいデータを使用して既存のモデルを再ハイドレートします。さらに、ロードされたすべてのリレーションシップも再取得されます。

1$flight = Flight::where('number', 'FR 900')->first();
2 
3$flight->number = 'FR 456';
4 
5$flight->refresh();
6 
7$flight->number; // "FR 900"

コレクション

これまで見てきたように、allgetなどのEloquentメソッドは、データベースから複数のレコードを取得します。ただし、これらのメソッドはプレーンなPHP配列を返しません。代わりに、Illuminate\Database\Eloquent\Collectionのインスタンスが返されます。

EloquentのCollectionクラスは、LaravelのベースのIlluminate\Support\Collectionクラスを拡張し、データコレクションを操作するためのさまざまな便利なメソッドを提供します。たとえば、rejectメソッドを使用して、呼び出されたクロージャの結果に基づいてコレクションからモデルを削除できます。

1$flights = Flight::where('destination', 'Paris')->get();
2 
3$flights = $flights->reject(function (Flight $flight) {
4 return $flight->cancelled;
5});

Laravelのベースコレクションクラスが提供するメソッドに加えて、Eloquentコレクションクラスは、Eloquentモデルのコレクションを操作するために特別に意図されたいくつかの追加メソッドを提供します。

LaravelのすべてのコレクションはPHPの反復可能なインターフェイスを実装しているため、コレクションを配列のようにループできます。

1foreach ($flights as $flight) {
2 echo $flight->name;
3}

結果のチャンク(分割取得)

allメソッドやgetメソッドを介して数万のEloquentレコードをロードしようとすると、アプリケーションがメモリ不足になる可能性があります。これらのメソッドを使用する代わりに、chunkメソッドを使用して大量のモデルをより効率的に処理できます。

chunkメソッドはEloquentモデルのサブセットを取得し、処理のためにクロージャに渡します。一度に現在のEloquentモデルのチャンクのみが取得されるため、chunkメソッドは大量のモデルを扱う際のメモリ使用量を大幅に削減します。

1use App\Models\Flight;
2use Illuminate\Database\Eloquent\Collection;
3 
4Flight::chunk(200, function (Collection $flights) {
5 foreach ($flights as $flight) {
6 // ...
7 }
8});

chunkメソッドに渡される最初の引数は、「チャンク」ごとに受け取りたいレコード数です。2番目の引数として渡されるクロージャは、データベースから取得された各チャンクに対して呼び出されます。各チャンクのレコードを取得するためにデータベースクエリが実行され、クロージャに渡されます。

結果を反復処理しながら更新も行うカラムに基づいてchunkメソッドの結果をフィルタリングしている場合は、chunkByIdメソッドを使用する必要があります。このようなシナリオでchunkメソッドを使用すると、予期しない一貫性のない結果につながる可能性があります。内部的に、chunkByIdメソッドは常に前のチャンクの最後のモデルより大きいidカラムを持つモデルを取得します。

1Flight::where('departed', true)
2 ->chunkById(200, function (Collection $flights) {
3 $flights->each->update(['departed' => false]);
4 }, column: 'id');

chunkByIdおよびlazyByIdメソッドは、実行されるクエリに独自の「where」条件を追加するため、通常、独自の条件をクロージャ内で論理的にグループ化する必要があります。

1Flight::where(function ($query) {
2 $query->where('delayed', true)->orWhere('cancelled', true);
3})->chunkById(200, function (Collection $flights) {
4 $flights->each->update([
5 'departed' => false,
6 'cancelled' => true
7 ]);
8}, column: 'id');

Lazy Collectionを使用したチャンク

lazyメソッドは、舞台裏でクエリをチャンクで実行するという点で、chunkメソッドと同様に機能します。ただし、各チャンクをコールバックに直接渡す代わりに、lazyメソッドはEloquentモデルのフラット化されたLazyCollectionを返し、結果を単一のストリームとして操作できます。

1use App\Models\Flight;
2 
3foreach (Flight::lazy() as $flight) {
4 // ...
5}

結果を反復処理しながら更新も行うカラムに基づいてlazyメソッドの結果をフィルタリングしている場合は、lazyByIdメソッドを使用する必要があります。内部的に、lazyByIdメソッドは常に前のチャンクの最後のモデルより大きいidカラムを持つモデルを取得します。

1Flight::where('departed', true)
2 ->lazyById(200, column: 'id')
3 ->each->update(['departed' => false]);

lazyByIdDescメソッドを使用して、idの降順で結果をフィルタリングできます。

カーソル

lazyメソッドと同様に、cursorメソッドを使用すると、数万のEloquentモデルレコードを反復処理する際のアプリケーションのメモリ消費量を大幅に削減できます。

cursorメソッドは単一のデータベースクエリしか実行しません。ただし、個々のEloquentモデルは、実際に反復処理されるまでハイドレートされません。したがって、カーソルを反復処理している間、メモリには常に1つのEloquentモデルしか保持されません。

cursorメソッドは一度に1つのEloquentモデルしかメモリに保持しないため、リレーションシップをEagerロードすることはできません。リレーションシップをEagerロードする必要がある場合は、代わりにlazyメソッドの使用を検討してください。

内部的に、cursorメソッドはこの機能を実装するためにPHPのジェネレータを使用します。

1use App\Models\Flight;
2 
3foreach (Flight::where('destination', 'Zurich')->cursor() as $flight) {
4 // ...
5}

cursorIlluminate\Support\LazyCollectionインスタンスを返します。Lazy Collectionを使用すると、一度に1つのモデルのみをメモリにロードしながら、通常のLaravelコレクションで利用可能な多くのコレクションメソッドを使用できます。

1use App\Models\User;
2 
3$users = User::cursor()->filter(function (User $user) {
4 return $user->id > 500;
5});
6 
7foreach ($users as $user) {
8 echo $user->id;
9}

cursorメソッドは通常のクエリよりもはるかに少ないメモリを使用しますが(一度に1つのEloquentモデルしかメモリに保持しないため)、それでも最終的にはメモリ不足になります。これは、PHPのPDOドライバが内部的にすべての生のクエリ結果をバッファにキャッシュするためです。非常に大量のEloquentレコードを扱っている場合は、代わりにlazyメソッドの使用を検討してください。

高度なサブクエリ

サブクエリのSELECT

Eloquentは高度なサブクエリサポートも提供しており、単一のクエリで関連テーブルから情報を取得できます。たとえば、フライトのdestinationsテーブルと、目的地へのflightsテーブルがあるとします。flightsテーブルには、フライトが目的地に到着した日時を示すarrived_atカラムが含まれています。

クエリビルダのselectメソッドとaddSelectメソッドで利用可能なサブクエリ機能を使用して、すべてのdestinationsと、その目的地に最も最近到着したフライトの名前を単一のクエリで選択できます。

1use App\Models\Destination;
2use App\Models\Flight;
3 
4return Destination::addSelect(['last_flight' => Flight::select('name')
5 ->whereColumn('destination_id', 'destinations.id')
6 ->orderByDesc('arrived_at')
7 ->limit(1)
8])->get();

サブクエリのORDER BY

さらに、クエリビルダのorderBy関数はサブクエリをサポートしています。フライトの例を続けると、この機能を使用して、最後のフライトがその目的地に到着した日時に基づいてすべての目的地を並べ替えることができます。これもまた、単一のデータベースクエリを実行しながら行うことができます。

1return Destination::orderByDesc(
2 Flight::select('arrived_at')
3 ->whereColumn('destination_id', 'destinations.id')
4 ->orderByDesc('arrived_at')
5 ->limit(1)
6)->get();

単一モデル/集計の取得

特定のクエリに一致するすべてのレコードを取得することに加えて、findfirst、またはfirstWhereメソッドを使用して単一のレコードを取得することもできます。モデルのコレクションを返す代わりに、これらのメソッドは単一のモデルインスタンスを返します。

1use App\Models\Flight;
2 
3// Retrieve a model by its primary key...
4$flight = Flight::find(1);
5 
6// Retrieve the first model matching the query constraints...
7$flight = Flight::where('active', 1)->first();
8 
9// Alternative to retrieving the first model matching the query constraints...
10$flight = Flight::firstWhere('active', 1);

結果が見つからなかった場合に他のアクションを実行したい場合があります。findOrメソッドとfirstOrメソッドは、単一のモデルインスタンスを返すか、結果が見つからない場合は指定されたクロージャを実行します。クロージャによって返された値は、メソッドの結果と見なされます。

1$flight = Flight::findOr(1, function () {
2 // ...
3});
4 
5$flight = Flight::where('legs', '>', 3)->firstOr(function () {
6 // ...
7});

Not Found例外

モデルが見つからない場合に例外をスローしたい場合があります。これは、ルートやコントローラで特に便利です。findOrFailメソッドとfirstOrFailメソッドはクエリの最初の結果を取得しますが、結果が見つからない場合はIlluminate\Database\Eloquent\ModelNotFoundExceptionがスローされます。

1$flight = Flight::findOrFail(1);
2 
3$flight = Flight::where('legs', '>', 3)->firstOrFail();

ModelNotFoundExceptionがキャッチされない場合、404 HTTPレスポンスが自動的にクライアントに返送されます。

1use App\Models\Flight;
2 
3Route::get('/api/flights/{id}', function (string $id) {
4 return Flight::findOrFail($id);
5});

モデルの取得または作成

firstOrCreateメソッドは、指定されたカラム/値のペアを使用してデータベースレコードの検索を試みます。データベースでモデルが見つからない場合は、最初の配列引数とオプションの2番目の配列引数をマージした結果の属性を持つレコードが挿入されます。

firstOrNewメソッドは、firstOrCreateと同様に、指定された属性に一致するデータベース内のレコードを検索しようとします。ただし、モデルが見つからない場合は、新しいモデルインスタンスが返されます。firstOrNewによって返されたモデルは、まだデータベースに永続化されていないことに注意してください。永続化するには、手動でsaveメソッドを呼び出す必要があります。

1use App\Models\Flight;
2 
3// Retrieve flight by name or create it if it doesn't exist...
4$flight = Flight::firstOrCreate([
5 'name' => 'London to Paris'
6]);
7 
8// Retrieve flight by name or create it with the name, delayed, and arrival_time attributes...
9$flight = Flight::firstOrCreate(
10 ['name' => 'London to Paris'],
11 ['delayed' => 1, 'arrival_time' => '11:30']
12);
13 
14// Retrieve flight by name or instantiate a new Flight instance...
15$flight = Flight::firstOrNew([
16 'name' => 'London to Paris'
17]);
18 
19// Retrieve flight by name or instantiate with the name, delayed, and arrival_time attributes...
20$flight = Flight::firstOrNew(
21 ['name' => 'Tokyo to Sydney'],
22 ['delayed' => 1, 'arrival_time' => '11:30']
23);

集計の取得

Eloquentモデルを操作するときは、Laravelのクエリビルダが提供するcountsummax、およびその他の集計メソッドを使用することもできます。ご想像のとおり、これらのメソッドはEloquentモデルインスタンスではなくスカラー値を返します。

1$count = Flight::where('active', 1)->count();
2 
3$max = Flight::where('active', 1)->max('price');

モデルの挿入と更新

挿入

もちろん、Eloquentを使用する場合、データベースからモデルを取得するだけでなく、新しいレコードを挿入する必要もあります。幸いなことに、Eloquentはそれを簡単にします。データベースに新しいレコードを挿入するには、新しいモデルインスタンスをインスタンス化し、モデルに属性を設定する必要があります。次に、モデルインスタンスでsaveメソッドを呼び出します。

1<?php
2 
3namespace App\Http\Controllers;
4 
5use App\Http\Controllers\Controller;
6use App\Models\Flight;
7use Illuminate\Http\RedirectResponse;
8use Illuminate\Http\Request;
9 
10class FlightController extends Controller
11{
12 /**
13 * Store a new flight in the database.
14 */
15 public function store(Request $request): RedirectResponse
16 {
17 // Validate the request...
18 
19 $flight = new Flight;
20 
21 $flight->name = $request->name;
22 
23 $flight->save();
24 
25 return redirect('/flights');
26 }
27}

この例では、受信HTTPリクエストのnameフィールドをApp\Models\Flightモデルインスタンスのname属性に代入しています。saveメソッドを呼び出すと、レコードがデータベースに挿入されます。モデルのcreated_atタイムスタンプとupdated_atタイムスタンプは、saveメソッドが呼び出されると自動的に設定されるため、手動で設定する必要はありません。

あるいは、createメソッドを使用して、単一のPHPステートメントで新しいモデルを「保存」することもできます。挿入されたモデルインスタンスは、createメソッドによって返されます。

1use App\Models\Flight;
2 
3$flight = Flight::create([
4 'name' => 'London to Paris',
5]);

ただし、createメソッドを使用する前に、モデルクラスでfillableプロパティまたはguardedプロパティのいずれかを指定する必要があります。すべてのEloquentモデルはデフォルトで複数代入の脆弱性から保護されているため、これらのプロパティは必須です。複数代入の詳細については、複数代入のドキュメントを参照してください。

更新

saveメソッドは、データベースに既に存在するモデルを更新するためにも使用できます。モデルを更新するには、それを取得し、更新したい属性を設定する必要があります。次に、モデルのsaveメソッドを呼び出す必要があります。繰り返しますが、updated_atタイムスタンプは自動的に更新されるため、手動でその値を設定する必要はありません。

1use App\Models\Flight;
2 
3$flight = Flight::find(1);
4 
5$flight->name = 'Paris to London';
6 
7$flight->save();

場合によっては、既存のモデルを更新するか、一致するモデルが存在しない場合は新しいモデルを作成する必要があります。firstOrCreateメソッドと同様に、updateOrCreateメソッドはモデルを永続化するため、手動でsaveメソッドを呼び出す必要はありません。

以下の例では、departureの場所がOaklandで、destinationの場所がSan Diegoのフライトが存在する場合、そのpriceカラムとdiscountedカラムが更新されます。そのようなフライトが存在しない場合は、最初の引数配列と2番目の引数配列をマージした結果の属性を持つ新しいフライトが作成されます。

1$flight = Flight::updateOrCreate(
2 ['departure' => 'Oakland', 'destination' => 'San Diego'],
3 ['price' => 99, 'discounted' => 1]
4);

一括更新

特定のクエリに一致するモデルに対して更新を実行することもできます。この例では、activedestinationSan Diegoのすべてのフライトが遅延としてマークされます。

1Flight::where('active', 1)
2 ->where('destination', 'San Diego')
3 ->update(['delayed' => 1]);

updateメソッドは、更新するカラムを表すカラムと値のペアの配列を期待します。updateメソッドは、影響を受けた行数を返します。

Eloquentを介して一括更新を発行する場合、更新されたモデルに対してsavingsavedupdating、およびupdatedモデルイベントは発行されません。これは、一括更新を発行する際にモデルが実際には取得されないためです。

属性の変更の確認

Eloquentは、モデルの内部状態を調べて、モデルが最初に取得されたときから属性がどのように変更されたかを判断するためのisDirtyisClean、およびwasChangedメソッドを提供します。

isDirtyメソッドは、モデルが取得されてからモデルの属性のいずれかが変更されたかどうかを判断します。特定の属性名または属性の配列をisDirtyメソッドに渡して、いずれかの属性が「dirty」であるかどうかを判断できます。isCleanメソッドは、モデルが取得されてから属性が変更されていないかどうかを判断します。このメソッドは、オプションの属性引数も受け入れます。

1use App\Models\User;
2 
3$user = User::create([
4 'first_name' => 'Taylor',
5 'last_name' => 'Otwell',
6 'title' => 'Developer',
7]);
8 
9$user->title = 'Painter';
10 
11$user->isDirty(); // true
12$user->isDirty('title'); // true
13$user->isDirty('first_name'); // false
14$user->isDirty(['first_name', 'title']); // true
15 
16$user->isClean(); // false
17$user->isClean('title'); // false
18$user->isClean('first_name'); // true
19$user->isClean(['first_name', 'title']); // false
20 
21$user->save();
22 
23$user->isDirty(); // false
24$user->isClean(); // true

wasChangedメソッドは、現在のリクエストサイクル内でモデルが最後に保存されたときに属性が変更されたかどうかを判断します。必要に応じて、属性名を渡して、特定の属性が変更されたかどうかを確認できます。

1$user = User::create([
2 'first_name' => 'Taylor',
3 'last_name' => 'Otwell',
4 'title' => 'Developer',
5]);
6 
7$user->title = 'Painter';
8 
9$user->save();
10 
11$user->wasChanged(); // true
12$user->wasChanged('title'); // true
13$user->wasChanged(['title', 'slug']); // true
14$user->wasChanged('first_name'); // false
15$user->wasChanged(['first_name', 'title']); // true

getOriginalメソッドは、モデルが取得されてからのモデルへの変更に関係なく、モデルの元の属性を含む配列を返します。必要に応じて、特定の属性名を渡して、特定の属性の元の値を取得できます。

1$user = User::find(1);
2 
3$user->name; // John
4$user->email; // [email protected]
5 
6$user->name = "Jack";
7$user->name; // Jack
8 
9$user->getOriginal('name'); // John
10$user->getOriginal(); // Array of original attributes...

複数代入

createメソッドを使用して、単一のPHPステートメントで新しいモデルを「保存」できます。挿入されたモデルインスタンスは、メソッドによって返されます。

1use App\Models\Flight;
2 
3$flight = Flight::create([
4 'name' => 'London to Paris',
5]);

ただし、createメソッドを使用する前に、モデルクラスでfillableプロパティまたはguardedプロパティのいずれかを指定する必要があります。すべてのEloquentモデルはデフォルトで複数代入の脆弱性から保護されているため、これらのプロパティは必須です。

複数代入の脆弱性は、ユーザーが予期しないHTTPリクエストフィールドを渡し、そのフィールドがデータベースの予期しないカラムを変更した場合に発生します。たとえば、悪意のあるユーザーがHTTPリクエストを介してis_adminパラメータを送信し、それがモデルのcreateメソッドに渡され、ユーザーが管理者に昇格できるようになる可能性があります。

そこで、まず、どのモデル属性を複数代入可能にするかを定義する必要があります。これは、モデルの$fillableプロパティを使用して行います。たとえば、Flightモデルのname属性を複数代入可能にしてみましょう。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6 
7class Flight extends Model
8{
9 /**
10 * The attributes that are mass assignable.
11 *
12 * @var array<int, string>
13 */
14 protected $fillable = ['name'];
15}

どの属性が複数代入可能であるかを指定したら、createメソッドを使用してデータベースに新しいレコードを挿入できます。createメソッドは、新しく作成されたモデルインスタンスを返します。

1$flight = Flight::create(['name' => 'London to Paris']);

既にモデルインスタンスがある場合は、fillメソッドを使用して属性の配列でそれを埋めることができます。

1$flight->fill(['name' => 'Amsterdam to Frankfurt']);

複数代入とJSONカラム

JSONカラムを代入する場合、各カラムの複数代入可能なキーをモデルの$fillable配列で指定する必要があります。セキュリティのため、Laravelはguardedプロパティを使用する場合のネストされたJSON属性の更新をサポートしていません。

1/**
2 * The attributes that are mass assignable.
3 *
4 * @var array<int, string>
5 */
6protected $fillable = [
7 'options->enabled',
8];

複数代入の許可

すべての属性を複数代入可能にしたい場合は、モデルの$guardedプロパティを空の配列として定義できます。モデルの保護を解除することを選択した場合は、Eloquentのfillcreateupdateメソッドに渡す配列を常に手作業で作成するように特に注意する必要があります。

1/**
2 * The attributes that aren't mass assignable.
3 *
4 * @var array<string>|bool
5 */
6protected $guarded = [];

複数代入の例外

デフォルトでは、$fillable配列に含まれていない属性は、複数代入操作を実行するときにサイレントに破棄されます。本番環境では、これは期待される動作です。ただし、ローカル開発中は、モデルの変更がなぜ有効にならないのかについて混乱を招く可能性があります。

必要に応じて、preventSilentlyDiscardingAttributesメソッドを呼び出すことにより、代入不可な属性を埋めようとしたときにLaravelに例外をスローするように指示できます。通常、このメソッドはアプリケーションのAppServiceProviderクラスのbootメソッドで呼び出す必要があります。

1use Illuminate\Database\Eloquent\Model;
2 
3/**
4 * Bootstrap any application services.
5 */
6public function boot(): void
7{
8 Model::preventSilentlyDiscardingAttributes($this->app->isLocal());
9}

Upsert

Eloquentのupsertメソッドを使用して、単一のアトミックな操作でレコードを更新または作成できます。メソッドの最初の引数は挿入または更新する値で構成され、2番目の引数は関連テーブル内のレコードを一意に識別するカラムをリストします。メソッドの3番目の最後の引数は、一致するレコードがデータベースに既に存在する場合に更新する必要があるカラムの配列です。upsertメソッドは、モデルでタイムスタンプが有効になっている場合、created_atタイムスタンプとupdated_atタイムスタンプを自動的に設定します。

1Flight::upsert([
2 ['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99],
3 ['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150]
4], uniqueBy: ['departure', 'destination'], update: ['price']);

SQL Serverを除くすべてのデータベースでは、upsertメソッドの2番目の引数のカラムに「primary」または「unique」インデックスが必要です。さらに、MariaDBおよびMySQLデータベースドライバは、upsertメソッドの2番目の引数を無視し、常にテーブルの「primary」および「unique」インデックスを使用して既存のレコードを検出します。

モデルの削除

モデルを削除するには、モデルインスタンスでdeleteメソッドを呼び出します。

1use App\Models\Flight;
2 
3$flight = Flight::find(1);
4 
5$flight->delete();

主キーによる既存のモデルの削除

上記の例では、deleteメソッドを呼び出す前にデータベースからモデルを取得しています。ただし、モデルの主キーがわかっている場合は、destroyメソッドを呼び出すことで、明示的に取得せずにモデルを削除できます。単一の主キーを受け入れることに加えて、destroyメソッドは、複数の主キー、主キーの配列、または主キーのコレクションを受け入れます。

1Flight::destroy(1);
2 
3Flight::destroy(1, 2, 3);
4 
5Flight::destroy([1, 2, 3]);
6 
7Flight::destroy(collect([1, 2, 3]));

ソフトデリートモデルを利用している場合は、forceDestroyメソッドを介してモデルを永久に削除できます。

1Flight::forceDestroy(1);

destroyメソッドは各モデルを個別にロードし、deleteメソッドを呼び出すため、各モデルに対してdeletingイベントとdeletedイベントが適切にディスパッチされます。

クエリを使用したモデルの削除

もちろん、Eloquentクエリを構築して、クエリの基準に一致するすべてのモデルを削除できます。この例では、非アクティブとしてマークされているすべてのフライトを削除します。一括更新と同様に、一括削除は削除されたモデルのモデルイベントをディスパッチしません。

1$deleted = Flight::where('active', 0)->delete();

テーブル内のすべてのモデルを削除するには、条件を追加せずにクエリを実行する必要があります。

1$deleted = Flight::query()->delete();

Eloquentを介して一括削除ステートメントを実行する場合、削除されたモデルに対してdeletingおよびdeletedモデルイベントはディスパッチされません。これは、削除ステートメントの実行時にモデルが実際には取得されないためです。

ソフトデリート

データベースから実際にレコードを削除することに加えて、Eloquentはモデルを「ソフトデリート」することもできます。モデルがソフトデリートされると、実際にはデータベースから削除されません。代わりに、モデルにdeleted_at属性が設定され、モデルが「削除」された日時が示されます。モデルのソフトデリートを有効にするには、モデルにIlluminate\Database\Eloquent\SoftDeletesトレイトを追加します。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6use Illuminate\Database\Eloquent\SoftDeletes;
7 
8class Flight extends Model
9{
10 use SoftDeletes;
11}

SoftDeletesトレイトは、deleted_at属性をDateTime / Carbonインスタンスに自動的にキャストします。

データベーステーブルにdeleted_atカラムも追加する必要があります。Laravel スキーマビルダには、このカラムを作成するためのヘルパーメソッドが含まれています。

1use Illuminate\Database\Schema\Blueprint;
2use Illuminate\Support\Facades\Schema;
3 
4Schema::table('flights', function (Blueprint $table) {
5 $table->softDeletes();
6});
7 
8Schema::table('flights', function (Blueprint $table) {
9 $table->dropSoftDeletes();
10});

これで、モデルでdeleteメソッドを呼び出すと、deleted_atカラムが現在の日時に設定されます。ただし、モデルのデータベースレコードはテーブルに残されます。ソフトデリートを使用するモデルをクエリする場合、ソフトデリートされたモデルはすべてのクエリ結果から自動的に除外されます。

特定のモデルインスタンスがソフトデリートされているかどうかを判断するには、trashedメソッドを使用できます。

1if ($flight->trashed()) {
2 // ...
3}

ソフトデリート済みモデルの復元

ソフトデリートされたモデルを「復元」したい場合があります。ソフトデリートされたモデルを復元するには、モデルインスタンスでrestoreメソッドを呼び出します。restoreメソッドは、モデルのdeleted_atカラムをnullに設定します。

1$flight->restore();

クエリでrestoreメソッドを使用して、複数のモデルを復元することもできます。繰り返しますが、他の「一括」操作と同様に、これは復元されたモデルのモデルイベントをディスパッチしません。

1Flight::withTrashed()
2 ->where('airline_id', 1)
3 ->restore();

restoreメソッドは、リレーションシップクエリを構築するときにも使用できます。

1$flight->history()->restore();

モデルの完全削除

データベースからモデルを本当に削除する必要がある場合があります。forceDeleteメソッドを使用して、ソフトデリートされたモデルをデータベーステーブルから完全に削除できます。

1$flight->forceDelete();

Eloquentリレーションシップクエリを構築するときにforceDeleteメソッドを使用することもできます。

1$flight->history()->forceDelete();

ソフトデリート済みモデルのクエリ

ソフトデリート済みモデルの含入

前述のとおり、ソフトデリートされたモデルはクエリ結果から自動的に除外されます。ただし、クエリでwithTrashedメソッドを呼び出すことにより、ソフトデリートされたモデルをクエリの結果に強制的に含めることができます。

1use App\Models\Flight;
2 
3$flights = Flight::withTrashed()
4 ->where('account_id', 1)
5 ->get();

withTrashedメソッドは、リレーションシップクエリを構築するときにも呼び出すことができます。

1$flight->history()->withTrashed()->get();

ソフトデリート済みモデルのみの取得

onlyTrashedメソッドは、ソフトデリートされたモデルのみを取得します。

1$flights = Flight::onlyTrashed()
2 ->where('airline_id', 1)
3 ->get();

モデルの整理

不要になったモデルを定期的に削除したい場合があります。これを実現するには、定期的に整理したいモデルにIlluminate\Database\Eloquent\PrunableまたはIlluminate\Database\Eloquent\MassPrunableトレイトを追加します。モデルにトレイトのいずれかを追加した後、不要になったモデルを解決するEloquentクエリビルダを返すprunableメソッドを実装します。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Builder;
6use Illuminate\Database\Eloquent\Model;
7use Illuminate\Database\Eloquent\Prunable;
8 
9class Flight extends Model
10{
11 use Prunable;
12 
13 /**
14 * Get the prunable model query.
15 */
16 public function prunable(): Builder
17 {
18 return static::where('created_at', '<=', now()->subMonth());
19 }
20}

モデルをPrunableとしてマークする場合、モデルにpruningメソッドを定義することもできます。このメソッドは、モデルが削除される前に呼び出されます。このメソッドは、モデルがデータベースから完全に削除される前に、保存されたファイルなど、モデルに関連付けられた追加のリソースを削除するのに役立ちます。

1/**
2 * Prepare the model for pruning.
3 */
4protected function pruning(): void
5{
6 // ...
7}

整理可能なモデルを設定した後、アプリケーションのroutes/console.phpファイルでmodel:prune Artisanコマンドをスケジュールする必要があります。このコマンドを実行する適切な間隔を自由に選択できます。

1use Illuminate\Support\Facades\Schedule;
2 
3Schedule::command('model:prune')->daily();

舞台裏では、model:pruneコマンドは、アプリケーションのapp/Modelsディレクトリ内の「Prunable」モデルを自動的に検出します。モデルが別の場所にある場合は、--modelオプションを使用してモデルクラス名を指定できます。

1Schedule::command('model:prune', [
2 '--model' => [Address::class, Flight::class],
3])->daily();

検出された他のすべてのモデルを整理しながら、特定のモデルを整理から除外したい場合は、--exceptオプションを使用できます。

1Schedule::command('model:prune', [
2 '--except' => [Address::class, Flight::class],
3])->daily();

--pretendオプションを付けてmodel:pruneコマンドを実行することで、prunableクエリをテストできます。pretendする場合、model:pruneコマンドは、コマンドが実際に実行された場合に整理されるレコード数を単に報告します。

1php artisan model:prune --pretend

ソフトデリートされたモデルは、整理可能なクエリに一致する場合、永久に削除(forceDelete)されます。

一括整理

モデルがIlluminate\Database\Eloquent\MassPrunableトレイトでマークされている場合、モデルは一括削除クエリを使用してデータベースから削除されます。したがって、pruningメソッドは呼び出されず、deletingおよびdeletedモデルイベントもディスパッチされません。これは、モデルが削除前に実際には取得されないためであり、これにより整理プロセスがはるかに効率的になります。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Builder;
6use Illuminate\Database\Eloquent\Model;
7use Illuminate\Database\Eloquent\MassPrunable;
8 
9class Flight extends Model
10{
11 use MassPrunable;
12 
13 /**
14 * Get the prunable model query.
15 */
16 public function prunable(): Builder
17 {
18 return static::where('created_at', '<=', now()->subMonth());
19 }
20}

モデルの複製

replicateメソッドを使用して、既存のモデルインスタンスの未保存のコピーを作成できます。このメソッドは、同じ属性の多くを共有するモデルインスタンスがある場合に特に便利です。

1use App\Models\Address;
2 
3$shipping = Address::create([
4 'type' => 'shipping',
5 'line_1' => '123 Example Street',
6 'city' => 'Victorville',
7 'state' => 'CA',
8 'postcode' => '90001',
9]);
10 
11$billing = $shipping->replicate()->fill([
12 'type' => 'billing'
13]);
14 
15$billing->save();

新しいモデルに複製される属性を1つ以上除外するには、replicateメソッドに配列を渡します。

1$flight = Flight::create([
2 'destination' => 'LAX',
3 'origin' => 'LHR',
4 'last_flown' => '2020-03-04 11:00:00',
5 'last_pilot_id' => 747,
6]);
7 
8$flight = $flight->replicate([
9 'last_flown',
10 'last_pilot_id'
11]);

クエリスコープ

グローバルスコープ

グローバルスコープを使用すると、特定のモデルのすべてのクエリに制約を追加できます。Laravel独自のソフトデリート機能は、グローバルスコープを利用して、データベースから「削除されていない」モデルのみを取得します。独自のグローバルスコープを作成すると、特定のモデルのすべてのクエリが特定の制約を確実に受け取るための便利で簡単な方法を提供できます。

スコープの生成

新しいグローバルスコープを生成するには、make:scope Artisanコマンドを呼び出します。これにより、生成されたスコープがアプリケーションのapp/Models/Scopesディレクトリに配置されます。

1php artisan make:scope AncientScope

グローバルスコープの作成

グローバルスコープの作成は簡単です。まず、make:scopeコマンドを使用して、Illuminate\Database\Eloquent\Scopeインターフェイスを実装するクラスを生成します。Scopeインターフェイスでは、applyという1つのメソッドを実装する必要があります。applyメソッドは、必要に応じてwhere制約やその他のタイプの句をクエリに追加できます。

1<?php
2 
3namespace App\Models\Scopes;
4 
5use Illuminate\Database\Eloquent\Builder;
6use Illuminate\Database\Eloquent\Model;
7use Illuminate\Database\Eloquent\Scope;
8 
9class AncientScope implements Scope
10{
11 /**
12 * Apply the scope to a given Eloquent query builder.
13 */
14 public function apply(Builder $builder, Model $model): void
15 {
16 $builder->where('created_at', '<', now()->subYears(2000));
17 }
18}

グローバルスコープがクエリのselect句にカラムを追加する場合は、selectの代わりにaddSelectメソッドを使用する必要があります。これにより、クエリの既存のselect句が意図せず置き換えられるのを防ぎます。

グローバルスコープの適用

モデルにグローバルスコープを割り当てるには、モデルにScopedBy属性を配置するだけです。

1<?php
2 
3namespace App\Models;
4 
5use App\Models\Scopes\AncientScope;
6use Illuminate\Database\Eloquent\Attributes\ScopedBy;
7 
8#[ScopedBy([AncientScope::class])]
9class User extends Model
10{
11 //
12}

または、モデルのbootedメソッドをオーバーライドし、モデルのaddGlobalScopeメソッドを呼び出すことで、グローバルスコープを手動で登録することもできます。addGlobalScopeメソッドは、スコープのインスタンスを唯一の引数として受け入れます。

1<?php
2 
3namespace App\Models;
4 
5use App\Models\Scopes\AncientScope;
6use Illuminate\Database\Eloquent\Model;
7 
8class User extends Model
9{
10 /**
11 * The "booted" method of the model.
12 */
13 protected static function booted(): void
14 {
15 static::addGlobalScope(new AncientScope);
16 }
17}

上記の例のスコープをApp\Models\Userモデルに追加した後、User::all()メソッドを呼び出すと、次のSQLクエリが実行されます。

1select * from `users` where `created_at` < 0021-02-18 00:00:00

無名グローバルスコープ

Eloquentでは、クロージャを使用してグローバルスコープを定義することもできます。これは、独自のクラスを必要としない単純なスコープに特に便利です。クロージャを使用してグローバルスコープを定義する場合、addGlobalScopeメソッドの最初の引数として、自分で選択したスコープ名を指定する必要があります。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Builder;
6use Illuminate\Database\Eloquent\Model;
7 
8class User extends Model
9{
10 /**
11 * The "booted" method of the model.
12 */
13 protected static function booted(): void
14 {
15 static::addGlobalScope('ancient', function (Builder $builder) {
16 $builder->where('created_at', '<', now()->subYears(2000));
17 });
18 }
19}

グローバルスコープの削除

特定のクエリのグローバルスコープを削除したい場合は、withoutGlobalScopeメソッドを使用できます。このメソッドは、グローバルスコープのクラス名を唯一の引数として受け入れます。

1User::withoutGlobalScope(AncientScope::class)->get();

または、クロージャを使用してグローバルスコープを定義した場合は、グローバルスコープに割り当てた文字列名を渡す必要があります。

1User::withoutGlobalScope('ancient')->get();

クエリのグローバルスコープをいくつか、またはすべて削除したい場合は、withoutGlobalScopesメソッドを使用できます。

1// Remove all of the global scopes...
2User::withoutGlobalScopes()->get();
3 
4// Remove some of the global scopes...
5User::withoutGlobalScopes([
6 FirstScope::class, SecondScope::class
7])->get();

ローカルスコープ

ローカルスコープを使用すると、アプリケーション全体で簡単に再利用できる共通のクエリ制約セットを定義できます。たとえば、「人気のある」と見なされるすべてのユーザーを頻繁に取得する必要がある場合があります。スコープを定義するには、Eloquentモデルメソッドの前にscopeを付けます。

スコープは常に同じクエリビルダインスタンスまたはvoidを返す必要があります。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Builder;
6use Illuminate\Database\Eloquent\Model;
7 
8class User extends Model
9{
10 /**
11 * Scope a query to only include popular users.
12 */
13 public function scopePopular(Builder $query): void
14 {
15 $query->where('votes', '>', 100);
16 }
17 
18 /**
19 * Scope a query to only include active users.
20 */
21 public function scopeActive(Builder $query): void
22 {
23 $query->where('active', 1);
24 }
25}

ローカルスコープの利用

スコープが定義されたら、モデルをクエリするときにスコープメソッドを呼び出すことができます。ただし、メソッドを呼び出すときにscopeプレフィックスを含めるべきではありません。さまざまなスコープへの呼び出しをチェーンすることもできます。

1use App\Models\User;
2 
3$users = User::popular()->active()->orderBy('created_at')->get();

orクエリ演算子を介して複数のEloquentモデルスコープを組み合わせるには、正しい論理グループ化を実現するためにクロージャを使用する必要がある場合があります。

1$users = User::popular()->orWhere(function (Builder $query) {
2 $query->active();
3})->get();

ただし、これは面倒になる可能性があるため、Laravelは、クロージャを使用せずにスコープを流暢にチェーンできる「高階」のorWhereメソッドを提供しています。

1$users = User::popular()->orWhere->active()->get();

動的スコープ

パラメータを受け入れるスコープを定義したい場合があります。開始するには、スコープメソッドのシグネチャに追加のパラメータを追加するだけです。スコープパラメータは、$queryパラメータの後に定義する必要があります。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Builder;
6use Illuminate\Database\Eloquent\Model;
7 
8class User extends Model
9{
10 /**
11 * Scope a query to only include users of a given type.
12 */
13 public function scopeOfType(Builder $query, string $type): void
14 {
15 $query->where('type', $type);
16 }
17}

期待される引数がスコープメソッドのシグネチャに追加されたら、スコープを呼び出すときに引数を渡すことができます。

1$users = User::ofType('admin')->get();

保留中の属性

スコープを使用して、スコープを制約するために使用された属性と同じ属性を持つモデルを作成したい場合は、スコープクエリを構築するときにwithAttributesメソッドを使用できます。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Builder;
6use Illuminate\Database\Eloquent\Model;
7 
8class Post extends Model
9{
10 /**
11 * Scope the query to only include drafts.
12 */
13 public function scopeDraft(Builder $query): void
14 {
15 $query->withAttributes([
16 'hidden' => true,
17 ]);
18 }
19}

withAttributesメソッドは、指定された属性を使用してクエリにwhere句の制約を追加し、スコープを介して作成されたモデルにも指定された属性を追加します。

1$draft = Post::draft()->create(['title' => 'In Progress']);
2 
3$draft->hidden; // true

モデルの比較

2つのモデルが「同じ」であるかどうかを判断する必要がある場合があります。isメソッドとisNotメソッドを使用して、2つのモデルが同じ主キー、テーブル、およびデータベース接続を持っているかどうかをすばやく確認できます。

1if ($post->is($anotherPost)) {
2 // ...
3}
4 
5if ($post->isNot($anotherPost)) {
6 // ...
7}

isメソッドとisNotメソッドは、belongsTohasOnemorphTomorphOneリレーションシップを使用する場合にも利用できます。このメソッドは、そのモデルを取得するためのクエリを発行せずに、関連モデルを比較したい場合に特に役立ちます。

1if ($post->author()->is($user)) {
2 // ...
3}

イベント

Eloquentイベントをクライアントサイドのアプリケーションに直接ブロードキャストしたいですか?Laravelのモデルイベントブロードキャスティングをチェックしてください。

Eloquentモデルはいくつかのイベントをディスパッチし、モデルのライフサイクルの次の瞬間にフックできるようにします:retrievedcreatingcreatedupdatingupdatedsavingsaveddeletingdeletedtrashedforceDeletingforceDeletedrestoringrestored、およびreplicating

retrievedイベントは、既存のモデルがデータベースから取得されたときにディスパッチされます。新しいモデルが初めて保存されると、creatingイベントとcreatedイベントがディスパッチされます。updating / updatedイベントは、既存のモデルが変更され、saveメソッドが呼び出されたときにディスパッチされます。saving / savedイベントは、モデルの属性が変更されていなくても、モデルが作成または更新されたときにディスパッチされます。-ingで終わるイベント名は、モデルへの変更が永続化される前にディスパッチされ、-edで終わるイベントは、モデルへの変更が永続化された後にディスパッチされます。

モデルイベントのリッスンを開始するには、Eloquentモデルで$dispatchesEventsプロパティを定義します。このプロパティは、Eloquentモデルのライフサイクルのさまざまなポイントを独自のイベントクラスにマッピングします。各モデルイベントクラスは、コンストラクタを介して影響を受けるモデルのインスタンスを受け取ることを期待する必要があります。

1<?php
2 
3namespace App\Models;
4 
5use App\Events\UserDeleted;
6use App\Events\UserSaved;
7use Illuminate\Foundation\Auth\User as Authenticatable;
8use Illuminate\Notifications\Notifiable;
9 
10class User extends Authenticatable
11{
12 use Notifiable;
13 
14 /**
15 * The event map for the model.
16 *
17 * @var array<string, string>
18 */
19 protected $dispatchesEvents = [
20 'saved' => UserSaved::class,
21 'deleted' => UserDeleted::class,
22 ];
23}

Eloquentイベントを定義してマッピングした後、イベントリスナを使用してイベントを処理できます。

Eloquentを介して一括更新または削除クエリを発行する場合、影響を受けるモデルに対してsavedupdateddeleting、およびdeletedモデルイベントはディスパッチされません。これは、一括更新または削除を実行するときにモデルが実際には取得されないためです。

クロージャの使用

カスタムイベントクラスを使用する代わりに、さまざまなモデルイベントがディスパッチされたときに実行されるクロージャを登録できます。通常、これらのクロージャはモデルのbootedメソッドで登録する必要があります。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6 
7class User extends Model
8{
9 /**
10 * The "booted" method of the model.
11 */
12 protected static function booted(): void
13 {
14 static::created(function (User $user) {
15 // ...
16 });
17 }
18}

必要に応じて、モデルイベントを登録するときにキュー可能な匿名イベントリスナを利用できます。これにより、Laravelはアプリケーションのキューを使用してバックグラウンドでモデルイベントリスナを実行するように指示します。

1use function Illuminate\Events\queueable;
2 
3static::created(queueable(function (User $user) {
4 // ...
5}));

オブザーバ

オブザーバの定義

特定のモデルで多くのイベントをリッスンしている場合は、オブザーバを使用してすべてのリスナを単一のクラスにグループ化できます。オブザーバクラスには、リッスンしたいEloquentイベントを反映したメソッド名があります。これらのメソッドはそれぞれ、影響を受けるモデルを唯一の引数として受け取ります。make:observer Artisanコマンドは、新しいオブザーバクラスを作成する最も簡単な方法です。

1php artisan make:observer UserObserver --model=User

このコマンドは、新しいオブザーバをapp/Observersディレクトリに配置します。このディレクトリが存在しない場合、Artisanが作成します。新しいオブザーバは次のようになります。

1<?php
2 
3namespace App\Observers;
4 
5use App\Models\User;
6 
7class UserObserver
8{
9 /**
10 * Handle the User "created" event.
11 */
12 public function created(User $user): void
13 {
14 // ...
15 }
16 
17 /**
18 * Handle the User "updated" event.
19 */
20 public function updated(User $user): void
21 {
22 // ...
23 }
24 
25 /**
26 * Handle the User "deleted" event.
27 */
28 public function deleted(User $user): void
29 {
30 // ...
31 }
32 
33 /**
34 * Handle the User "restored" event.
35 */
36 public function restored(User $user): void
37 {
38 // ...
39 }
40 
41 /**
42 * Handle the User "forceDeleted" event.
43 */
44 public function forceDeleted(User $user): void
45 {
46 // ...
47 }
48}

オブザーバを登録するには、対応するモデルにObservedBy属性を配置します。

1use App\Observers\UserObserver;
2use Illuminate\Database\Eloquent\Attributes\ObservedBy;
3 
4#[ObservedBy([UserObserver::class])]
5class User extends Authenticatable
6{
7 //
8}

または、監視したいモデルでobserveメソッドを呼び出すことにより、オブザーバを手動で登録することもできます。オブザーバは、アプリケーションのAppServiceProviderクラスのbootメソッドで登録できます。

1use App\Models\User;
2use App\Observers\UserObserver;
3 
4/**
5 * Bootstrap any application services.
6 */
7public function boot(): void
8{
9 User::observe(UserObserver::class);
10}

オブザーバがリッスンできる追加のイベントとして、savingretrievedなどがあります。これらのイベントは、イベントのドキュメントで説明されています。

オブザーバとデータベーストランザクション

データベーストランザクション内でモデルが作成されている場合、データベーストランザクションがコミットされた後にのみイベントハンドラを実行するようにオブザーバに指示したい場合があります。これは、オブザーバでShouldHandleEventsAfterCommitインターフェイスを実装することで実現できます。データベーストランザクションが進行中でない場合、イベントハンドラはすぐに実行されます。

1<?php
2 
3namespace App\Observers;
4 
5use App\Models\User;
6use Illuminate\Contracts\Events\ShouldHandleEventsAfterCommit;
7 
8class UserObserver implements ShouldHandleEventsAfterCommit
9{
10 /**
11 * Handle the User "created" event.
12 */
13 public function created(User $user): void
14 {
15 // ...
16 }
17}

イベントのミュート

モデルによって発行されるすべてのイベントを一時的に「ミュート」する必要がある場合があります。これは、withoutEventsメソッドを使用して実現できます。withoutEventsメソッドは、クロージャを唯一の引数として受け入れます。このクロージャ内で実行されるコードはモデルイベントをディスパッチせず、クロージャによって返された値はwithoutEventsメソッドによって返されます。

1use App\Models\User;
2 
3$user = User::withoutEvents(function () {
4 User::findOrFail(1)->delete();
5 
6 return User::find(2);
7});

イベントなしでの単一モデルの保存

イベントをディスパッチせずに特定のモデルを「保存」したい場合があります。これは、saveQuietlyメソッドを使用して実現できます。

1$user = User::findOrFail(1);
2 
3$user->name = 'Victoria Faith';
4 
5$user->saveQuietly();

イベントをディスパッチせずに、特定のモデルを「更新」、「削除」、「ソフトデリート」、「復元」、「複製」することもできます。

1$user->deleteQuietly();
2$user->forceDeleteQuietly();
3$user->restoreQuietly();

Laravelは最も生産的な方法です
ソフトウェアを構築、デプロイ、監視します。