Laravel 9.x Eloquentの準備

イントロダクション

Laravelは、データベース操作を楽しくする、オブジェクトリレーショナルマッパー(ORM)であるEloquentを用意しています。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 Member --pivot

Inspecting Models

モデルのコードに目を通すだけでは、そのモデルで利用可能な全ての属性とリレーションを判断するのが難しい場合があります。そのような場合は、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という名前の主キーカラムがあることも想定しています。必要に応じて、モデルのprotected $primaryKeyプロパティを定義して、主キーとして機能する別のカラムを指定できます。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

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

さらに、Eloquentは、主キーが増分整数値であることも想定しています。これは、Eloquentが主キーを自動的に整数にキャストすることを意味します。非インクリメントまたは非数値の主キーを使用する場合は、モデルにpublicの$incrementingプロパティを定義し、falseをセットする必要があります。

<?php

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

モデルの主キーが整数でない場合は、モデルにprotectedな$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は辞書式にソートすることができるため、インデックス付きデータベースの保存が効率的です。

モデルへnewUniqueIdメソッドを定義すれば、指定モデルのUUID生成処理をオーバーライドできます。さらに、モデルにuniqueIdsメソッドを定義すれば、UUIDをどのカラムで受け取るのかを指定できます。

use Ramsey\Uuid\Uuid;

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

/**
 * 一意の識別子を受け取るカラムの取得
 *
 * @return array
 */
public function uniqueIds()
{
    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
{
    /**
     * モデルにタイムスタンプを付けるか
     *
     * @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_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
{
    /**
     * このモデルが使用するデータベース接続
     *
     * @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;

/**
 * 全アプリケーションサービスの初期起動処理
 *
 * @return void
 */
public function boot()
{
    Model::preventLazyLoading(! $this->app->isProduction());
}

また、preventSilentlyDiscardingAttributesメソッドを呼び出せば、複数代入非許可な属性へ複数代入しようとしたときに例外を投げるようにLaravelに指示することもできます。これは、ローカル開発時に、モデルのfillable配列へ追加されていない属性をセットしようとしたときに、予期せぬエラーが発生するのを防ぐのに役立ちます。

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

最後に、モデル上の属性にアクセスしようとしたときに、その属性が実際にデータベースから取得されなかったり、その属性が存在しなかったりした場合に、Eloquentへ例外を投げるように指示することもできます。例えば、Eloquentクエリのselect節に属性を追加し忘れた場合などに起こり得ます。

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

Eloquent「厳格モード」の有効化

簡単に使えるようにするため、shouldBeStrictメソッドを呼び出すだけで、前記で説明した3メソッドをすべて有効にできます。

Model::shouldBeStrict(! $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"

コレクション

これまで見てきたように、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) {
    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;

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

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

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

Flight::where('departed', true)
    ->chunkById(200, function ($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モデルしかメモリに保持しないため、リレーションをEagerロードできません。リレーションシップをEagerロードする必要がある場合は、代わりに lazyメソッド の使用を検討してください。

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

use App\Models\Flight;

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

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

use App\Models\User;

$users = User::cursor()->filter(function ($user) {
    return $user->id > 500;
});

foreach ($users as $user) {
    echo $user->id;
}

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

上級サブクエリ

サブクエリのSELECT

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

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

use App\Models\Destination;
use App\Models\Flight;

return Destination::addSelect(['last_flight' => Flight::select('name')
    ->whereColumn('destination_id', 'destinations.id')
    ->orderByDesc('arrived_at')
    ->limit(1)
])->get();

サブクエリの順序

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

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

結果が見つからない場合は、別のアクションを実行したいこともあります。findOrfirstOrメソッドは単一のモデルインスタンスを返しますが、結果が見つからなかった場合は指定クロージャを実行します。クロージャの返却値は、メソッドの結果とみなします。

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

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

Not Found例外

モデルが見つからない場合は、例外を投げたい場合があります。これは、ルートやコントローラでとくに役立ちます。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 ($id) {
    return Flight::findOrFail($id);
});

モデルの取得/生成

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

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\Request;

class FlightController extends Controller
{
    /**
     * 新しいフライトをデータベースに保存
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        // リクエストのバリデーション処理…

        $flight = new Flight;

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

        $flight->save();
    }
}

この例では、受信HTTPリクエストのnameフィールドをApp\Models\Flightモデルインスタンスの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();

複数更新

特定のクエリに一致するモデルに対して更新を実行することもできます。この例では、「アクティブ(active)」でdestinationSan Diegoのすべてのフライトが遅延(delayed)としてマークされます。

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

updateメソッドは、更新する必要のあるカラムを表すカラム名と値のペアの配列を引数に取ります。updateメソッドは、影響を受けた行数を返します。

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

属性の変更の判断

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

isDirtyメソッドは、モデル取得後にそのモデルの属性が変更されたかを判定します。isDirtyメソッドに特定の属性名、あるいは属性の配列を渡せば、いずれかの属性に変更があったかを判定できます。isCleanメソッドは、モデル取得後、属性が変更されていないことを判定します。このメソッドはオプションのattribute引数も受け付けます。

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メソッドを使用して、単一PHPステートメントで新しいモデルを「保存」できます。挿入したモデルインスタンスが、このメソッドにより返されます。

use App\Models\Flight;

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

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

複数代入の脆弱性は、ユーザーから予期していないHTTPリクエストフィールドを渡され、そのフィールドがデータベース内の予想外のカラムを変更する場合に発生します。たとえば、悪意のあるユーザーがHTTPリクエストを介してis_adminパラメータを送信し、それがモデルのcreateメソッドに渡されて、ユーザーが自分自身を管理者に格上げする場合が考えられます。

したがって、Eloquentを使い始めるには、複数代入可能にするモデル属性を定義する必要があります。これは、モデルの$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のfillcreate、およびupdateメソッドに渡たす配列へいちいち特別な注意を払う必要があります。

/**
 * 複数代入不可能な属性
 *
 * @var array
 */
protected $guarded = [];

複数代入例外

複数代入操作を行うときデフォルトで、$fillable配列に含まれない属性は黙って破棄されます。実稼働環境でこれは期待されている動作です。しかし、ローカル開発時では、なぜモデルの変更が反映されないのか混乱させる可能性があります。

もし必要であれば、preventSilentlyDiscardingAttributesメソッドを呼び出し、複数代入不可な属性を埋めようとしたときに例外を投げるよう、Laravelへ指示できます。通常、このメソッドはアプリケーションのサービスプロバイダのbootメソッド内で呼び出します。

use Illuminate\Database\Eloquent\Model;

/**
 * 全アプリケーションサービスの初期起動処理
 *
 * @return void
 */
public function boot()
{
    Model::preventSilentlyDiscardingAttributes($this->app->isLocal());
}

更新/挿入

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

以下の例では、Oaklandが「出発地(departure)」で、San Diegoが「目的地(destination)」のフライトが存在する場合、その「価格(price)」カラムと「割引(discounted)」カラムが更新されます。該当するフライトが存在しない場合は、最初の引数配列を2番目の引数配列とマージした結果の属性を持つ新しいフライトが作成されます。

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

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

Warning!! SQL Server以外のすべてのデータベースでは、upsertメソッドの第2引数のカラムへ"primary"、または"unique"インデックスを指定する必要があります。さらに、MySQLデータベースドライバは、upsertメソッドの第2引数を無視し、常にテーブルの"primary"および"unique"インデックスを既存レコードの検出に使用します。

モデルの削除

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

use App\Models\Flight;

$flight = Flight::find(1);

$flight->delete();

モデルに関連しているすべてのデータベースレコードを削除するには、truncateメソッドを呼びだせます。truncate操作は、モデルの関連テーブルの自動増分IDをリセットします。

Flight::truncate();

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

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

Flight::destroy(1);

Flight::destroy(1, 2, 3);

Flight::destroy([1, 2, 3]);

Flight::destroy(collect([1, 2, 3]));

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

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

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

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

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

ソフトデリート

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\PrunableIlluminate\Database\Eloquent\MassPrunableトレイトを追加してください。モデルにどちらかのトレイトを追加したら、不要なモデルを指定するEloquentのクエリビルダを返すprunableメソッドを実装します。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Prunable;

class Flight extends Model
{
    use Prunable;

    /**
     * 整理可能モデルクエリの取得
     *
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function prunable()
    {
        return static::where('created_at', '<=', now()->subMonth());
    }
}

Prunableとしてモデルを作成する際に、そのモデルへpruningメソッドを定義することもできます。このメソッドはモデルが削除される前に呼び出されます。このメソッドは、モデルがデータベースから永久に削除される前に、保存しているファイルなど、モデルに関連する追加リソースを削除するために役立ちます。

/**
 * 整理可能モデルの準備
 *
 * @return void
 */
protected function pruning()
{
    //
}

Prunableモデルを設定した後は、アプリケーションのApp\Console\Kernelクラスでmodel:prune Artisanコマンドをスケジュールする必要があります。このコマンドを実行する適切な間隔は自由に選択できます。

/**
 * アプリケーションのコマンドスケジュールを定義
 *
 * @param  \Illuminate\Console\Scheduling\Schedule  $schedule
 * @return void
 */
protected function schedule(Schedule $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();

model:pruneコマンドを--pretendオプション付きで実行することで、prunableクエリをテストできます。このオプションを付けると、model:pruneコマンドが実際にコマンドを実行する場合、いくつのレコードを整理するかだけを報告します。

php artisan model:prune --pretend

Warning!! Prunableクエリに一致した場合、ソフトデリートするモデルでも、永久的に削除(forceDelete)します。

複数整理

モデルへIlluminate\Database\Eloquent\MassPrunableトレイトが付与されると、モデルは複数削除クエリを使ってデータベースから削除されます。そのため、pruningメソッドを呼び出しませんし、deletingdeletedのモデルイベントも発行しません。これはモデルの削除前で実際に取得しないため、整理処理をより効率的に行うことができるからです。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\MassPrunable;

class Flight extends Model
{
    use MassPrunable;

    /**
     * 整理可能モデルクエリの取得
     *
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function prunable()
    {
        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独自のソフトデリート機能は、グローバルスコープを利用してデータベースから「削除していない」モデルのみを取得します。独自のグローバルスコープを作成すれば、指定したモデルですべてのクエリが同じ制約を受けるようにする、便利で簡単な方法が利用できます。

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

グローバルスコープの作成は簡単です。まず、Illuminate\Database\Eloquent\Scopeインターフェイスの実装クラスを定義します。Laravelには、スコープクラスを配置する決まった場所がないため、このクラスは任意のディレクトリへ自由に配置できます。

Scopeインターフェイスでは、applyという1つのメソッドを実装する必要があります。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クエリビルダにスコープを適用
     *
     * @param  \Illuminate\Database\Eloquent\Builder  $builder
     * @param  \Illuminate\Database\Eloquent\Model  $model
     * @return void
     */
    public function apply(Builder $builder, Model $model)
    {
        $builder->where('created_at', '<', now()->subYears(2000));
    }
}

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

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

モデルにグローバルスコープを割り当てるには、モデルのbootedメソッドをオーバーライドし、モデルのaddGlobalScopeメソッドを呼び出す必要があります。addGlobalScopeメソッドは、スコープのインスタンスだけを引数に取ります。

<?php

namespace App\Models;

use App\Models\Scopes\AncientScope;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * モデルの「起動」メソッド
     *
     * @return void
     */
    protected static function booted()
    {
        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
{
    /**
     * モデルの「起動」メソッド
     *
     * @return void
     */
    protected static function booted()
    {
        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();

ローカルスコープ

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

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

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * 人気のあるユーザーのみを含むようにクエリのスコープを設定
     *
     * @param  \Illuminate\Database\Eloquent\Builder  $query
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopePopular($query)
    {
        return $query->where('votes', '>', 100);
    }

    /**
     * アクティブユーザーのみを含むようにクエリのスコープを設定
     *
     * @param  \Illuminate\Database\Eloquent\Builder  $query
     * @return void
     */
    public function scopeActive($query)
    {
        $query->where('active', 1);
    }
}

ローカルスコープの利用

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

use App\Models\User;

$users = User::popular()->active()->orderBy('created_at')->get();

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

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

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

$users = App\Models\User::popular()->orWhere->active()->get();

動的スコープ

パラメータを受け入れるスコープを定義したい場合もあるでしょう。使用するには、スコープメソッドの引数にパラメータを追加するだけです。スコープパラメータは、$queryパラメータの後に定義する必要があります。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * 特定のタイプのユーザーのみを含むようにクエリのスコープを設定
     *
     * @param  \Illuminate\Database\Eloquent\Builder  $query
     * @param  mixed  $type
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeOfType($query, $type)
    {
        return $query->where('type', $type);
    }
}

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

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

モデルの比較

2つのモデルが「同じ」であるかを判定する必要がある場合があるでしょう。2つのモデルに同じ主キー、テーブル、およびデータベース接続があるかどうかを手早く検証するために、isisNotメソッドを使用できます。

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

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

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

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

イベント

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

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

retrievedイベントは、既存のモデルをデータベースから取得したときにディスパッチします。新しいモデルをはじめて保存するときは、creatingイベントとcreatedイベントをディスパッチします。updatingupdatedイベントは、既存のモデルを変更し、saveメソッドが呼び出されたときにディスパッチします。savingsavedイベントは、モデルを作成または更新したときにディスパッチします。モデルの属性に変化がない場合でも、ディスパッチします。イベント名が-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
     */
    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
{
    /**
     * モデルの「起動」メソッド
     *
     * @return void
     */
    protected static function booted()
    {
        static::created(function ($user) {
            //
        });
    }
}

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

use function Illuminate\Events\queueable;

static::created(queueable(function ($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
{
    /**
     * ユーザーの"created"イベントの処理
     *
     * @param  \App\Models\User  $user
     * @return void
     */
    public function created(User $user)
    {
        //
    }

    /**
     * ユーザーの"updated"イベントの処理
     *
     * @param  \App\Models\User  $user
     * @return void
     */
    public function updated(User $user)
    {
        //
    }

    /**
     * ユーザーの"deleted"イベントの処理
     *
     * @param  \App\Models\User  $user
     * @return void
     */
    public function deleted(User $user)
    {
        //
    }

    /**
     * ユーザーの"restored"イベントの処理
     *
     * @param  \App\Models\User  $user
     * @return void
     */
    public function restored(User $user)
    {
        //
    }

    /**
     * ユーザーの"forceDeleted"イベントの処理
     *
     * @param  \App\Models\User  $user
     * @return void
     */
    public function forceDeleted(User $user)
    {
        //
    }
}

オブザーバーを登録するには、監視するモデルでobserveメソッドを呼び出す必要があります。アプリケーションのApp\Providers\EventServiceProviderサービスプロバイダのbootメソッドにオブザーバーを登録できます。

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

/**
 * アプリケーションの全イベントの登録
 *
 * @return void
 */
public function boot()
{
    User::observe(UserObserver::class);
}

あるいは、アプリケーションのApp\Providers\EventServiceProviderクラスの$observersプロパティへオブザーバをリストすることもできます。

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

/**
 * アプリケーションのモデルオブザーバ
 *
 * @var array
 */
protected $observers = [
    User::class => [UserObserver::class],
];

Note: オブザーバーがリッスンできる他のイベントには、savingretrievedなどがあります。こうしたイベントについては、イベントのドキュメントで説明しています。

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

データベーストランザクション内でモデルを作成している場合、データベーストランザクションがコミットされた後にのみイベントハンドラを実行するようにオブザーバへ指示したい場合があるでしょう。これを実現するには、オブザーバで$afterCommitプロパティを定義します。データベーストランザクションが進行中でなければ、イベントハンドラは直ちに実行されます。

<?php

namespace App\Observers;

use App\Models\User;

class UserObserver
{
    /**
     * すべてのトランザクションがコミットされた後にイベントを処理
     *
     * @var bool
     */
    public $afterCommit = true;

    /**
     * ユーザーの"created"イベントの処理
     *
     * @param  \App\Models\User  $user
     * @return void
     */
    public function created(User $user)
    {
        //
    }
}

イベントのミュート

モデルにより発生するすべてのイベントを一時的に「ミュート」したい場合が起こりえます。実現するには、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();

また、イベントをディスパッチせずに、与えられたモデルを「更新(update)」、「削除(delete)」、「ソフトデリート(soft delete)」、「復元(restore)」、「複製(replicate)」できます。

$user->deleteQuietly();
$user->forceDeleteQuietly();
$user->restoreQuietly();

ドキュメント章別ページ

ヘッダー項目移動

注目:アイコン:ページ内リンク設置(リンクがないヘッダーへの移動では、リンクがある以前のヘッダーのハッシュをURLへ付加します。

移動

クリックで即時移動します。

設定

適用ボタンクリック後に、全項目まとめて適用されます。

カラーテーマ
和文指定 Pagination
和文指定 Scaffold
Largeスクリーン表示幅
インデント
本文フォント
コードフォント
フォント適用確認

フォントの指定フィールドから、フォーカスが外れると、当ブロックの内容に反映されます。EnglishのDisplayもPreviewしてください。

フォント設定時、表示に不具合が出た場合、当サイトのクッキーを削除してください。

バックスラッシュを含むインライン\Code\Blockの例です。

以下はコードブロックの例です。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * ユーザに関連する電話レコードを取得
     */
    public function phone()
    {
        return $this->hasOne('App\Phone');
    }
}

設定を保存する前に、表示が乱れないか必ず確認してください。CSSによるフォントファミリー指定の知識がない場合は、フォントを変更しないほうが良いでしょう。

キーボード・ショートカット

オープン操作

PDC

ページ(章)移動の左オフキャンバスオープン

HA

ヘッダー移動モーダルオープン

MS

移動/設定の右オフキャンバスオープン

ヘッダー移動

T

最初のヘッダーへ移動

E

最後のヘッダーへ移動

NJ

次ヘッダー(H2〜H4)へ移動

BK

前ヘッダー(H2〜H4)へ移動

その他

?

このヘルプページ表示
閉じる