Skip to content

Eloquent: はじめに

イントロダクション

LaravelにはEloquentが含まれており、これはデータベースとのやり取りを楽しくするオブジェクト関係マッパー(ORM)です。Eloquentを使用する場合、各データベーステーブルにはそのテーブルとやり取りするために使用される対応する「モデル」があります。データベーステーブルからレコードを取得するだけでなく、Eloquentモデルを使用してテーブルからレコードを挿入、更新、削除することもできます。

Note

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

Laravel Bootcamp

Laravelを初めて使う方は、Laravel Bootcampに飛び込んでみてください。Laravel Bootcampでは、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
{
    // ...
}

テーブル名

上記の例では、Flightモデルに対応するデータベーステーブルを明示的に指定していないことにお気づきでしょう。慣例により、クラスの「スネークケース」の複数形の名前がテーブル名として使用されます。ただし、別の名前が明示的に指定されていない限りです。したがって、この場合、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は主キーが増分する整数値であると仮定します。つまり、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';
}

「複合」主キー

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

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

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文字です。順序付き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カラムが存在することを期待します。Eloquentは、モデルが作成または更新されるときにこれらのカラムの値を自動的に設定します。これらのカラムをEloquentで自動的に管理したくない場合は、モデルに$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 = 'mysql';
}

デフォルトの属性値

新しくインスタンス化されたモデルインスタンスには、デフォルトで属性値が含まれません。モデルの一部の属性にデフォルト値を定義したい場合は、モデルに$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メソッドを呼び出すことで、設定不可能な属性を設定しようとしたときに例外をスローするように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();

Note

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のallgetなどのメソッドは、データベースから複数のレコードを取得します。ただし、これらのメソッドはプレーンな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の反復可能なインターフェースを実装しているため、コレクションを配列のようにループすることができます。

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

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

カーソル

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

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

Warning

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

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

use App\Models\Flight;

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

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

単一モデル / 集計の取得

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

use App\Models\Flight;

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

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

// クエリ制約に一致する最初のモデルを取得する代替方法...
$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;

// 名前でフライトを取得するか、存在しない場合は作成...
$flight = Flight::firstOrCreate([
    'name' => 'London to Paris'
]);

// 名前でフライトを取得するか、名前、遅延、到着時間の属性で作成...
$flight = Flight::firstOrCreate(
    ['name' => 'London to Paris'],
    ['delayed' => 1, 'arrival_time' => '11:30']
);

// 名前でフライトを取得するか、新しいFlightインスタンスをインスタンス化...
$flight = Flight::firstOrNew([
    'name' => 'London to Paris'
]);

// 名前でフライトを取得するか、名前、遅延、到着時間の属性でインスタンス化...
$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');
    }
}
$flight->name = $request->name;

$flight->save();

return redirect('/flights');

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

あるいは、createメソッドを使用して、1つの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メソッドを呼び出す必要はありません。

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

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

マス更新

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

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

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

Warning

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(); // 元の属性の配列...

マスアサインメント

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
{
    /**
     * マスアサイン可能な属性。
     *
     * @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配列に含まれていない属性は、マスアサインメント操作を実行する際に静かに破棄されます。本番環境ではこれが期待される動作ですが、ローカル開発中には、モデルの変更が効果を発揮しない理由について混乱を招く可能性があります。

必要に応じて、Laravelに、フィル可能でない属性を試みて埋める際に例外をスローするように指示できます。これは、アプリケーションのAppServiceProviderクラスのbootメソッドでpreventSilentlyDiscardingAttributesメソッドを呼び出すことで実行できます。

use Illuminate\Database\Eloquent\Model;

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

アップサート

Eloquentのupsertメソッドは、単一のアトミック操作でレコードを更新または作成するために使用できます。メソッドの最初の引数は、挿入または更新する値で構成され、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']);
Flight::upsert([
    ['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99],
    ['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150]
], uniqueBy: ['departure', 'destination'], update: ['price']);

Warning

upsertメソッドの2番目の引数にある列には、SQL Serverを除くすべてのデータベースで「主キー」または「ユニーク」インデックスが必要です。さらに、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);

Warning

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

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

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

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

Warning

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

Note

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

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

use Illuminate\Support\Facades\Schedule;

Schedule::command('model:prune')->daily();

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

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

整理中に特定のモデルを除外したい場合は、--exceptオプションを使用できます:

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

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

php artisan model:prune --pretend

Warning

ソフトデリートされたモデルは、整理可能なクエリに一致する場合、完全に削除(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;

    /**
     * 整理可能なモデルクエリを取得します。
     */
    public function prunable(): Builder
    {
        return static::where('created_at', '<=', now()->subMonth());
    }
}

```

モデルの複製

既存のモデルインスタンスの未保存のコピーを作成するには、replicateメソッドを使用できます。このメソッドは、多くの同じ属性を共有するモデルインスタンスがある場合に特に便利です。 php use App\Models\Address; $shipping = Address::create([ 'type' => 'shipping', 'line_1' => '123 Example Street', 'city' => 'Victorville', 'state' => 'CA', 'postcode' => '90001', ]); $billing = $shipping->replicate()->fill([ 'type' => 'billing' ]); $billing->save();

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

$flight = Flight::create([
    'destination' => 'LAX',
    'origin' => 'LHR',
    'last_flown' => '2020-03-04 11:00:00',
    'last_pilot_id' => 747,
]);

$flight = $flight->replicate([
    'last_flown',
    'last_pilot_id'
]);

クエリスコープ

グローバルスコープ

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

スコープの生成

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

php artisan make:scope AncientScope

グローバルスコープの記述

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

<?php

namespace App\Models\Scopes;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;

class AncientScope implements Scope
{
    /**
     * Apply the scope to a given Eloquent query builder.
     */
    public function apply(Builder $builder, Model $model): void
    {
        $builder->where('created_at', '<', now()->subYears(2000));
    }
}

Note

グローバルスコープがクエリの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)) {
    // ...
}

isメソッドとisNotメソッドは、belongsTohasOnemorphTo、およびmorphOneリレーションを使用する場合にも利用できます。このメソッドは、関連モデルを取得するクエリを発行せずに関連モデルを比較する場合に特に便利です。

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

イベント

Note

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

Eloquentモデルは、いくつかのイベントをディスパッチします。これにより、モデルのライフサイクルの次のポイントにフックすることができます:retrievedcreatingcreatedupdatingupdatedsavingsaveddeletingdeletedtrashedforceDeletingforceDeletedrestoringrestored、およびreplicating

retrieved イベントは、既存のモデルがデータベースから取得されたときにディスパッチされます。新しいモデルが初めて保存されるときは、creatingcreated イベントがディスパッチされます。既存のモデルが変更され、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;

    /**
     * モデルのイベントマップ
     *
     * @var array<string, string>
     */
    protected $dispatchesEvents = [
        'saved' => UserSaved::class,
        'deleted' => UserDeleted::class,
    ];
}

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

Warning

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 メソッドを呼び出して手動でオブザーバーを登録することもできます。アプリケーションの AppServiceProvider クラスの boot メソッドでオブザーバーを登録することができます。

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

/**
 * アプリケーションサービスのブートストラップ
 */
public function boot(): void
{
    User::observe(UserObserver::class);
}

Note

オブザーバーは savingretrieved などの追加のイベントをリスニングできます。これらのイベントは イベント ドキュメント内で説明されています。

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

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

ユーザーノート