サイトを更新しています。 数日間、レイアウトや翻訳に不具合が出ることがあります。ドキュメントは引き続きご利用いただけます。表示が崩れている場合は、後ほど再読み込みしてください。

ホーム Laravel 10.x Eloquent:はじめに

Eloquent:はじめに

10.x 2026年3月7日

#はじめに

LaravelにはEloquentというオブジェクトリレーショナルマッパー(ORM)が含まれており、データベースとのやり取りを楽しく行えます。Eloquentを使うと、各データベーステーブルに対応する「モデル」があり、そのモデルを通じてテーブルとやり取りします。レコードの取得だけでなく、挿入、更新、削除もモデルで行えます。

Примечание

はじめる前に、アプリケーションの config/database.php 設定ファイルでデータベース接続を設定してください。データベース設定の詳細はデータベース設定のドキュメントを参照してください。

#Laravelブートキャンプ

Laravelが初めての場合は、Laravelブートキャンプにぜひ参加してください。Laravelブートキャンプでは、Eloquentを使った最初のLaravelアプリケーションの作成を案内します。LaravelとEloquentの全体像を理解するのに最適です。

#モデルクラスの生成

まずはEloquentモデルを作成しましょう。モデルは通常 app\Models ディレクトリに置かれ、Illuminate\Database\Eloquent\Model クラスを継承します。make:modelArtisanコマンドで新しいモデルを生成できます。

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

#テーブル名

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

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

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    /**
     * モデルに関連付けられたテーブル名
     *
     * @var string
     */
    protected $table = 'my_flights';
}

#主キー

Eloquentはモデルに対応するテーブルの主キーが id という名前のカラムであると想定します。異なるカラムを主キーにしたい場合は、モデルに保護された $primaryKey プロパティを定義して指定できます。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    /**
     * テーブルに関連付けられた主キー
     *
     * @var string
     */
    protected $primaryKey = 'flight_id';
}

さらに、Eloquentは主キーが自動増分の整数値であると想定し、自動的に整数にキャストします。非自動増分や非数値の主キーを使う場合は、モデルにパブリックな $incrementing プロパティを false に設定して定義してください。

<?php

class Flight extends Model
{
    /**
     * モデルのIDが自動増分かどうかを示す
     *
     * @var bool
     */
    public $incrementing = false;
}

主キーが整数でない場合は、モデルに保護された $keyType プロパティを定義し、値を string に設定してください。

<?php

class Flight extends Model
{
    /**
     * 主キーIDのデータ型
     *
     * @var string
     */
    protected $keyType = 'string';
}

#複合主キー(Composite Primary Keys)

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

#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 トレイトはモデルに対して"ordered" UUIDを生成します。これらのUUIDは辞書式にソート可能で、インデックス付きデータベースに効率的です。

モデルでUUID生成処理をカスタマイズしたい場合は、newUniqueId メソッドを定義できます。また、UUIDを付与するカラムを指定したい場合は、uniqueIds メソッドを定義してください。

use Ramsey\Uuid\Uuid;

/**
 * モデル用の新しいUUIDを生成する
 */
public function newUniqueId(): string
{
    return (string) Uuid::uuid4();
}

/**
 * 一意識別子を付与するカラムを取得する
 *
 * @return array<int, string>
 */
public function uniqueIds(): array
{
    return ['id', 'discount_code'];
}

UUIDの代わりにULIDを使うこともできます。ULIDはUUIDに似ていますが26文字の長さです。ordered 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_atupdated_at カラムがあることを期待します。モデルの作成や更新時にこれらのカラムは自動的に設定されます。自動管理したくない場合は、モデルに $timestamps プロパティを false に設定してください。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    /**
     * モデルにタイムスタンプを付与するかどうか
     *
     * @var bool
     */
    public $timestamps = false;
}

モデルのタイムスタンプのフォーマットをカスタマイズしたい場合は、モデルに $dateFormat プロパティを設定します。このプロパティは、データベースに保存される日付属性の形式と、モデルが配列やJSONにシリアライズされる際の形式を決定します。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    /**
     * モデルの日付カラムの保存形式
     *
     * @var string
     */
    protected $dateFormat = 'U';
}

タイムスタンプを保存するカラム名をカスタマイズしたい場合は、モデルに CREATED_ATUPDATED_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
{
    /**
     * モデルで使用するデータベース接続。
     *
     * @var string
     */
    protected $connection = 'sqlite';
}

#デフォルト属性値

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

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    /**
     * モデルの属性に対するデフォルト値。
     *
     * @var array
     */
    protected $attributes = [
        'options' => '[]',
        'delayed' => false,
    ];
}

#Eloquent の厳格モード設定

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

まず、preventLazyLoading メソッドは遅延ロードを防止するかどうかを示すオプションのブール引数を受け取ります。例えば、本番環境では遅延ロードを無効にせず、非本番環境のみで無効にしたい場合があります。通常、このメソッドはアプリケーションの AppServiceProviderboot メソッド内で呼び出します。

use Illuminate\Database\Eloquent\Model;

/**
 * アプリケーションサービスの初期化。
 */
public function boot(): void
{
    Model::preventLazyLoading(! $this->app->isProduction());
}

また、preventSilentlyDiscardingAttributes メソッドを呼び出すことで、モデルの fillable 配列に含まれない属性を設定しようとした際に Laravel が例外を投げるようにできます。これにより、ローカル開発時に fillable に追加していない属性を設定しようとして発生する予期しないエラーを防げます:

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

#モデルの取得

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

use App\Models\Flight;

foreach (Flight::all() as $flight) {
    echo $flight->name;
}

#クエリの構築

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

$flights = Flight::where('active', 1)
               ->orderBy('name')
               ->take(10)
               ->get();
Примечание

Eloquent モデルはクエリビルダーであるため、Laravel のクエリビルダーが提供するすべてのメソッドを確認してください。Eloquent クエリを書く際にこれらのメソッドを自由に使えます。

#モデルのリフレッシュ

すでにデータベースから取得した Eloquent モデルのインスタンスがある場合、freshrefresh メソッドでモデルを「リフレッシュ」できます。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;
}

#結果のチャンク処理

allget メソッドで数万件の Eloquent レコードを一度に読み込もうとするとメモリ不足になる可能性があります。代わりに chunk メソッドを使うと、大量のモデルを効率的に処理できます。

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

use App\Models\Flight;
use Illuminate\Database\Eloquent\Collection;

Flight::chunk(200, function (Collection $flights) {
    foreach ($flights as $flight) {
        // ...
    }
});

chunk メソッドの第一引数は1チャンクあたりに取得するレコード数です。第二引数のクロージャは、取得した各チャンクに対して呼び出されます。チャンクごとにデータベースクエリが実行されます。

chunk メソッドの結果をフィルタリングしつつ、結果を反復処理中に更新する場合は chunkById メソッドを使うべきです。chunk メソッドを使うと予期しない不整合が起こる可能性があります。chunkById は常に前のチャンクの最後のモデルより大きい id を持つモデルを取得します。

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

#Lazy Collection を使ったチャンク処理

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つだけです。

Внимание

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

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

use App\Models\Flight;

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

cursorIlluminate\Support\LazyCollection のインスタンスを返します。Lazy collection は通常の 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 メソッドは通常のクエリよりずっと少ないメモリを使用します(同時にメモリに保持する Eloquent モデルは1つだけ)が、それでも最終的にはメモリが不足します。これは PHP の PDO ドライバが内部的にすべての生のクエリ結果をバッファにキャッシュしていること によるものです。非常に多くの Eloquent レコードを扱う場合は、代わりに lazy メソッド の使用を検討してください。

#高度なサブクエリ

#サブクエリのセレクト

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

クエリビルダーの selectaddSelect メソッドで使えるサブクエリ機能を使い、すべての 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();

#単一モデル / 集約の取得

クエリに一致するすべてのレコードを取得する以外に、findfirstfirstWhere メソッドで単一レコードを取得できます。これらはモデルのコレクションではなく、単一のモデルインスタンスを返します。

use App\Models\Flight;

// 主キーでモデルを取得...
$flight = Flight::find(1);

// クエリ条件に一致する最初のモデルを取得...
$flight = Flight::where('active', 1)->first();

// クエリ条件に一致する最初のモデルを取得する別の方法...
$flight = Flight::firstWhere('active', 1);

結果が見つからなかった場合に別の処理をしたい場合、findOrfirstOr メソッドを使えます。これらは単一モデルを返すか、結果がなければ指定したクロージャを実行します。クロージャの戻り値がメソッドの結果となります。

$flight = Flight::findOr(1, function () {
    // ...
});

$flight = Flight::where('legs', '>', 3)->firstOr(function () {
    // ...
});

#見つからなかった場合の例外

モデルが見つからなかった場合に例外を投げたいことがあります。これは特にルートやコントローラーで便利です。findOrFailfirstOrFail メソッドはクエリの最初の結果を取得しますが、結果が見つからない場合は 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;

// 名前でフライトを取得、なければ作成...
$flight = Flight::firstOrCreate([
    'name' => 'London to Paris'
]);

// 名前でフライトを取得、なければ name, delayed, arrival_time 属性で作成...
$flight = Flight::firstOrCreate(
    ['name' => 'London to Paris'],
    ['delayed' => 1, 'arrival_time' => '11:30']
);

// 名前でフライトを取得、なければ新しい Flight インスタンスを生成...
$flight = Flight::firstOrNew([
    'name' => 'London to Paris'
]);

// 名前でフライトを取得、なければ name, delayed, arrival_time 属性で新しいインスタンスを生成...
$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
{
    /**
     * 新しいフライトをデータベースに保存します。
     */
    public function store(Request $request): RedirectResponse
    {
        // リクエストをバリデート...

        $flight = new Flight;

        $flight->name = $request->name;

        $flight->save();

        return redirect('/flights');
    }
}

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

また、create メソッドを使うと、1行の 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();

#一括更新

クエリに合致するモデルに対して一括更新も可能です。この例では、active が 1 で 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; // [email protected]

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

$user->getOriginal('name'); // John
$user->getOriginal(); // 元の属性の配列...

#マスアサインメント

create メソッドを使うと、1行の PHP 文で新しいモデルを「保存」できます。挿入されたモデルインスタンスが返されます:

use App\Models\Flight;

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

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

マスアサインメントの脆弱性は、ユーザーが予期しない HTTP リクエストのフィールドを送信し、そのフィールドがデータベースの予期しないカラムを変更してしまう場合に発生します。例えば、悪意のあるユーザーが is_admin パラメータを送信し、それがモデルの create メソッドに渡されると、管理者権限を不正に取得される可能性があります。

そのため、まずはマスアサイン可能にしたいモデルの属性を $fillable プロパティで定義します。例えば、Flight モデルの name 属性をマスアサイン可能にするには以下のようにします:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    /**
     * マスアサイン可能な属性
     *
     * @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 属性の更新をサポートしていません:

/**
 * マスアサイン可能な属性
 *
 * @var array
 */
protected $fillable = [
    'options->enabled',
];

#マスアサインメントを許可する

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

/**
 * マスアサイン不可の属性
 *
 * @var array
 */
protected $guarded = [];

#マスアサインメントの例外

デフォルトでは、$fillable に含まれない属性はマスアサインメント時に静かに破棄されます。これは本番環境では期待される動作ですが、ローカル開発時にはモデルの変更が反映されない原因で混乱することがあります。

必要に応じて、preventSilentlyDiscardingAttributes メソッドを呼び出すことで、マスアサイン不可の属性をセットしようとした際に例外を投げるよう Laravel に指示できます。通常、このメソッドはアプリケーションのサービスプロバイダーの boot メソッド内で呼び出します:

use Illuminate\Database\Eloquent\Model;

/**
 * アプリケーションサービスのブートストラップ
 */
public function boot(): void
{
    Model::preventSilentlyDiscardingAttributes($this->app->isLocal());
}

#アップサート

既存のモデルを更新するか、該当するモデルがなければ新規作成したい場合があります。firstOrCreate と同様に、updateOrCreate メソッドはモデルを永続化するため、手動で save を呼ぶ必要はありません。

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

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

複数の「アップサート」を単一のクエリで実行したい場合は、代わりに upsert メソッドを使用してください。このメソッドの第1引数は挿入または更新する値の配列、第2引数は関連テーブル内でレコードを一意に識別するカラムのリストです。第3引数は、既存のレコードがあった場合に更新するカラムの配列です。upsert メソッドは、モデルでタイムスタンプが有効な場合、自動的に created_atupdated_at のタイムスタンプを設定します。

Flight::upsert([
    ['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99],
    ['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150]
], ['departure', 'destination'], ['price']);
Внимание

SQL Server を除くすべてのデータベースでは、upsert メソッドの第2引数に指定するカラムに「プライマリ」または「ユニーク」インデックスが必要です。さらに、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]));
Внимание

destroy メソッドは各モデルを個別にロードし、delete メソッドを呼び出すため、deletingdeleted イベントが各モデルに対して正しく発火します。

#クエリを使ったモデルの削除

もちろん、Eloquentのクエリを組み立てて、クエリの条件に一致するすべてのモデルを削除できます。 この例では、無効とマークされたすべてのフライトを削除します。 一括更新と同様に、一括削除では削除されるモデルに対してモデルイベントはディスパッチされません:

$deleted = Flight::where('active', 0)->delete();
Внимание

Eloquent で大量削除を実行すると、削除されたモデルに対して deletingdeleted モデルイベントは発火しません。これは、削除クエリ実行時にモデルが実際には取得されないためです。

#ソフトデリート

レコードを実際にデータベースから削除する代わりに、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;

    /**
     * プルーニング対象のモデルクエリを取得します。
     */
    public function prunable(): Builder
    {
        return static::where('created_at', '<=', now()->subMonth());
    }
}

モデルを Prunable としてマークした場合、pruning メソッドを定義できます。このメソッドはモデル削除前に呼ばれ、モデルに関連するファイルなどの追加リソースを削除するのに便利です。

/**
 * プルーニングの準備をします。
 */
protected function pruning(): void
{
    // ...
}

プルーニング対象モデルを設定したら、アプリケーションの App\Console\Kernel クラスで model:prune Artisan コマンドをスケジュールしてください。実行間隔は自由に設定できます。

/**
 * アプリケーションのコマンドスケジュールを定義します。
 */
protected function schedule(Schedule $schedule): void
{
    $schedule->command('model:prune')->daily();
}

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

$schedule->command('model:prune', [
    '--model' => [Address::class, Flight::class],
])->daily();

特定のモデルをプルーニング対象から除外し、他の検出されたモデルのみをプルーニングしたい場合は、--except オプションを使えます。

$schedule->command('model:prune', [
    '--except' => [Address::class, Flight::class],
])->daily();

--pretend オプションを使って model:prune コマンドを実行すると、prunable クエリをテストできます。--pretend を使うと、model:prune コマンドは実際に実行した場合に何件のレコードが削除されるかを単に報告します:

php artisan model:prune --pretend
Внимание

ソフトデリートされたモデルは、プルーニングクエリに該当すると forceDelete により完全に削除されます。

#大量プルーニング

Illuminate\Database\Eloquent\MassPrunable トレイトを使うと、モデルは大量削除クエリでデータベースから削除されます。そのため、pruning メソッドは呼ばれず、deletingdeleted モデルイベントも発火しません。モデルが削除前に取得されないため、プルーニング処理がより効率的になります。

<?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;

    /**
     * プルーニング対象のモデルクエリを取得します。
     */
    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
{
    /**
     * 指定された Eloquent クエリビルダーにスコープを適用します。
     */
    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
{
    /**
     * モデルの "booted" メソッド。
     */
    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
{
    /**
     * モデルの "booted" メソッド。
     */
    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 メソッドを使います。

// すべてのグローバルスコープを解除...
User::withoutGlobalScopes()->get();

// 一部のグローバルスコープを解除...
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
{
    /**
     * 人気のあるユーザーのみを含むクエリスコープ。
     */
    public function scopePopular(Builder $query): void
    {
        $query->where('votes', '>', 100);
    }

    /**
     * アクティブなユーザーのみを含むクエリスコープ。
     */
    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
{
    /**
     * 指定されたタイプのユーザーのみを含むクエリスコープ。
     */
    public function scopeOfType(Builder $query, string $type): void
    {
        $query->where('type', $type);
    }
}

スコープメソッドのシグネチャに期待する引数を追加したら、スコープ呼び出し時に引数を渡せます。

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

#モデルの比較

2つのモデルが「同じ」かどうか判定したい場合があります。isisNot メソッドを使うと、2つのモデルが同じ主キー、テーブル、データベース接続を持つかどうかを素早く確認できます。

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

if ($post->isNot($anotherPost)) {
    // ...
}

isisNot メソッドは、belongsTohasOnemorphTomorphOneリレーションシップでも利用可能です。このメソッドは、関連モデルを取得するクエリを発行せずに比較したい場合に特に便利です。

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

#イベント

Примечание

Eloquentイベントをクライアントサイドアプリケーションに直接ブロードキャストしたい場合は、Laravelのモデルイベントブロードキャストを参照してください。

Eloquentモデルは複数のイベントを発行し、モデルのライフサイクルの以下のタイミングにフックできます:retrievedcreatingcreatedupdatingupdatedsavingsaveddeletingdeletedtrashedforceDeletingforceDeletedrestoringrestoredreplicating

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

モデルイベントをリッスンするには、Eloquentモデルに $dispatchesEvents プロパティを定義します。このプロパティはモデルのライフサイクルの各ポイントを独自のeventクラスにマッピングします。各モデルイベントクラスは、影響を受けるモデルのインスタンスをコンストラクタで受け取ることを想定しています。

<?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;

    /**
     * モデルのイベントマップ。
     *
     * @var array
     */
    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
{
    /**
     * モデルの "booted" メソッド。
     */
    protected static function booted(): void
    {
        static::created(function (User $user) {
            // ...
        });
    }
}

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

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
{
    /**
     * User の "created" イベントを処理します。
     */
    public function created(User $user): void
    {
        // ...
    }

    /**
     * User の "updated" イベントを処理します。
     */
    public function updated(User $user): void
    {
        // ...
    }

    /**
     * User の "deleted" イベントを処理します。
     */
    public function deleted(User $user): void
    {
        // ...
    }

    /**
     * User の "restored" イベントを処理します。
     */
    public function restored(User $user): void
    {
        // ...
    }

    /**
     * User の "forceDeleted" イベントを処理します。
     */
    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 メソッドを呼び出して手動でオブザーバーを登録できます。アプリケーションの App\Providers\EventServiceProvider サービスプロバイダーの boot メソッド内でオブザーバーを登録するのが一般的です。

use App\Models\User;
use App\Observers\UserObserver;

/**
 * アプリケーションのイベントを登録します。
 */
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
{
    /**
     * User の "created" イベントを処理します。
     */
    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();