メインコンテンツまでスキップ

Eloquent: はじめに

はじめに

LaravelにはEloquentが含まれており、データベースとのやり取りが楽しくなるオブジェクト関係マッパー(ORM)が提供されています。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 --factory
php artisan make:model Flight -f

# Generate a model and a FlightSeeder class...
php artisan make:model Flight --seed
php artisan make:model Flight -s

# Generate a model and a FlightController class...
php artisan make:model Flight --controller
php artisan make:model Flight -c

# Generate a model, FlightController resource class, and form request classes...
php artisan make:model Flight --controller --resource --requests
php 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 --all
php artisan make:model Flight -a

# Generate a pivot model...
php artisan make:model Member --pivot
php 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
{
// ...
}

テーブル名

上記の例を一度見てみると、EloquentにFlightモデルに対応するデータベーステーブルを指定していないことに気付いたかもしれません。慣例として、クラスの「スネークケース」形式の複数形の名前が、別の名前が明示的に指定されていない限り、テーブル名として使用されます。したがって、この場合、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がプライマリキーを自動的に整数にキャストすることを意味します。増分しないまたは数値でないプライマリキーを使用したい場合は、モデルに公開された$incrementingプロパティを定義して、falseに設定する必要があります:

    <?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は、各モデルが少なくとも1つの一意に識別可能な「ID」を持つ必要があります。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は、辞書式にソートできるため、インデックス付きデータベースストレージに効率的です。

特定のモデルに対してUUID生成プロセスをオーバーライドするには、モデルにnewUniqueIdメソッドを定義します。さらに、モデルに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の代わりに"ULIDs"を使用することもできます。ULIDsはUUIDに似ていますが、長さが26文字しかありません。順序付けられたUUIDと同様に、ULIDsは効率的なデータベースインデックス作成のために辞書式にソートできます。ULIDsを使用するには、モデルに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によってこれらの列を自動的に管理したくない場合は、モデルに$timestampsプロパティを定義し、値をfalseに設定する必要があります:

    <?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 メソッドは、遅延読み込みを防止するかどうかを示すオプションのブール値引数を受け入れます。たとえば、本番環境では遅延読み込みを無効にしたい場合があります。これにより、本番コードに遅延読み込み関係が誤って存在していても、本番環境が通常通り機能するようになります。通常、このメソッドはアプリケーションの AppServiceProviderboot メソッドで呼び出すべきです:

use Illuminate\Database\Eloquent\Model;

/**
* Bootstrap any application services.
*/
public function boot(): void
{
Model::preventLazyLoading(! $this->app->isProduction());
}

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

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"

コレクション

allgetなどの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モデルのコレクションとやり取りするために特に設計されたいくつかの追加メソッドを提供しています。

LaravelのすべてのコレクションはPHPのiterableインターフェースを実装しているため、コレクションを配列のようにループ処理することができます:

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

chunkメソッドの結果を列でフィルタリングし、同時に結果を反復処理する場合は、chunkByIdメソッドを使用する必要があります。これらのシナリオでchunkメソッドを使用すると、予期しない一貫性のない結果が発生する可能性があります。内部的には、chunkByIdメソッドは常に、前のチャンクの最後のモデルよりもid列が大きいモデルを取得します:

Flight::where('departed', true)
->chunkById(200, function (Collection $flights) {
$flights->each->update(['departed' => false]);
}, $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 モデルは実際にイテレートされるまでハイドレートされません。したがって、カーソルをイテレートする際には常にメモリに 1 つの Eloquent モデルのみが保持されます。

警告

cursor メソッドは常に 1 つの Eloquent モデルのみをメモリに保持するため、リレーションシップをイーガーロードすることはできません。リレーションシップをイーガーロードする必要がある場合は、the lazy method を検討してください。

内部的に、cursor メソッドは PHP の generators を使用してこの機能を実装します:

use App\Models\Flight;

foreach (Flight::where('destination', 'Zurich')->cursor() as $flight) {
// ...
}

cursorIlluminate\Support\LazyCollection インスタンスを返します。Lazy collections は、通常の Laravel コレクションで利用可能な多くのコレクションメソッドを使用できるようにしますが、1 回につき 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 回につき 1 つの Eloquent モデルのみをメモリに保持するため)、しかし最終的にはメモリ不足になります。これは PHP の PDO ドライバが内部的にすべての生のクエリ結果をバッファにキャッシュしているため です。非常に多数の Eloquent レコードを扱う場合は、the lazy method を使用することを検討してください。```

高度なサブクエリ

サブクエリの選択

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

クエリビルダのselectおよびaddSelectメソッドで利用可能なサブクエリ機能を使用すると、1つのクエリで、各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関数はサブクエリをサポートしています。フライトの例を引き続き使用すると、この機能を使用して、すべての目的地を、その目的地に最後に到着したフライトの時刻に基づいて並べ替えることができます。これも、1つのデータベースクエリを実行しながら行うことができます。

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

単一のモデル / 集計の取得

特定のクエリに一致するすべてのレコードを取得するだけでなく、findfirst、または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 の クエリビルダ が提供する countsummax などの他の集計メソッドを使用することもできます。これらのメソッドは、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 メソッドを呼び出すと、レコードがデータベースに挿入されます。save メソッドが呼び出されると、モデルの created_at および updated_at タイムスタンプが自動的に設定されるため、手動で設定する必要はありません。

または、単一のPHPステートメントを使用して新しいモデルを「保存」するためにcreateメソッドを使用することもできます。挿入されたモデルインスタンスは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メソッドを手動で呼び出す必要はありません。

以下の例では、departureOaklandでありdestinationSan Diegoであるフライトが存在する場合、そのpricediscountedの列が更新されます。そのようなフライトが存在しない場合、最初の引数の配列と2番目の引数の配列をマージした属性を持つ新しいフライトが作成されます:

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

大量更新

指定されたクエリに一致するモデルに対しても更新を行うことができます。この例では、activeでありdestinationSan Diegoであるすべてのフライトが遅延とマークされます:

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

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

警告

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

属性の変更を調査する

Eloquentは、モデルの内部状態を調べ、モデルが元々取得された時点から属性がどのように変更されたかを判断するためのisDirtyisCleanwasChangedメソッドを提供しています。

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->email; // john@example.com

$user->name = "Jack";
$user->name; // Jack

$user->getOriginal('name'); // John
$user->getOriginal(); // Array of original attributes...

一括代入

createメソッドを使用して、1つの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
*/
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
*/
protected $fillable = [
'options->enabled',
];

一括代入を許可する

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

    /**
* The attributes that aren't mass assignable.
*
* @var array
*/
protected $guarded = [];

一括代入の例外

デフォルトでは、$fillable配列に含まれていない属性は、一括代入操作を行う際に無視されます。本番環境では、これは期待される動作ですが、ローカル開発中には、モデルの変更が反映されない理由がわからなくなる可能性があります。

希望する場合は、preventSilentlyDiscardingAttributesメソッドを呼び出すことで、一括代入不可の属性を埋めようとしたときに例外をスローするようにLaravelに指示することができます。通常、このメソッドは、アプリケーションのAppServiceProviderクラスのbootメソッドで呼び出すべきです:

    use Illuminate\Database\Eloquent\Model;

/**
* Bootstrap any application services.
*/
public function boot(): void
{
Model::preventSilentlyDiscardingAttributes($this->app->isLocal());
}

Upserts

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']);

:::warning

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

モデルの削除

モデルを削除するには、モデルインスタンスで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]));

警告

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 トレイトを定期的に整理したいモデルに追加することができます。モデルにいずれかのトレイトを追加した後、prunable メソッドを実装し、不要になったモデルを解決する Eloquent クエリビルダを返すようにします。

    <?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
{
// ...
}

prunableモデルを構成した後は、アプリケーションのroutes/console.phpファイルでmodel:pruneアーティザンコマンドをスケジュールする必要があります。このコマンドを実行する間隔を自由に選択することができます:

    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();

prunableクエリをテストする場合は、--pretendオプションを使用してmodel:pruneコマンドを実行できます。プリテンド時には、model:pruneコマンドは実際に実行された場合に剪定されるレコードの数を単に報告します:

php artisan model:prune --pretend
警告

ソフト削除されたモデルは、剪定クエリに一致する場合に永久に削除(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();

新しいモデルに複製される属性を除外するには、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();

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

    $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)) {
// ...
}

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

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

イベント

注記

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

Eloquent モデルは、モデルのライフサイクルの以下の瞬間にフックすることができるように、いくつかのイベントをディスパッチします:retrievedcreatingcreatedupdatingupdatedsavingsaveddeletingdeletedtrashedforceDeletingforceDeletedrestoringrestoredreplicating

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を介して大量の更新または削除クエリを発行する場合、影響を受けるモデルに対してsavedupdateddeletingdeletedモデルイベントはディスパッチされません。これは、大量の更新または削除を実行する際に、モデルが実際に取得されないためです。

クロージャの使用

カスタムイベントクラスを使用する代わりに、さまざまなモデルイベントがディスパッチされるときに実行されるクロージャを登録することができます。通常、これらのクロージャは、モデルの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) {
// ...
});
}
}

必要に応じて、モデルイベントを登録する際にqueueable anonymous event listenersを利用することができます。これにより、Laravelはアプリケーションのqueueを使用してモデルイベントリスナーをバックグラウンドで実行するように指示されます:

    use function Illuminate\Events\queueable;

static::created(queueable(function (User $user) {
// ...
}));

オブザーバ

オブザーバの定義

特定のモデルで多くのイベントを聞いている場合は、オブザーバを使用してすべてのリスナーを1つのクラスにグループ化することができます。オブザーバクラスには、聞きたい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);
}

注記

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

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

モデルがデータベーストランザクション内で作成されている場合、オブザーバーに対してデータベーストランザクションがコミットされた後にイベントハンドラーのみを実行するように指示したい場合があります。これは、オブザーバーに 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();