はじめに
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
モデルを生成する際に、ファクトリ、シーダー、ポリシー、コントローラー、フォームリクエストなど、さまざまな他のタイプのクラスを生成することもできます。さらに、これらのオプションを組み合わせて、複数のクラスを一度に作成することもできます:
# モデルと FlightFactory クラスを生成...
php artisan make:model Flight --factory
php artisan make:model Flight -f
# モデルと FlightSeeder クラスを生成...
php artisan make:model Flight --seed
php artisan make:model Flight -s
# モデルと FlightController クラスを生成...
php artisan make:model Flight --controller
php artisan make:model Flight -c
# モデル、FlightController リソースクラス、およびフォームリクエストクラスを生成...
php artisan make:model Flight --controller --resource --requests
php artisan make:model Flight -crR
# モデルと FlightPolicy クラスを生成...
php artisan make:model Flight --policy
# モデルとマイグレーション、ファクトリ、シーダー、コントローラーを生成...
php artisan make:model Flight -mfsc
# モデル、マイグレーション、ファクトリ、シーダー、ポリシー、コントローラー、およびフォームリクエストを生成するショートカット...
php artisan make:model Flight --all
php artisan make:model Flight -a
# ピボットモデルを生成...
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
モデルに対応するかを伝えていないことに気付いたかもしれません。慣習として、クラスの「スネークケース」の複数形の名前がテーブル名として使用され、別の名前が明示的に指定されない限り、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
トレイトは、モデルのために 「順序付き」UUIDs を生成します。これらの UUID は、辞書順にソートできるため、インデックス付きデータベースストレージに対してより効率的です。
特定のモデルの UUID 生成プロセスをオーバーライドするには、モデルに newUniqueId
メソッドを定義します。さらに、モデルにどの列が UUID を受け取るべきかを指定するには、モデルに uniqueIds
メソッドを定義します:
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 によって自動的に管理させたくない場合は、モデルに $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
メソッドは、遅延読み込みを防ぐかどうかを示すオプションのブール引数を受け取ります。たとえば、非本番環境でのみ遅延読み込みを無効にしたい場合があります。これにより、本番環境で遅延読み込みされた関係が誤って存在しても、通常通りに機能し続けます。通常、このメソッドはアプリケーションの boot
メソッド内で呼び出されるべきです:
use Illuminate\Database\Eloquent\Model;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Model::preventLazyLoading(! $this->app->isProduction());
}
また、preventSilentlyDiscardingAttributes
メソッドを呼び出すことで、埋め込めない属性を埋め込もうとしたときに 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"
コレクション
ご覧のとおり、Eloquent メソッドの all
および get
は、データベースから複数のレコードを取得します。ただし、これらのメソッドはプレーンな 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
メソッドに渡される最初の引数は、「チャンク」ごとに受け取りたいレコードの数です。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]);
id
の降順に基づいて結果をフィルタリングするには、lazyByIdDesc
メソッドを使用します。
カーソル
lazy
メソッドと同様に、cursor
メソッドを使用して、数万の Eloquent モデルレコードを反復処理する際のアプリケーションのメモリ消費を大幅に削減できます。
cursor
メソッドは、単一のデータベースクエリを実行します。ただし、個々の Eloquent モデルは、実際に反復処理されるまで水和されません。したがって、カーソルを反復処理している間は、常にメモリに保持されるのは 1 つの Eloquent モデルだけです。
cursor
メソッドは、常にメモリに 1 つの Eloquent モデルしか保持しないため、リレーションシップをイager ロードすることはできません。リレーションシップをイager ロードする必要がある場合は、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
メソッドは、指定された列 / 値のペアを使用してデータベースレコードを検索しようとします。モデルがデータベースで見つからない場合、最初の配列引数とオプションの第二配列引数をマージした属性を持つレコードが挿入されます:
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 モデルと対話する際に、count
、sum
、max
、および Laravel クエリビルダー が提供する他の 集約メソッド を使用することもできます。これらのメソッドは、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
フィールドを 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
列が更新されます。そのようなフライトが存在しない場合、最初の引数配列と第二引数配列をマージした属性を持つ新しいフライトが作成されます:
$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->email; //
$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
プロパティを使用してこれを行うことができます。たとえば、name
属性を Flight
モデルのマスアサイン可能にしましょう:
<?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 の fill
、create
、および update
メソッドに渡す配列を常に手作りするように特に注意してください:
/**
* The attributes that aren't mass assignable.
*
* @var array
*/
protected $guarded = [];
マスアサインメントの例外
デフォルトでは、$fillable
配列に含まれていない属性は、マスアサインメント操作を実行する際に静かに破棄されます。本番環境では、これは期待される動作ですが、ローカル開発中は、モデルの変更が反映されない理由について混乱を招く可能性があります。
希望する場合は、preventSilentlyDiscardingAttributes
メソッドを呼び出すことで、埋め込むことができない属性を埋め込もうとしたときに Laravel に例外をスローさせることができます。通常、このメソッドはアプリケーションの 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
{
// ...
}
プルーニング可能なモデルを構成した後、アプリケーションの model:prune
ファイルで routes/console.php
Artisan コマンドをスケジュールする必要があります。このコマンドを実行する適切な間隔を自由に選択できます:
use Illuminate\Support\Facades\Schedule;
Schedule::command('model:prune')->daily();
裏で、model:prune
コマンドは、アプリケーションの app/Models
ディレクトリ内の「プルーニング可能」モデルを自動的に検出します。モデルが別の場所にある場合は、--model
オプションを使用してモデルクラス名を指定できます:
Schedule::command('model:prune', [
'--model' => [Address::class, Flight::class],
])->daily();
プルーニング中に他の検出されたモデルをすべてプルーニングする一方で、特定のモデルをプルーニングから除外したい場合は、--except
オプションを使用できます:
Schedule::command('model:prune', [
'--except' => [Address::class, Flight::class],
])->daily();
prunable
クエリをテストするには、model:prune
コマンドを --pretend
オプションで実行します。擬似実行中、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();
新しいモデルに複製されない属性を 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
インターフェースでは、1 つのメソッド 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));
}
}
クエリの選択句に列を追加している場合は、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)) {
// ...
}
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
イベントがディスパッチされます。updating
/ updated
イベントは、既存のモデルが変更され、save
メソッドが呼び出されたときにディスパッチされます。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
メソッドを呼び出すことでオブザーバーを手動で登録できます。アプリケーションの 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);
});