コンテンツへスキップ

Eloquent: リレーションシップ

イントロダクション

データベースのテーブルは、多くの場合、互いに関連しています。たとえば、ブログ投稿には多くのコメントがあるかもしれませんし、注文はそれを行ったユーザーに関連しているでしょう。Eloquentはこれらのリレーションシップの管理と操作を簡単にし、さまざまな一般的なリレーションをサポートしています。

リレーションの定義

Eloquentのリレーションは、Eloquentモデルクラスのメソッドとして定義します。リレーションは強力なクエリビルダとしても機能するため、リレーションをメソッドとして定義することで、強力なメソッドチェーンとクエリ機能が提供されます。たとえば、このpostsリレーションに追加のクエリ制約をチェーンできます。

1$user->posts()->where('active', 1)->get();

しかし、リレーションシップの使用法を深く掘り下げる前に、Eloquentがサポートする各リレーションシップの定義方法を学びましょう。

1対1 / Has One

1対1リレーションは、データベースリレーションの非常に基本的なタイプです。たとえば、Userモデルは1つのPhoneモデルに関連付けられているかもしれません。このリレーションを定義するには、Userモデルにphoneメソッドを配置します。phoneメソッドはhasOneメソッドを呼び出し、その結果を返すべきです。hasOneメソッドは、モデルのIlluminate\Database\Eloquent\Modelベースクラスを介してモデルで使用できます。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6use Illuminate\Database\Eloquent\Relations\HasOne;
7 
8class User extends Model
9{
10 /**
11 * Get the phone associated with the user.
12 */
13 public function phone(): HasOne
14 {
15 return $this->hasOne(Phone::class);
16 }
17}

hasOneメソッドに渡す最初の引数は、関連するモデルクラスの名前です。リレーションを定義したら、Eloquentの動的プロパティを使用して関連レコードを取得できます。動的プロパティを使用すると、モデルで定義されたプロパティであるかのようにリレーションメソッドにアクセスできます。

1$phone = User::find(1)->phone;

Eloquentは親モデル名に基づいてリレーションの外部キーを決定します。この場合、Phoneモデルは自動的にuser_id外部キーを持つと想定されます。この規約を上書きしたい場合は、hasOneメソッドの2番目の引数を渡します。

1return $this->hasOne(Phone::class, 'foreign_key');

さらに、Eloquentは、外部キーが親の主キーカラムに一致する値を持つことを想定しています。つまり、Eloquentは、Phoneレコードのuser_idカラムで、ユーザーのidカラムの値を検索します。リレーションにid以外の主キー値やモデルの$primaryKeyプロパティを使用させたい場合は、hasOneメソッドに3番目の引数を渡せます。

1return $this->hasOne(Phone::class, 'foreign_key', 'local_key');

リレーションの逆引きの定義

これで、UserモデルからPhoneモデルにアクセスできるようになりました。次に、電話を所有するユーザーにアクセスできるPhoneモデルのリレーションを定義しましょう。hasOneリレーションの逆は、belongsToメソッドを使用して定義できます。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6use Illuminate\Database\Eloquent\Relations\BelongsTo;
7 
8class Phone extends Model
9{
10 /**
11 * Get the user that owns the phone.
12 */
13 public function user(): BelongsTo
14 {
15 return $this->belongsTo(User::class);
16 }
17}

userメソッドを呼び出すと、EloquentはPhoneモデルのuser_idカラムに一致するidを持つUserモデルを見つけようとします。

Eloquentは、リレーションメソッドの名前を調べ、メソッド名に_idを接尾辞として付けることで外部キー名を決定します。したがって、この場合、EloquentはPhoneモデルにuser_idカラムがあると想定します。ただし、Phoneモデルの外部キーがuser_idでない場合は、belongsToメソッドの2番目の引数としてカスタムキー名を渡せます。

1/**
2 * Get the user that owns the phone.
3 */
4public function user(): BelongsTo
5{
6 return $this->belongsTo(User::class, 'foreign_key');
7}

親モデルが主キーとしてidを使用していない場合、または別のカラムを使用して関連モデルを検索したい場合は、belongsToメソッドの3番目の引数に親テーブルのカスタムキーを指定できます。

1/**
2 * Get the user that owns the phone.
3 */
4public function user(): BelongsTo
5{
6 return $this->belongsTo(User::class, 'foreign_key', 'owner_key');
7}

1対多 / Has Many

1対多リレーションは、単一のモデルが1つ以上の子モデルの親であるリレーションを定義するために使用します。たとえば、ブログの投稿には無限の数のコメントがあるかもしれません。他のすべてのEloquentリレーションと同様に、1対多リレーションはEloquentモデルにメソッドを定義することで定義します。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6use Illuminate\Database\Eloquent\Relations\HasMany;
7 
8class Post extends Model
9{
10 /**
11 * Get the comments for the blog post.
12 */
13 public function comments(): HasMany
14 {
15 return $this->hasMany(Comment::class);
16 }
17}

Eloquentは、Commentモデルの適切な外部キーカラムを自動的に決定することを忘れないでください。規約により、Eloquentは親モデルの「スネークケース」名を取得し、それに_idを付けます。したがって、この例では、EloquentはCommentモデルの外部キーカラムがpost_idであると想定します。

リレーションメソッドを定義したら、commentsプロパティにアクセスすることで、関連するコメントのコレクションにアクセスできます。Eloquentは「動的リレーションプロパティ」を提供しているため、モデルのプロパティとして定義されているかのようにリレーションメソッドにアクセスできることを忘れないでください。

1use App\Models\Post;
2 
3$comments = Post::find(1)->comments;
4 
5foreach ($comments as $comment) {
6 // ...
7}

すべてのリレーションはクエリビルダとしても機能するため、commentsメソッドを呼び出し、クエリに条件をチェーンし続けることで、リレーションクエリにさらに制約を追加できます。

1$comment = Post::find(1)->comments()
2 ->where('title', 'foo')
3 ->first();

hasOneメソッドと同様に、hasManyメソッドに追加の引数を渡すことで、外部キーとローカルキーをオーバーライドすることもできます。

1return $this->hasMany(Comment::class, 'foreign_key');
2 
3return $this->hasMany(Comment::class, 'foreign_key', 'local_key');

子モデルへの親モデルの自動ハイドレート

EloquentのEagerロードを利用している場合でも、子モデルをループ処理しながら子モデルから親モデルにアクセスしようとすると、「N+1」クエリ問題が発生する可能性があります。

1$posts = Post::with('comments')->get();
2 
3foreach ($posts as $post) {
4 foreach ($post->comments as $comment) {
5 echo $comment->post->title;
6 }
7}

上の例では、すべてのPostモデルに対してコメントがEagerロードされているにもかかわらず、Eloquentは各子Commentモデルに親のPostを自動的にハイドレートしないため、「N+1」クエリ問題が発生しています。

Eloquentが親モデルを子モデルに自動的にハイドレートするようにしたい場合は、hasManyリレーションを定義する際にchaperoneメソッドを呼び出すことができます。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6use Illuminate\Database\Eloquent\Relations\HasMany;
7 
8class Post extends Model
9{
10 /**
11 * Get the comments for the blog post.
12 */
13 public function comments(): HasMany
14 {
15 return $this->hasMany(Comment::class)->chaperone();
16 }
17}

または、実行時に自動的な親のハイドレートを選択したい場合は、リレーションをEagerロードする際にchaperoneモデルを呼び出すことができます。

1use App\Models\Post;
2 
3$posts = Post::with([
4 'comments' => fn ($comments) => $comments->chaperone(),
5])->get();

1対多 (逆) / Belongs To

これで、投稿のすべてのコメントにアクセスできるようになったので、コメントがその親投稿にアクセスできるようにするリレーションを定義しましょう。hasManyリレーションの逆を定義するには、子モデルにbelongsToメソッドを呼び出すリレーションメソッドを定義します。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6use Illuminate\Database\Eloquent\Relations\BelongsTo;
7 
8class Comment extends Model
9{
10 /**
11 * Get the post that owns the comment.
12 */
13 public function post(): BelongsTo
14 {
15 return $this->belongsTo(Post::class);
16 }
17}

リレーションを定義したら、post「動的リレーションプロパティ」にアクセスすることで、コメントの親投稿を取得できます。

1use App\Models\Comment;
2 
3$comment = Comment::find(1);
4 
5return $comment->post->title;

上記の例では、EloquentはCommentモデルのpost_idカラムに一致するidを持つPostモデルを見つけようとします。

Eloquentは、リレーションメソッドの名前を調べ、メソッド名の後ろに_と親モデルの主キーカラム名を付け加えることで、デフォルトの外部キー名を決定します。したがって、この例では、Eloquentはcommentsテーブル上のPostモデルの外部キーがpost_idであると想定します。

ただし、リレーションシップの外部キーがこれらの規約に従っていない場合は、belongsToメソッドの2番目の引数としてカスタム外部キー名を渡すことができます。

1/**
2 * Get the post that owns the comment.
3 */
4public function post(): BelongsTo
5{
6 return $this->belongsTo(Post::class, 'foreign_key');
7}

親モデルが主キーとしてidを使用していない場合、または別のカラムを使用して関連モデルを検索したい場合は、belongsToメソッドの3番目の引数に親テーブルのカスタムキーを指定できます。

1/**
2 * Get the post that owns the comment.
3 */
4public function post(): BelongsTo
5{
6 return $this->belongsTo(Post::class, 'foreign_key', 'owner_key');
7}

デフォルトモデル

belongsTohasOnehasOneThroughmorphOneリレーションでは、指定したリレーションがnullの場合に返されるデフォルトモデルを定義できます。このパターンは、Nullオブジェクトパターンとしてよく知られており、コード内の条件付きチェックを削除するのに役立ちます。以下の例では、Postモデルにユーザーが添付されていない場合、userリレーションは空のApp\Models\Userモデルを返します。

1/**
2 * Get the author of the post.
3 */
4public function user(): BelongsTo
5{
6 return $this->belongsTo(User::class)->withDefault();
7}

デフォルトモデルに属性を設定するには、withDefaultメソッドに配列またはクロージャを渡します。

1/**
2 * Get the author of the post.
3 */
4public function user(): BelongsTo
5{
6 return $this->belongsTo(User::class)->withDefault([
7 'name' => 'Guest Author',
8 ]);
9}
10 
11/**
12 * Get the author of the post.
13 */
14public function user(): BelongsTo
15{
16 return $this->belongsTo(User::class)->withDefault(function (User $user, Post $post) {
17 $user->name = 'Guest Author';
18 });
19}

Belongs Toリレーションのクエリ

「belongs to」リレーションの子をクエリする場合、対応するEloquentモデルを取得するために手動でwhere句を構築できます。

1use App\Models\Post;
2 
3$posts = Post::where('user_id', $user->id)->get();

しかし、whereBelongsToメソッドを使用する方が便利でしょう。このメソッドは、指定されたモデルに対して適切なリレーションと外部キーを自動的に決定します。

1$posts = Post::whereBelongsTo($user)->get();

whereBelongsToメソッドにコレクションインスタンスを渡すこともできます。そうすると、Laravelはコレクション内のいずれかの親モデルに属するモデルを取得します。

1$users = User::where('vip', true)->get();
2 
3$posts = Post::whereBelongsTo($users)->get();

デフォルトで、Laravelはモデルのクラス名に基づいて、指定モデルに関連するリレーションを決定します。しかし、whereBelongsToメソッドの2番目の引数としてリレーション名を指定することで、手動で指定できます。

1$posts = Post::whereBelongsTo($user, 'author')->get();

Has One Of Many

モデルに多くの関連モデルがある場合でも、リレーションの「最新」または「最古」の関連モデルを簡単に取得したい場合があります。たとえば、Userモデルは多くのOrderモデルに関連しているかもしれませんが、ユーザーが最後に行った注文を簡単に操作する便利な方法を定義したい場合があります。これは、hasOneリレーションタイプとofManyメソッドを組み合わせて実現できます。

1/**
2 * Get the user's most recent order.
3 */
4public function latestOrder(): HasOne
5{
6 return $this->hasOne(Order::class)->latestOfMany();
7}

同様に、リレーションの「最古」、つまり最初の関連モデルを取得するメソッドを定義できます。

1/**
2 * Get the user's oldest order.
3 */
4public function oldestOrder(): HasOne
5{
6 return $this->hasOne(Order::class)->oldestOfMany();
7}

デフォルトでは、latestOfManyoldestOfManyメソッドは、ソート可能なモデルの主キーに基づいて最新または最古の関連モデルを取得します。しかし、時には異なるソート基準を使用して、より大きなリレーションから単一のモデルを取得したい場合があります。

たとえば、ofManyメソッドを使用すると、ユーザーの最も高価な注文を取得できます。ofManyメソッドは、最初の引数としてソート可能な列を受け取り、関連モデルをクエリするときに適用する集計関数(minまたはmax)を受け取ります。

1/**
2 * Get the user's largest order.
3 */
4public function largestOrder(): HasOne
5{
6 return $this->hasOne(Order::class)->ofMany('price', 'max');
7}

PostgreSQLはUUIDカラムに対してMAX関数を実行することをサポートしていないため、現在、PostgreSQLのUUIDカラムと組み合わせてone-of-manyリレーションを使用することはできません。

「多」リレーションを「Has One」リレーションに変換する

多くの場合、latestOfManyoldestOfMany、またはofManyメソッドを使用して単一のモデルを取得するとき、同じモデルに対してすでに「has many」リレーションが定義されています。Laravelでは、リレーションに対してoneメソッドを呼び出すことで、このリレーションを簡単に「has one」リレーションに変換できます。

1/**
2 * Get the user's orders.
3 */
4public function orders(): HasMany
5{
6 return $this->hasMany(Order::class);
7}
8 
9/**
10 * Get the user's largest order.
11 */
12public function largestOrder(): HasOne
13{
14 return $this->orders()->one()->ofMany('price', 'max');
15}

高度なHas One of Manyリレーション

より高度な「has one of many」リレーションを構築することが可能です。たとえば、Productモデルには、新しい価格が公開された後でもシステムに保持される多くの関連Priceモデルがあるかもしれません。さらに、製品の新しい価格データは、published_atカラムを介して将来の日付で有効になるように事前に公開できる場合があります。

要約すると、公開日が未来ではない最新の公開価格を取得する必要があります。さらに、2つの価格の公開日が同じ場合は、IDが最も大きい価格を優先します。これを実現するには、最新の価格を決定するソート可能な列を含む配列をofManyメソッドに渡す必要があります。さらに、クロージャがofManyメソッドの2番目の引数として提供されます。このクロージャは、リレーションシップクエリに公開日の制約を追加する責任があります。

1/**
2 * Get the current pricing for the product.
3 */
4public function currentPricing(): HasOne
5{
6 return $this->hasOne(Price::class)->ofMany([
7 'published_at' => 'max',
8 'id' => 'max',
9 ], function (Builder $query) {
10 $query->where('published_at', '<', now());
11 });
12}

Has One Through

「has-one-through」リレーションは、別のモデルとの1対1のリレーションを定義します。しかし、このリレーションは、宣言するモデルが第3のモデルを*経由*して、別のモデルの1つのインスタンスと一致できることを示します。

例えば、自動車修理工場のアプリケーションでは、各Mechanicモデルは1つのCarモデルに関連付けられ、各Carモデルは1つのOwnerモデルに関連付けられているかもしれません。整備士と所有者はデータベース内で直接的な関係はありませんが、整備士はCarモデルを*経由*して所有者にアクセスできます。このリレーションを定義するために必要なテーブルを見てみましょう。

1mechanics
2 id - integer
3 name - string
4
5cars
6 id - integer
7 model - string
8 mechanic_id - integer
9
10owners
11 id - integer
12 name - string
13 car_id - integer

リレーションのテーブル構造を確認したので、Mechanicモデルにリレーションを定義しましょう。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6use Illuminate\Database\Eloquent\Relations\HasOneThrough;
7 
8class Mechanic extends Model
9{
10 /**
11 * Get the car's owner.
12 */
13 public function carOwner(): HasOneThrough
14 {
15 return $this->hasOneThrough(Owner::class, Car::class);
16 }
17}

hasOneThroughメソッドに渡す最初の引数はアクセスしたい最終的なモデルの名前で、2番目の引数は中間モデルの名前です。

または、関連するリレーションがリレーションに関与するすべてのモデルですでに定義されている場合は、throughメソッドを呼び出してそれらのリレーションの名前を指定することで、「has-one-through」リレーションを流暢に定義できます。たとえば、Mechanicモデルにcarsリレーションがあり、Carモデルにownerリレーションがある場合、整備士と所有者を接続する「has-one-through」リレーションを次のように定義できます。

1// String based syntax...
2return $this->through('cars')->has('owner');
3 
4// Dynamic syntax...
5return $this->throughCars()->hasOwner();

キーの規約

リレーションのクエリを実行する際には、通常のEloquent外部キー規約が使用されます。リレーションのキーをカスタマイズしたい場合は、hasOneThroughメソッドの第3引数と第4引数として渡すことができます。第3引数は中間モデルの外部キーの名前です。第4引数は最終モデルの外部キーの名前です。第5引数はローカルキーで、第6引数は中間モデルのローカルキーです。

1class Mechanic extends Model
2{
3 /**
4 * Get the car's owner.
5 */
6 public function carOwner(): HasOneThrough
7 {
8 return $this->hasOneThrough(
9 Owner::class,
10 Car::class,
11 'mechanic_id', // Foreign key on the cars table...
12 'car_id', // Foreign key on the owners table...
13 'id', // Local key on the mechanics table...
14 'id' // Local key on the cars table...
15 );
16 }
17}

または、前述のように、関連するリレーションがリレーションシップに関与するすべてのモデルで既に定義されている場合、throughメソッドを呼び出してそれらのリレーションシップの名前を指定することで、「has-one-through」リレーションを流暢に定義できます。このアプローチは、既存のリレーションですでに定義されているキーの規約を再利用できるという利点があります。

1// String based syntax...
2return $this->through('cars')->has('owner');
3 
4// Dynamic syntax...
5return $this->throughCars()->hasOwner();

Has Many Through

「has-many-through」リレーションは、中間リレーションを介して遠いリレーションにアクセスする便利な方法を提供します。たとえば、Laravel Cloudのようなデプロイメントプラットフォームを構築しているとしましょう。Applicationモデルは、中間Environmentモデルを介して多くのDeploymentモデルにアクセスするかもしれません。この例を使用すると、特定のアプリケーションのすべてのデプロイメントを簡単に収集できます。このリレーションを定義するために必要なテーブルを見てみましょう。

1applications
2 id - integer
3 name - string
4
5environments
6 id - integer
7 application_id - integer
8 name - string
9
10deployments
11 id - integer
12 environment_id - integer
13 commit_hash - string

リレーションのテーブル構造を調べたので、Applicationモデルにリレーションを定義しましょう。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6use Illuminate\Database\Eloquent\Relations\HasManyThrough;
7 
8class Application extends Model
9{
10 /**
11 * Get all of the deployments for the application.
12 */
13 public function deployments(): HasManyThrough
14 {
15 return $this->hasManyThrough(Deployment::class, Environment::class);
16 }
17}

hasManyThroughメソッドに渡す最初の引数は、アクセスしたい最終的なモデルの名前で、2番目の引数は中間モデルの名前です。

あるいは、リレーションに関与するすべてのモデルで関連するリレーションが既に定義されている場合は、throughメソッドを呼び出し、それらのリレーションの名前を指定することで、「has-many-through」リレーションを流暢に定義できます。例えば、Applicationモデルにenvironmentsリレーションがあり、Environmentモデルにdeploymentsリレーションがある場合、アプリケーションとデプロイメントを接続する「has-many-through」リレーションを次のように定義できます。

1// String based syntax...
2return $this->through('environments')->has('deployments');
3 
4// Dynamic syntax...
5return $this->throughEnvironments()->hasDeployments();

Deploymentモデルのテーブルにはapplication_idカラムが含まれていませんが、hasManyThroughリレーションは$application->deploymentsを介してアプリケーションのデプロイメントへのアクセスを提供します。これらのモデルを取得するために、Eloquentは中間Environmentモデルのテーブルのapplication_idカラムを検査します。関連する環境IDを見つけた後、それらはDeploymentモデルのテーブルをクエリするために使用されます。

キーの規約

リレーションのクエリを実行する際には、典型的なEloquent外部キー規約が使用されます。リレーションのキーをカスタマイズしたい場合は、hasManyThroughメソッドの第3引数と第4引数として渡すことができます。第3引数は中間モデルの外部キーの名前です。第4引数は最終モデルの外部キーの名前です。第5引数はローカルキーで、第6引数は中間モデルのローカルキーです。

1class Application extends Model
2{
3 public function deployments(): HasManyThrough
4 {
5 return $this->hasManyThrough(
6 Deployment::class,
7 Environment::class,
8 'application_id', // Foreign key on the environments table...
9 'environment_id', // Foreign key on the deployments table...
10 'id', // Local key on the applications table...
11 'id' // Local key on the environments table...
12 );
13 }
14}

または、前述の通り、リレーションに関与するすべてのモデルで関連するリレーションがすでに定義されている場合は、throughメソッドを呼び出してそれらのリレーションの名前を指定することで、「has-many-through」リレーションを流暢に定義できます。このアプローチは、既存のリレーションで既に定義されているキーの規約を再利用できるという利点があります。

1// String based syntax...
2return $this->through('environments')->has('deployments');
3 
4// Dynamic syntax...
5return $this->throughEnvironments()->hasDeployments();

スコープ付きリレーション

リレーションを制約する追加のメソッドをモデルに追加することはよくあります。たとえば、UserモデルにfeaturedPostsメソッドを追加して、より広いpostsリレーションに追加のwhere制約をかけることができます。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6use Illuminate\Database\Eloquent\Relations\HasMany;
7 
8class User extends Model
9{
10 /**
11 * Get the user's posts.
12 */
13 public function posts(): HasMany
14 {
15 return $this->hasMany(Post::class)->latest();
16 }
17 
18 /**
19 * Get the user's featured posts.
20 */
21 public function featuredPosts(): HasMany
22 {
23 return $this->posts()->where('featured', true);
24 }
25}

しかし、featuredPostsメソッドを介してモデルを作成しようとすると、そのfeatured属性はtrueに設定されません。リレーションメソッドを介してモデルを作成し、そのリレーションを介して作成されるすべてのモデルに追加すべき属性を指定したい場合は、リレーションクエリを構築する際にwithAttributesメソッドを使用できます。

1/**
2 * Get the user's featured posts.
3 */
4public function featuredPosts(): HasMany
5{
6 return $this->posts()->withAttributes(['featured' => true]);
7}

withAttributesメソッドは、指定された属性を使用してクエリにwhere句の制約を追加し、リレーションメソッドを介して作成されたすべてのモデルに指定された属性を追加します。

1$post = $user->featuredPosts()->create(['title' => 'Featured Post']);
2 
3$post->featured; // true

多対多リレーション

多対多リレーションは、hasOnehasManyリレーションよりも少し複雑です。多対多リレーションの例としては、多くのロールを持つユーザーがあり、それらのロールはアプリケーション内の他のユーザーとも共有されている場合が挙げられます。例えば、あるユーザーに「Author」や「Editor」というロールが割り当てられるかもしれませんが、それらのロールは他のユーザーにも割り当てられることがあります。つまり、一人のユーザーは多くのロールを持ち、一つのロールは多くのユーザーを持つということです。

テーブル構造

このリレーションを定義するには、usersrolesrole_userの3つのデータベーステーブルが必要です。role_userテーブルは、関連するモデル名のアルファベット順から派生し、user_idrole_idカラムを含みます。このテーブルは、ユーザーとロールをリンクする中間テーブルとして使用されます。

ロールは多くのユーザーに属することができるため、rolesテーブルに単純にuser_idカラムを配置することはできないことを覚えておいてください。これは、ロールが1人のユーザーにしか属せないことを意味します。ロールが複数のユーザーに割り当てられるようにサポートするには、role_userテーブルが必要です。リレーションのテーブル構造は次のように要約できます。

1users
2 id - integer
3 name - string
4
5roles
6 id - integer
7 name - string
8
9role_user
10 user_id - integer
11 role_id - integer

モデル構造

多対多リレーションは、belongsToManyメソッドの結果を返すメソッドを作成することで定義します。belongsToManyメソッドは、アプリケーションのすべてのEloquentモデルで使用されるIlluminate\Database\Eloquent\Modelベースクラスによって提供されます。例えば、Userモデルにrolesメソッドを定義してみましょう。このメソッドに渡す最初の引数は、関連するモデルクラスの名前です。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6use Illuminate\Database\Eloquent\Relations\BelongsToMany;
7 
8class User extends Model
9{
10 /**
11 * The roles that belong to the user.
12 */
13 public function roles(): BelongsToMany
14 {
15 return $this->belongsToMany(Role::class);
16 }
17}

リレーションを定義したら、roles動的リレーションプロパティを使用してユーザーのロールにアクセスできます。

1use App\Models\User;
2 
3$user = User::find(1);
4 
5foreach ($user->roles as $role) {
6 // ...
7}

すべてのリレーションはクエリビルダとしても機能するため、rolesメソッドを呼び出し、クエリに条件をチェーンし続けることで、リレーションクエリにさらに制約を追加できます。

1$roles = User::find(1)->roles()->orderBy('name')->get();

リレーションの中間テーブルのテーブル名を決定するために、Eloquentは2つの関連モデル名をアルファベット順に結合します。しかし、この規約は自由に上書きできます。それには、belongsToManyメソッドの2番目の引数を渡します。

1return $this->belongsToMany(Role::class, 'role_user');

中間テーブルの名前をカスタマイズすることに加えて、belongsToManyメソッドに追加の引数を渡すことで、テーブル上のキーのカラム名をカスタマイズすることもできます。第3引数はリレーションを定義しているモデルの外部キー名で、第4引数は結合先のモデルの外部キー名です。

1return $this->belongsToMany(Role::class, 'role_user', 'user_id', 'role_id');

リレーションの逆引きの定義

多対多リレーションの「逆」を定義するには、関連モデルにbelongsToManyメソッドの結果を返すメソッドを定義する必要があります。ユーザーとロールの例を完成させるために、Roleモデルにusersメソッドを定義しましょう。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6use Illuminate\Database\Eloquent\Relations\BelongsToMany;
7 
8class Role extends Model
9{
10 /**
11 * The users that belong to the role.
12 */
13 public function users(): BelongsToMany
14 {
15 return $this->belongsToMany(User::class);
16 }
17}

ご覧の通り、リレーションはApp\Models\Userモデルを参照している点を除けば、Userモデルの対応するリレーションと全く同じように定義されています。belongsToManyメソッドを再利用しているので、多対多リレーションの「逆」を定義する際にも、通常のテーブルとキーのカスタマイズオプションがすべて利用可能です。

中間テーブルカラムの取得

すでに学んだように、多対多リレーションを扱うには中間テーブルの存在が必要です。Eloquentはこのテーブルと対話するための非常に便利な方法を提供しています。たとえば、Userモデルが関連する多くのRoleモデルを持っているとします。このリレーションにアクセスした後、モデルのpivot属性を使用して中間テーブルにアクセスできます。

1use App\Models\User;
2 
3$user = User::find(1);
4 
5foreach ($user->roles as $role) {
6 echo $role->pivot->created_at;
7}

取得した各Roleモデルには、自動的にpivot属性が割り当てられていることに注目してください。この属性には、中間テーブルを表すモデルが含まれています。

デフォルトでは、モデルキーのみがpivotモデルに存在します。中間テーブルに追加の属性が含まれている場合は、リレーションを定義する際にそれらを指定する必要があります。

1return $this->belongsToMany(Role::class)->withPivot('active', 'created_by');

中間テーブルに、Eloquentによって自動的に維持されるcreated_atupdated_atタイムスタンプを持たせたい場合は、リレーションを定義する際にwithTimestampsメソッドを呼び出してください。

1return $this->belongsToMany(Role::class)->withTimestamps();

Eloquentの自動保守タイムスタンプを利用する中間テーブルには、`created_at`と`updated_at`の両方のタイムスタンプカラムが必要です。

pivot属性名のカスタマイズ

前述の通り、中間テーブルの属性はpivot属性を介してモデル上でアクセスできます。しかし、アプリケーション内でのその目的をより良く反映するために、この属性の名前を自由にカスタマイズできます。

たとえば、アプリケーションにポッドキャストに登録できるユーザーが含まれている場合、ユーザーとポッドキャストの間に多対多の関係がある可能性が高いです。この場合、中間テーブルの属性をpivotではなくsubscriptionに名前を変更したいかもしれません。これは、リレーションを定義する際にasメソッドを使用することで行えます。

1return $this->belongsToMany(Podcast::class)
2 ->as('subscription')
3 ->withTimestamps();

カスタム中間テーブル属性を指定したら、カスタマイズされた名前を使用して中間テーブルのデータにアクセスできます。

1$users = User::with('podcasts')->get();
2 
3foreach ($users->flatMap->podcasts as $podcast) {
4 echo $podcast->subscription->created_at;
5}

中間テーブルのカラムによるクエリのフィルタリング

リレーションを定義する際に、wherePivotwherePivotInwherePivotNotInwherePivotBetweenwherePivotNotBetweenwherePivotNullwherePivotNotNullメソッドを使用して、belongsToManyリレーションクエリによって返される結果をフィルタリングすることもできます。

1return $this->belongsToMany(Role::class)
2 ->wherePivot('approved', 1);
3 
4return $this->belongsToMany(Role::class)
5 ->wherePivotIn('priority', [1, 2]);
6 
7return $this->belongsToMany(Role::class)
8 ->wherePivotNotIn('priority', [1, 2]);
9 
10return $this->belongsToMany(Podcast::class)
11 ->as('subscriptions')
12 ->wherePivotBetween('created_at', ['2020-01-01 00:00:00', '2020-12-31 00:00:00']);
13 
14return $this->belongsToMany(Podcast::class)
15 ->as('subscriptions')
16 ->wherePivotNotBetween('created_at', ['2020-01-01 00:00:00', '2020-12-31 00:00:00']);
17 
18return $this->belongsToMany(Podcast::class)
19 ->as('subscriptions')
20 ->wherePivotNull('expired_at');
21 
22return $this->belongsToMany(Podcast::class)
23 ->as('subscriptions')
24 ->wherePivotNotNull('expired_at');

wherePivotはクエリにwhere句の制約を追加しますが、定義されたリレーションを介して新しいモデルを作成するときに指定された値は追加しません。特定のピボット値を持つリレーションをクエリして作成する必要がある場合は、withPivotValueメソッドを使用できます。

1return $this->belongsToMany(Role::class)
2 ->withPivotValue('approved', 1);

中間テーブルのカラムによるクエリのソート

orderByPivotメソッドを使用して、belongsToManyリレーションクエリによって返される結果を並べ替えることができます。以下の例では、ユーザーの最新のバッジをすべて取得します。

1return $this->belongsToMany(Badge::class)
2 ->where('rank', 'gold')
3 ->orderByPivot('created_at', 'desc');

カスタム中間テーブルモデルの定義

多対多リレーションの中間テーブルを表すカスタムモデルを定義したい場合は、リレーションを定義する際にusingメソッドを呼び出すことができます。カスタムピボットモデルは、メソッドやキャストなどの追加の振る舞いをピボットモデル上で定義する機会を与えてくれます。

カスタム多対多ピボットモデルはIlluminate\Database\Eloquent\Relations\Pivotクラスを拡張する必要があり、カスタムポリモーフィック多対多ピボットモデルはIlluminate\Database\Eloquent\Relations\MorphPivotクラスを拡張する必要があります。例えば、カスタムRoleUserピボットモデルを使用するRoleモデルを定義できます。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6use Illuminate\Database\Eloquent\Relations\BelongsToMany;
7 
8class Role extends Model
9{
10 /**
11 * The users that belong to the role.
12 */
13 public function users(): BelongsToMany
14 {
15 return $this->belongsToMany(User::class)->using(RoleUser::class);
16 }
17}

RoleUserモデルを定義する際には、Illuminate\Database\Eloquent\Relations\Pivotクラスを拡張する必要があります。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Relations\Pivot;
6 
7class RoleUser extends Pivot
8{
9 // ...
10}

ピボットモデルは`SoftDeletes`トレイトを使用できません。ピボットレコードをソフトデリートする必要がある場合は、ピボットモデルを実際のEloquentモデルに変換することを検討してください。

カスタムピボットモデルとインクリメントID

カスタムピボットモデルを使用する多対多リレーションを定義し、そのピボットモデルが自動インクリメントの主キーを持つ場合、カスタムピボットモデルクラスがincrementingプロパティをtrueに設定していることを確認する必要があります。

1/**
2 * Indicates if the IDs are auto-incrementing.
3 *
4 * @var bool
5 */
6public $incrementing = true;

ポリモーフィックリレーション

ポリモーフィックリレーションでは、子モデルが単一の関連付けを使用して複数のタイプのモデルに属することができます。たとえば、ユーザーがブログ投稿や動画を共有できるアプリケーションを構築しているとします。このようなアプリケーションでは、CommentモデルはPostモデルとVideoモデルの両方に属する可能性があります。

1対1(ポリモーフィック)

テーブル構造

1対1ポリモーフィックリレーションは、典型的な1対1リレーションに似ていますが、子モデルは単一の関連付けを使用して複数のタイプのモデルに属することができます。たとえば、ブログのPostUserは、Imageモデルへのポリモーフィックリレーションを共有できます。1対1ポリモーフィックリレーションを使用すると、投稿やユーザーに関連付けられるユニークな画像の単一テーブルを持つことができます。まず、テーブル構造を見てみましょう。

1posts
2 id - integer
3 name - string
4
5users
6 id - integer
7 name - string
8
9images
10 id - integer
11 url - string
12 imageable_id - integer
13 imageable_type - string

imagesテーブルのimageable_idimageable_typeカラムに注意してください。imageable_idカラムには投稿またはユーザーのID値が含まれ、imageable_typeカラムには親モデルのクラス名が含まれます。imageable_typeカラムはEloquentによって、imageableリレーションにアクセスする際にどの「タイプ」の親モデルを返すかを決定するために使用されます。この場合、カラムにはApp\Models\PostまたはApp\Models\Userのいずれかが含まれます。

モデル構造

次に、このリレーションを構築するために必要なモデル定義を確認しましょう。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6use Illuminate\Database\Eloquent\Relations\MorphTo;
7 
8class Image extends Model
9{
10 /**
11 * Get the parent imageable model (user or post).
12 */
13 public function imageable(): MorphTo
14 {
15 return $this->morphTo();
16 }
17}
18 
19use Illuminate\Database\Eloquent\Model;
20use Illuminate\Database\Eloquent\Relations\MorphOne;
21 
22class Post extends Model
23{
24 /**
25 * Get the post's image.
26 */
27 public function image(): MorphOne
28 {
29 return $this->morphOne(Image::class, 'imageable');
30 }
31}
32 
33use Illuminate\Database\Eloquent\Model;
34use Illuminate\Database\Eloquent\Relations\MorphOne;
35 
36class User extends Model
37{
38 /**
39 * Get the user's image.
40 */
41 public function image(): MorphOne
42 {
43 return $this->morphOne(Image::class, 'imageable');
44 }
45}

リレーションの取得

データベーステーブルとモデルを定義したら、モデルを介してリレーションにアクセスできます。たとえば、投稿の画像を取得するには、image動的リレーションプロパティにアクセスします。

1use App\Models\Post;
2 
3$post = Post::find(1);
4 
5$image = $post->image;

ポリモーフィックモデルの親を取得するには、morphToの呼び出しを行うメソッドの名前にアクセスします。この場合、Imageモデルのimageableメソッドです。したがって、そのメソッドに動的リレーションプロパティとしてアクセスします。

1use App\Models\Image;
2 
3$image = Image::find(1);
4 
5$imageable = $image->imageable;

Imageモデルのimageableリレーションは、画像を所有するモデルのタイプに応じて、PostまたはUserインスタンスのいずれかを返します。

キーの規約

必要であれば、ポリモーフィックな子モデルで利用する「id」と「type」のカラム名を指定することができます。その場合は、必ずmorphToメソッドの最初の引数としてリレーションの名前を渡してください。通常、この値はメソッド名と一致するはずなので、PHPの__FUNCTION__定数を使用することができます。

1/**
2 * Get the model that the image belongs to.
3 */
4public function imageable(): MorphTo
5{
6 return $this->morphTo(__FUNCTION__, 'imageable_type', 'imageable_id');
7}

1対多(ポリモーフィック)

テーブル構造

1対多ポリモーフィックリレーションは、通常の1対多リレーションに似ていますが、子モデルは単一の関連付けを使用して複数のモデルタイプに属することができます。たとえば、アプリケーションのユーザーが投稿や動画に「コメント」できると想像してください。ポリモーフィックリレーションを使用すると、単一のcommentsテーブルを使用して、投稿と動画の両方のコメントを格納できます。まず、このリレーションを構築するために必要なテーブル構造を見てみましょう。

1posts
2 id - integer
3 title - string
4 body - text
5
6videos
7 id - integer
8 title - string
9 url - string
10
11comments
12 id - integer
13 body - text
14 commentable_id - integer
15 commentable_type - string

モデル構造

次に、このリレーションを構築するために必要なモデル定義を確認しましょう。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6use Illuminate\Database\Eloquent\Relations\MorphTo;
7 
8class Comment extends Model
9{
10 /**
11 * Get the parent commentable model (post or video).
12 */
13 public function commentable(): MorphTo
14 {
15 return $this->morphTo();
16 }
17}
18 
19use Illuminate\Database\Eloquent\Model;
20use Illuminate\Database\Eloquent\Relations\MorphMany;
21 
22class Post extends Model
23{
24 /**
25 * Get all of the post's comments.
26 */
27 public function comments(): MorphMany
28 {
29 return $this->morphMany(Comment::class, 'commentable');
30 }
31}
32 
33use Illuminate\Database\Eloquent\Model;
34use Illuminate\Database\Eloquent\Relations\MorphMany;
35 
36class Video extends Model
37{
38 /**
39 * Get all of the video's comments.
40 */
41 public function comments(): MorphMany
42 {
43 return $this->morphMany(Comment::class, 'commentable');
44 }
45}

リレーションの取得

データベーステーブルとモデルを定義したら、モデルの動的リレーションプロパティを介してリレーションにアクセスできます。たとえば、投稿のすべてのコメントにアクセスするには、comments動的プロパティを使用できます。

1use App\Models\Post;
2 
3$post = Post::find(1);
4 
5foreach ($post->comments as $comment) {
6 // ...
7}

ポリモーフィックな子モデルの親を取得するには、morphToを呼び出すメソッドの名前にアクセスします。この場合、Commentモデルのcommentableメソッドです。したがって、コメントの親モデルにアクセスするために、そのメソッドを動的リレーションプロパティとしてアクセスします。

1use App\Models\Comment;
2 
3$comment = Comment::find(1);
4 
5$commentable = $comment->commentable;

Commentモデルのcommentableリレーションは、コメントの親であるモデルのタイプに応じて、PostまたはVideoインスタンスのいずれかを返します。

子モデルへの親モデルの自動ハイドレート

EloquentのEagerロードを利用している場合でも、子モデルをループ処理しながら子モデルから親モデルにアクセスしようとすると、「N+1」クエリ問題が発生する可能性があります。

1$posts = Post::with('comments')->get();
2 
3foreach ($posts as $post) {
4 foreach ($post->comments as $comment) {
5 echo $comment->commentable->title;
6 }
7}

上の例では、すべてのPostモデルに対してコメントがEagerロードされているにもかかわらず、Eloquentは各子Commentモデルに親のPostを自動的にハイドレートしないため、「N+1」クエリ問題が発生しています。

Eloquentに親モデルを子モデルに自動的にハイドレートさせたい場合は、`morphMany`リレーションを定義する際に`chaperone`メソッドを呼び出すことができます。

1class Post extends Model
2{
3 /**
4 * Get all of the post's comments.
5 */
6 public function comments(): MorphMany
7 {
8 return $this->morphMany(Comment::class, 'commentable')->chaperone();
9 }
10}

または、実行時に自動的な親のハイドレートを選択したい場合は、リレーションをEagerロードする際にchaperoneモデルを呼び出すことができます。

1use App\Models\Post;
2 
3$posts = Post::with([
4 'comments' => fn ($comments) => $comments->chaperone(),
5])->get();

One of Many(ポリモーフィック)

モデルには多くの関連モデルがある場合がありますが、リレーションの「最新」または「最古」の関連モデルを簡単に取得したいことがあります。たとえば、Userモデルは多くのImageモデルに関連しているかもしれませんが、ユーザーがアップロードした最新の画像を簡単に操作するための便利な方法を定義したい場合があります。これは、morphOneリレーションタイプとofManyメソッドを組み合わせて実現できます。

1/**
2 * Get the user's most recent image.
3 */
4public function latestImage(): MorphOne
5{
6 return $this->morphOne(Image::class, 'imageable')->latestOfMany();
7}

同様に、リレーションの「最古」、つまり最初の関連モデルを取得するメソッドを定義できます。

1/**
2 * Get the user's oldest image.
3 */
4public function oldestImage(): MorphOne
5{
6 return $this->morphOne(Image::class, 'imageable')->oldestOfMany();
7}

デフォルトでは、latestOfManyoldestOfManyメソッドは、ソート可能なモデルの主キーに基づいて最新または最古の関連モデルを取得します。しかし、時には異なるソート基準を使用して、より大きなリレーションから単一のモデルを取得したい場合があります。

たとえば、ofManyメソッドを使用すると、ユーザーの最も「いいね!」された画像を取得できます。ofManyメソッドは、最初の引数としてソート可能な列を受け取り、関連モデルをクエリするときに適用する集計関数(minまたはmax)を受け取ります。

1/**
2 * Get the user's most popular image.
3 */
4public function bestImage(): MorphOne
5{
6 return $this->morphOne(Image::class, 'imageable')->ofMany('likes', 'max');
7}

より高度な「one of many」リレーションを構築することが可能です。詳細については、has one of manyのドキュメントを参照してください。

多対多(ポリモーフィック)

テーブル構造

多対多ポリモーフィックリレーションは、「morph one」や「morph many」リレーションよりも少し複雑です。たとえば、PostモデルとVideoモデルは、Tagモデルへのポリモーフィックリレーションを共有できます。この状況で多対多ポリモーフィックリレーションを使用すると、アプリケーションは投稿や動画に関連付けられるユニークなタグの単一テーブルを持つことができます。まず、このリレーションを構築するために必要なテーブル構造を見てみましょう。

1posts
2 id - integer
3 name - string
4
5videos
6 id - integer
7 name - string
8
9tags
10 id - integer
11 name - string
12
13taggables
14 tag_id - integer
15 taggable_id - integer
16 taggable_type - string

ポリモーフィックな多対多リレーションシップに飛び込む前に、典型的な多対多リレーションシップに関するドキュメントを読むと役立つかもしれません。

モデル構造

次に、モデルにリレーションを定義する準備ができました。PostモデルとVideoモデルの両方に、Eloquentのベースモデルクラスが提供するmorphToManyメソッドを呼び出すtagsメソッドが含まれます。

morphToManyメソッドは、関連モデルの名前と「リレーション名」を受け入れます。中間テーブル名とそのキーに割り当てた名前に基づいて、リレーションを「taggable」と呼びます。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6use Illuminate\Database\Eloquent\Relations\MorphToMany;
7 
8class Post extends Model
9{
10 /**
11 * Get all of the tags for the post.
12 */
13 public function tags(): MorphToMany
14 {
15 return $this->morphToMany(Tag::class, 'taggable');
16 }
17}

リレーションの逆引きの定義

次に、Tagモデルで、その可能な親モデルごとにメソッドを定義する必要があります。そこで、この例では、postsメソッドとvideosメソッドを定義します。これらのメソッドは両方とも、morphedByManyメソッドの結果を返す必要があります。

morphedByManyメソッドは、関連モデルの名前と「リレーション名」を受け入れます。中間テーブル名とそれに含まれるキーに割り当てた名前に基づいて、リレーションを「taggable」と呼びます。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6use Illuminate\Database\Eloquent\Relations\MorphToMany;
7 
8class Tag extends Model
9{
10 /**
11 * Get all of the posts that are assigned this tag.
12 */
13 public function posts(): MorphToMany
14 {
15 return $this->morphedByMany(Post::class, 'taggable');
16 }
17 
18 /**
19 * Get all of the videos that are assigned this tag.
20 */
21 public function videos(): MorphToMany
22 {
23 return $this->morphedByMany(Video::class, 'taggable');
24 }
25}

リレーションの取得

データベーステーブルとモデルを定義したら、モデルを介してリレーションにアクセスできます。たとえば、投稿のすべてのタグにアクセスするには、tags動的リレーションプロパティを使用できます。

1use App\Models\Post;
2 
3$post = Post::find(1);
4 
5foreach ($post->tags as $tag) {
6 // ...
7}

ポリモーフィックな子モデルからポリモーフィックリレーションの親を取得するには、morphedByManyの呼び出しを行うメソッドの名前にアクセスします。この場合、Tagモデルのpostsまたはvideosメソッドです。

1use App\Models\Tag;
2 
3$tag = Tag::find(1);
4 
5foreach ($tag->posts as $post) {
6 // ...
7}
8 
9foreach ($tag->videos as $video) {
10 // ...
11}

カスタムポリモーフィックタイプ

デフォルトでは、Laravelは完全修飾クラス名を使用して関連モデルの「タイプ」を保存します。例えば、上記の1対多リレーションの例で、CommentモデルがPostまたはVideoモデルに属する場合、デフォルトのcommentable_typeはそれぞれApp\Models\PostまたはApp\Models\Videoになります。しかし、これらの値をアプリケーションの内部構造から切り離したい場合があります。

たとえば、モデル名を「タイプ」として使用する代わりに、postvideoのような単純な文字列を使用できます。そうすることで、データベースのポリモーフィックな「タイプ」カラムの値は、モデル名が変更されても有効なままになります。

1use Illuminate\Database\Eloquent\Relations\Relation;
2 
3Relation::enforceMorphMap([
4 'post' => 'App\Models\Post',
5 'video' => 'App\Models\Video',
6]);

enforceMorphMapメソッドは、App\Providers\AppServiceProviderクラスのbootメソッドで呼び出すか、必要に応じて別のサービスプロバイダを作成して呼び出すことができます。

実行時に特定のモデルのモーフエイリアスを決定するには、モデルのgetMorphClassメソッドを使用します。逆に、モーフエイリアスに関連付けられた完全修飾クラス名を決定するには、Relation::getMorphedModelメソッドを使用します。

1use Illuminate\Database\Eloquent\Relations\Relation;
2 
3$alias = $post->getMorphClass();
4 
5$class = Relation::getMorphedModel($alias);

既存のアプリケーションに「モーフマップ」を追加する場合、データベース内のすべてのモーフィング可能な`*_type`カラム値で、まだ完全修飾クラスを含んでいるものは、その「マップ」名に変換する必要があります。

動的リレーション

resolveRelationUsingメソッドを使用して、実行時にEloquentモデル間のリレーションを定義できます。通常のアプリケーション開発では通常推奨されませんが、Laravelパッケージを開発する際に時々役立つことがあります。

resolveRelationUsingメソッドは、最初の引数として目的のリレーション名を受け入れます。メソッドに渡される2番目の引数は、モデルインスタンスを受け入れ、有効なEloquentリレーション定義を返すクロージャであるべきです。通常、動的リレーションはサービスプロバイダのbootメソッド内で設定すべきです。

1use App\Models\Order;
2use App\Models\Customer;
3 
4Order::resolveRelationUsing('customer', function (Order $orderModel) {
5 return $orderModel->belongsTo(Customer::class, 'customer_id');
6});

動的リレーションを定義するときは、常にEloquentリレーションメソッドに明示的なキー名引数を指定してください。

リレーションのクエリ

すべてのEloquentリレーションはメソッドを介して定義されるため、それらのメソッドを呼び出すことで、関連モデルをロードするためのクエリを実際に実行することなく、リレーションのインスタンスを取得できます。さらに、すべてのタイプのEloquentリレーションはクエリビルダとしても機能し、データベースに対してSQLクエリを最終的に実行する前に、リレーションクエリに制約をチェーンし続けることができます。

たとえば、Userモデルが多数の関連Postモデルを持つブログアプリケーションを想像してみてください。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6use Illuminate\Database\Eloquent\Relations\HasMany;
7 
8class User extends Model
9{
10 /**
11 * Get all of the posts for the user.
12 */
13 public function posts(): HasMany
14 {
15 return $this->hasMany(Post::class);
16 }
17}

postsリレーションをクエリして、リレーションに次のように追加の制約を加えることができます。

1use App\Models\User;
2 
3$user = User::find(1);
4 
5$user->posts()->where('active', 1)->get();

リレーションシップではLaravel クエリビルダのメソッドをどれでも使用できるので、利用可能なすべてのメソッドについて学ぶために、クエリビルダのドキュメントを必ず調べてください。

リレーション後のorWhere句の連結

上記の例で示したように、リレーションシップをクエリする際に追加の制約を自由に追加できます。ただし、orWhere句をリレーションシップにチェーンする場合は注意が必要です。なぜなら、orWhere句はリレーションシップの制約と同じレベルで論理的にグループ化されるからです。

1$user->posts()
2 ->where('active', 1)
3 ->orWhere('votes', '>=', 100)
4 ->get();

上記の例は、次のSQLを生成します。ご覧の通り、or句は、100票を超える*すべて*の投稿を返すようにクエリに指示しています。クエリはもはや特定のユーザーに制約されていません。

1select *
2from posts
3where user_id = ? and active = 1 or votes >= 100

ほとんどの状況では、括弧で囲まれた条件付きチェックをグループ化するために論理グループを使用すべきです。

1use Illuminate\Database\Eloquent\Builder;
2 
3$user->posts()
4 ->where(function (Builder $query) {
5 return $query->where('active', 1)
6 ->orWhere('votes', '>=', 100);
7 })
8 ->get();

上記の例は、次のSQLを生成します。論理グループ化によって制約が適切にグループ化され、クエリが特定のユーザーに制約されたままであることに注意してください。

1select *
2from posts
3where user_id = ? and (active = 1 or votes >= 100)

リレーションメソッド vs. 動的プロパティ

Eloquentリレーションクエリに追加の制約を加える必要がない場合は、プロパティのようにリレーションにアクセスできます。たとえば、引き続きUserPostのサンプルモデルを使用して、ユーザーのすべての投稿に次のようにアクセスできます。

1use App\Models\User;
2 
3$user = User::find(1);
4 
5foreach ($user->posts as $post) {
6 // ...
7}

動的リレーションプロパティは「レイジーローディング」を実行します。つまり、実際にアクセスするまでリレーションデータをロードしません。このため、開発者はモデルをロードした後にアクセスすることがわかっているリレーションを事前にロードするために、イーガーローディングをよく使用します。イーガーローディングは、モデルのリレーションをロードするために実行する必要のあるSQLクエリを大幅に削減します。

リレーションの存在をクエリ

モデルレコードを取得する際、リレーションの存在に基づいて結果を制限したい場合があります。たとえば、少なくとも1つのコメントがあるすべてのブログ投稿を取得したいとします。そのためには、リレーションの名前をhasおよびorHasメソッドに渡します。

1use App\Models\Post;
2 
3// Retrieve all posts that have at least one comment...
4$posts = Post::has('comments')->get();

クエリをさらにカスタマイズするために、演算子とカウント値を指定することもできます。

1// Retrieve all posts that have three or more comments...
2$posts = Post::has('comments', '>=', 3)->get();

ネストされた`has`文は「ドット」記法を使用して構築できます。たとえば、少なくとも1つの画像を持つ少なくとも1つのコメントがあるすべての投稿を取得できます。

1// Retrieve posts that have at least one comment with images...
2$posts = Post::has('comments.images')->get();

さらに強力な機能が必要な場合は、whereHasおよびorWhereHasメソッドを使用して、コメントの内容を検査するなど、hasクエリに追加のクエリ制約を定義できます。

1use Illuminate\Database\Eloquent\Builder;
2 
3// Retrieve posts with at least one comment containing words like code%...
4$posts = Post::whereHas('comments', function (Builder $query) {
5 $query->where('content', 'like', 'code%');
6})->get();
7 
8// Retrieve posts with at least ten comments containing words like code%...
9$posts = Post::whereHas('comments', function (Builder $query) {
10 $query->where('content', 'like', 'code%');
11}, '>=', 10)->get();

Eloquentは現在、データベースをまたいだリレーションの存在クエリをサポートしていません。リレーションは同じデータベース内に存在する必要があります。

インラインリレーション存在クエリ

リレーションの存在をクエリする際に、リレーションクエリに単一の単純なwhere条件を付けたい場合は、`whereRelation`、`orWhereRelation`、`whereMorphRelation`、`orWhereMorphRelation`メソッドを使用する方が便利かもしれません。例えば、未承認のコメントがあるすべての投稿をクエリすることができます。

1use App\Models\Post;
2 
3$posts = Post::whereRelation('comments', 'is_approved', false)->get();

もちろん、クエリビルダのwhereメソッドの呼び出しと同様に、演算子を指定することもできます。

1$posts = Post::whereRelation(
2 'comments', 'created_at', '>=', now()->subHour()
3)->get();

リレーションの不在をクエリ

モデルレコードを取得する際に、リレーションシップの不在に基づいて結果を制限したい場合があります。たとえば、コメントがないすべてのブログ投稿を取得したいとします。そのためには、リレーションシップの名前をdoesntHaveメソッドとorDoesntHaveメソッドに渡します。

1use App\Models\Post;
2 
3$posts = Post::doesntHave('comments')->get();

さらに強力な機能が必要な場合は、whereDoesntHaveおよびorWhereDoesntHaveメソッドを使用して、コメントの内容を検査するなど、doesntHaveクエリに追加のクエリ制約を追加できます。

1use Illuminate\Database\Eloquent\Builder;
2 
3$posts = Post::whereDoesntHave('comments', function (Builder $query) {
4 $query->where('content', 'like', 'code%');
5})->get();

「ドット」記法を使用して、ネストされたリレーションに対してクエリを実行できます。たとえば、次のクエリはコメントがないすべての投稿を取得します。ただし、禁止されていない著者からのコメントがある投稿は結果に含まれます。

1use Illuminate\Database\Eloquent\Builder;
2 
3$posts = Post::whereDoesntHave('comments.author', function (Builder $query) {
4 $query->where('banned', 0);
5})->get();

Morph Toリレーションのクエリ

「morph to」リレーションの存在をクエリするには、`whereHasMorph`と`whereDoesntHaveMorph`メソッドを使用します。これらのメソッドは、最初の引数としてリレーションの名前を受け入れます。次に、メソッドはクエリに含めたい関連モデルの名前を受け入れます。最後に、リレーションクエリをカスタマイズするクロージャを提供できます。

1use App\Models\Comment;
2use App\Models\Post;
3use App\Models\Video;
4use Illuminate\Database\Eloquent\Builder;
5 
6// Retrieve comments associated to posts or videos with a title like code%...
7$comments = Comment::whereHasMorph(
8 'commentable',
9 [Post::class, Video::class],
10 function (Builder $query) {
11 $query->where('title', 'like', 'code%');
12 }
13)->get();
14 
15// Retrieve comments associated to posts with a title not like code%...
16$comments = Comment::whereDoesntHaveMorph(
17 'commentable',
18 Post::class,
19 function (Builder $query) {
20 $query->where('title', 'like', 'code%');
21 }
22)->get();

関連するポリモーフィックモデルの「タイプ」に基づいてクエリ制約を追加する必要がある場合があります。`whereHasMorph`メソッドに渡されるクロージャは、2番目の引数として`$type`値を受け取ることがあります。この引数により、構築中のクエリの「タイプ」を検査できます。

1use Illuminate\Database\Eloquent\Builder;
2 
3$comments = Comment::whereHasMorph(
4 'commentable',
5 [Post::class, Video::class],
6 function (Builder $query, string $type) {
7 $column = $type === Post::class ? 'content' : 'title';
8 
9 $query->where($column, 'like', 'code%');
10 }
11)->get();

「morph to」リレーションの親の子をクエリしたい場合があります。これはwhereMorphedTowhereNotMorphedToメソッドを使用して実現できます。これらのメソッドは、与えられたモデルに対して適切なモーフタイプのマッピングを自動的に決定します。これらのメソッドは、最初の引数としてmorphToリレーションの名前を、2番目の引数として関連する親モデルを受け入れます。

1$comments = Comment::whereMorphedTo('commentable', $post)
2 ->orWhereMorphedTo('commentable', $video)
3 ->get();

可能なポリモーフィックモデルの配列を渡す代わりに、ワイルドカード値として*を指定できます。これにより、Laravelはデータベースからすべての可能なポリモーフィックタイプを取得するように指示します。Laravelはこの操作を実行するために追加のクエリを実行します。

1use Illuminate\Database\Eloquent\Builder;
2 
3$comments = Comment::whereHasMorph('commentable', '*', function (Builder $query) {
4 $query->where('title', 'like', 'foo%');
5})->get();

実際にモデルをロードせずに、特定のリレーションの関連モデルの数をカウントしたい場合があります。これを実現するには、withCountメソッドを使用します。withCountメソッドは、結果のモデルに{relation}_count属性を配置します。

1use App\Models\Post;
2 
3$posts = Post::withCount('comments')->get();
4 
5foreach ($posts as $post) {
6 echo $post->comments_count;
7}

withCountメソッドに配列を渡すことで、複数のリレーションの「カウント」を追加したり、クエリに追加の制約を加えたりすることができます。

1use Illuminate\Database\Eloquent\Builder;
2 
3$posts = Post::withCount(['votes', 'comments' => function (Builder $query) {
4 $query->where('content', 'like', 'code%');
5}])->get();
6 
7echo $posts[0]->votes_count;
8echo $posts[0]->comments_count;

リレーションのカウント結果にエイリアスを付けることもでき、同じリレーションで複数のカウントが可能になります。

1use Illuminate\Database\Eloquent\Builder;
2 
3$posts = Post::withCount([
4 'comments',
5 'comments as pending_comments_count' => function (Builder $query) {
6 $query->where('approved', false);
7 },
8])->get();
9 
10echo $posts[0]->comments_count;
11echo $posts[0]->pending_comments_count;

遅延カウントローディング

loadCountメソッドを使用して、親モデルが既に取得された後でリレーションシップのカウントをロードできます。

1$book = Book::first();
2 
3$book->loadCount('genres');

カウントクエリに追加のクエリ制約を設定する必要がある場合は、カウントしたいリレーションをキーとする配列を渡すことができます。配列の値は、クエリビルダインスタンスを受け取るクロージャである必要があります。

1$book->loadCount(['reviews' => function (Builder $query) {
2 $query->where('rating', 5);
3}])

リレーションシップカウントとカスタムセレクトステートメント

withCountselectステートメントと組み合わせる場合は、selectメソッドの後にwithCountを呼び出すようにしてください。

1$posts = Post::select(['title', 'body'])
2 ->withCount('comments')
3 ->get();

その他の集計関数

withCountメソッドに加えて、EloquentはwithMinwithMaxwithAvgwithSum、およびwithExistsメソッドを提供します。これらのメソッドは、結果のモデルに{relation}_{function}_{column}属性を配置します。

1use App\Models\Post;
2 
3$posts = Post::withSum('comments', 'votes')->get();
4 
5foreach ($posts as $post) {
6 echo $post->comments_sum_votes;
7}

集計関数の結果を別の名前でアクセスしたい場合は、独自のエイリアスを指定できます。

1$posts = Post::withSum('comments as total_comments', 'votes')->get();
2 
3foreach ($posts as $post) {
4 echo $post->total_comments;
5}

loadCountメソッドと同様に、これらのメソッドの遅延バージョンも利用可能です。これらの追加の集計操作は、既に取得済みのEloquentモデルに対して実行できます。

1$post = Post::first();
2 
3$post->loadSum('comments', 'votes');

これらの集計メソッドを`select`ステートメントと組み合わせる場合は、`select`メソッドの後に集計メソッドを呼び出すようにしてください。

1$posts = Post::select(['title', 'body'])
2 ->withExists('comments')
3 ->get();

「morph to」リレーションをEagerロードし、そのリレーションによって返される可能性のあるさまざまなエンティティの関連モデルカウントもEagerロードしたい場合は、`with`メソッドと`morphTo`リレーションの`morphWithCount`メソッドを組み合わせて利用できます。

この例では、`Photo`モデルと`Post`モデルが`ActivityFeed`モデルを作成できると仮定します。`ActivityFeed`モデルは、特定の`ActivityFeed`インスタンスの親`Photo`または`Post`モデルを取得できる`parentable`という名前の「morph to」リレーションを定義していると仮定します。さらに、`Photo`モデルは`Tag`モデルを「多数持ち」、`Post`モデルは`Comment`モデルを「多数持つ」と仮定します。

さて、ActivityFeedインスタンスを取得し、各ActivityFeedインスタンスのparentable親モデルをEagerロードしたいとします。さらに、各親写真に関連付けられているタグの数と、各親投稿に関連付けられているコメントの数を取得したいとします。

1use Illuminate\Database\Eloquent\Relations\MorphTo;
2 
3$activities = ActivityFeed::with([
4 'parentable' => function (MorphTo $morphTo) {
5 $morphTo->morphWithCount([
6 Photo::class => ['tags'],
7 Post::class => ['comments'],
8 ]);
9 }])->get();

遅延カウントローディング

ActivityFeedモデルのセットをすでに取得しており、アクティビティフィードに関連付けられたさまざまなparentableモデルのネストされたリレーションシップカウントをロードしたいとします。これを実現するには、loadMorphCountメソッドを使用できます。

1$activities = ActivityFeed::with('parentable')->get();
2 
3$activities->loadMorphCount('parentable', [
4 Photo::class => ['tags'],
5 Post::class => ['comments'],
6]);

Eagerロード

Eloquentリレーションにプロパティとしてアクセスすると、関連モデルは「レイジーロード」されます。これは、最初にプロパティにアクセスするまでリレーションデータが実際にはロードされないことを意味します。しかし、Eloquentは親モデルをクエリする際にリレーションを「Eagerロード」できます。Eagerロードは「N + 1」クエリ問題を軽減します。N + 1クエリ問題を説明するために、Authorモデルに「属する」Bookモデルを考えてみましょう。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6use Illuminate\Database\Eloquent\Relations\BelongsTo;
7 
8class Book extends Model
9{
10 /**
11 * Get the author that wrote the book.
12 */
13 public function author(): BelongsTo
14 {
15 return $this->belongsTo(Author::class);
16 }
17}

さて、すべての本とその著者を取得しましょう。

1use App\Models\Book;
2 
3$books = Book::all();
4 
5foreach ($books as $book) {
6 echo $book->author->name;
7}

このループは、データベーステーブル内のすべての本を取得するために1つのクエリを実行し、その後、本の著者を取得するために各本に対して別のクエリを実行します。したがって、25冊の本がある場合、上記のコードは26のクエリを実行します。元の本に対して1つ、各本の著者を取得するために25の追加クエリです。

ありがたいことに、Eagerロードを使用してこの操作をわずか2つのクエリに減らすことができます。クエリを構築する際に、withメソッドを使用してどのリレーションをEagerロードするかを指定できます。

1$books = Book::with('author')->get();
2 
3foreach ($books as $book) {
4 echo $book->author->name;
5}

この操作では、2つのクエリのみが実行されます。1つはすべての書籍を取得するためのクエリ、もう1つはすべての書籍のすべての著者を取得するためのクエリです。

1select * from books
2 
3select * from authors where id in (1, 2, 3, 4, 5, ...)

複数のリレーションのEagerロード

複数の異なるリレーションをEagerロードする必要がある場合があります。そのためには、リレーションの配列をwithメソッドに渡すだけです。

1$books = Book::with(['author', 'publisher'])->get();

ネストしたEagerロード

リレーションのリレーションをEagerロードするには、「ドット」構文を使用できます。たとえば、本のすべての著者と、著者のすべての個人的な連絡先をEagerロードしてみましょう。

1$books = Book::with('author.contacts')->get();

あるいは、`with`メソッドにネストされた配列を提供することで、ネストされたEagerロードされたリレーションを指定することもできます。これは、複数のネストされたリレーションをEagerロードする際に便利です。

1$books = Book::with([
2 'author' => [
3 'contacts',
4 'publisher',
5 ],
6])->get();

ネストしたEagerロード `morphTo` リレーション

morphToリレーション、およびそのリレーションによって返される可能性のある様々なエンティティ上のネストされたリレーションをEagerロードしたい場合は、withメソッドとmorphToリレーションのmorphWithメソッドを組み合わせて使用できます。このメソッドを説明するために、次のモデルを考えてみましょう。

1<?php
2 
3use Illuminate\Database\Eloquent\Model;
4use Illuminate\Database\Eloquent\Relations\MorphTo;
5 
6class ActivityFeed extends Model
7{
8 /**
9 * Get the parent of the activity feed record.
10 */
11 public function parentable(): MorphTo
12 {
13 return $this->morphTo();
14 }
15}

この例では、`Event`、`Photo`、`Post`モデルが`ActivityFeed`モデルを作成できると仮定します。さらに、`Event`モデルは`Calendar`モデルに属し、`Photo`モデルは`Tag`モデルに関連付けられ、`Post`モデルは`Author`モデルに属すると仮定します。

これらのモデル定義とリレーションを使用して、ActivityFeedモデルインスタンスを取得し、すべてのparentableモデルとそれぞれのネストされたリレーションをEagerロードできます。

1use Illuminate\Database\Eloquent\Relations\MorphTo;
2 
3$activities = ActivityFeed::query()
4 ->with(['parentable' => function (MorphTo $morphTo) {
5 $morphTo->morphWith([
6 Event::class => ['calendar'],
7 Photo::class => ['tags'],
8 Post::class => ['author'],
9 ]);
10 }])->get();

特定カラムのEagerロード

取得しているリレーションのすべてのカラムが常に必要とは限りません。このため、Eloquentでは、リレーションのどのカラムを取得したいかを指定できます。

1$books = Book::with('author:id,name,book_id')->get();

この機能を使用する場合、取得したいカラムのリストには常に `id` カラムと関連する外部キーカラムを含める必要があります。

デフォルトでのEagerロード

モデルを取得する際に、常にいくつかのリレーションをロードしたい場合があります。これを実現するには、モデルに$withプロパティを定義します。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6use Illuminate\Database\Eloquent\Relations\BelongsTo;
7 
8class Book extends Model
9{
10 /**
11 * The relationships that should always be loaded.
12 *
13 * @var array
14 */
15 protected $with = ['author'];
16 
17 /**
18 * Get the author that wrote the book.
19 */
20 public function author(): BelongsTo
21 {
22 return $this->belongsTo(Author::class);
23 }
24 
25 /**
26 * Get the genre of the book.
27 */
28 public function genre(): BelongsTo
29 {
30 return $this->belongsTo(Genre::class);
31 }
32}

単一のクエリで`$with`プロパティから項目を削除したい場合は、`without`メソッドを使用できます。

1$books = Book::without('author')->get();

単一のクエリで`$with`プロパティ内のすべての項目を上書きしたい場合は、`withOnly`メソッドを使用できます。

1$books = Book::withOnly('genre')->get();

Eagerロードの制約

リレーションをEagerロードしつつ、Eagerロードクエリに追加のクエリ条件を指定したい場合があります。これを実現するには、`with`メソッドにリレーションの配列を渡します。配列のキーはリレーション名、配列の値はEagerロードクエリに追加の制約を加えるクロージャです。

1use App\Models\User;
2use Illuminate\Contracts\Database\Eloquent\Builder;
3 
4$users = User::with(['posts' => function (Builder $query) {
5 $query->where('title', 'like', '%code%');
6}])->get();

この例では、Eloquentは投稿の`title`カラムに`code`という単語が含まれる投稿のみをEagerロードします。他のクエリビルダメソッドを呼び出して、Eagerロード操作をさらにカスタマイズできます。

1$users = User::with(['posts' => function (Builder $query) {
2 $query->orderBy('created_at', 'desc');
3}])->get();

morphToリレーションのEagerロードの制約

morphToリレーションをEagerロードする場合、Eloquentは関連モデルの各タイプを取得するために複数のクエリを実行します。MorphToリレーションのconstrainメソッドを使用して、これらの各クエリに追加の制約を加えることができます。

1use Illuminate\Database\Eloquent\Relations\MorphTo;
2 
3$comments = Comment::with(['commentable' => function (MorphTo $morphTo) {
4 $morphTo->constrain([
5 Post::class => function ($query) {
6 $query->whereNull('hidden_at');
7 },
8 Video::class => function ($query) {
9 $query->where('type', 'educational');
10 },
11 ]);
12}])->get();

この例では、Eloquentは非表示にされていない投稿と、`type`の値が「educational」である動画のみをEagerロードします。

リレーションの存在によるEagerロードの制約

リレーションの存在を確認しながら、同じ条件に基づいてリレーションを同時にロードする必要がある場合があります。たとえば、特定のクエリ条件に一致する子Postモデルを持つUserモデルのみを取得し、一致する投稿もEagerロードしたい場合があります。これはwithWhereHasメソッドを使用して実現できます。

1use App\Models\User;
2 
3$users = User::withWhereHas('posts', function ($query) {
4 $query->where('featured', true);
5})->get();

Lazy Eagerロード

親モデルがすでに取得された後で、リレーションをEagerロードする必要がある場合があります。たとえば、関連モデルをロードするかどうかを動的に決定する必要がある場合に便利です。

1use App\Models\Book;
2 
3$books = Book::all();
4 
5if ($someCondition) {
6 $books->load('author', 'publisher');
7}

Eagerロードクエリに追加のクエリ制約を設定する必要がある場合は、ロードしたいリレーションをキーとする配列を渡すことができます。配列の値は、クエリインスタンスを受け取るクロージャインスタンスである必要があります。

1$author->load(['books' => function (Builder $query) {
2 $query->orderBy('published_date', 'asc');
3}]);

まだロードされていないリレーションのみをロードするには、`loadMissing`メソッドを使用します。

1$book->loadMissing('author');

ネストされたLazy Eagerロードと`morphTo`

`morphTo`リレーション、およびそのリレーションによって返される可能性のある様々なエンティティ上のネストされたリレーションをEagerロードしたい場合は、`loadMorph`メソッドを使用できます。

このメソッドは、最初の引数として`morphTo`リレーションの名前を、2番目の引数としてモデルとリレーションのペアの配列を受け入れます。このメソッドを説明するために、次のモデルを考えてみましょう。

1<?php
2 
3use Illuminate\Database\Eloquent\Model;
4use Illuminate\Database\Eloquent\Relations\MorphTo;
5 
6class ActivityFeed extends Model
7{
8 /**
9 * Get the parent of the activity feed record.
10 */
11 public function parentable(): MorphTo
12 {
13 return $this->morphTo();
14 }
15}

この例では、`Event`、`Photo`、`Post`モデルが`ActivityFeed`モデルを作成できると仮定します。さらに、`Event`モデルは`Calendar`モデルに属し、`Photo`モデルは`Tag`モデルに関連付けられ、`Post`モデルは`Author`モデルに属すると仮定します。

これらのモデル定義とリレーションを使用して、ActivityFeedモデルインスタンスを取得し、すべてのparentableモデルとそれぞれのネストされたリレーションをEagerロードできます。

1$activities = ActivityFeed::with('parentable')
2 ->get()
3 ->loadMorph('parentable', [
4 Event::class => ['calendar'],
5 Photo::class => ['tags'],
6 Post::class => ['author'],
7 ]);

Lazyロードの防止

前述の通り、リレーションのEagerロードはアプリケーションのパフォーマンスを大幅に向上させることがよくあります。したがって、必要であれば、LaravelにリレーションのLazyロードを常に防止するように指示できます。これを実現するには、Eloquentのベースモデルクラスが提供する`preventLazyLoading`メソッドを呼び出します。通常、このメソッドはアプリケーションの`AppServiceProvider`クラスの`boot`メソッド内で呼び出すべきです。

preventLazyLoadingメソッドは、Lazyロードを防ぐべきかどうかを示すオプションのブール引数を受け入れます。たとえば、本番環境以外の環境でのみLazyロードを無効にして、本番環境で誤ってLazyロードされたリレーションが存在しても本番環境が正常に機能し続けるようにしたい場合があります。

1use Illuminate\Database\Eloquent\Model;
2 
3/**
4 * Bootstrap any application services.
5 */
6public function boot(): void
7{
8 Model::preventLazyLoading(! $this->app->isProduction());
9}

Lazyロードを防止した後、アプリケーションがEloquentリレーションをLazyロードしようとすると、Eloquentは`Illuminate\Database\LazyLoadingViolationException`例外をスローします。

Lazyロード違反の動作は、handleLazyLoadingViolationsUsingメソッドを使用してカスタマイズできます。たとえば、このメソッドを使用して、Lazyロード違反が例外でアプリケーションの実行を中断する代わりに、ログに記録されるだけにすることができます。

1Model::handleLazyLoadingViolationUsing(function (Model $model, string $relation) {
2 $class = $model::class;
3 
4 info("Attempted to lazy load [{$relation}] on model [{$class}].");
5});

saveメソッド

Eloquentは、リレーションに新しいモデルを追加するための便利なメソッドを提供します。たとえば、投稿に新しいコメントを追加する必要があるとします。Commentモデルに手動でpost_id属性を設定する代わりに、リレーションのsaveメソッドを使用してコメントを挿入できます。

1use App\Models\Comment;
2use App\Models\Post;
3 
4$comment = new Comment(['message' => 'A new comment.']);
5 
6$post = Post::find(1);
7 
8$post->comments()->save($comment);

commentsリレーションに動的プロパティとしてアクセスしていないことに注意してください。代わりに、commentsメソッドを呼び出してリレーションのインスタンスを取得しました。saveメソッドは、新しいCommentモデルに適切なpost_id値を自動的に追加します。

複数の関連モデルを保存する必要がある場合は、`saveMany`メソッドを使用できます。

1$post = Post::find(1);
2 
3$post->comments()->saveMany([
4 new Comment(['message' => 'A new comment.']),
5 new Comment(['message' => 'Another new comment.']),
6]);

savesaveManyメソッドは、指定されたモデルインスタンスを永続化しますが、新しく永続化されたモデルを親モデルに既にロードされているメモリ内のリレーションには追加しません。saveまたはsaveManyメソッドを使用した後にリレーションにアクセスする予定がある場合は、refreshメソッドを使用してモデルとそのリレーションをリロードすることをお勧めします。

1$post->comments()->save($comment);
2 
3$post->refresh();
4 
5// All comments, including the newly saved comment...
6$post->comments;

モデルとリレーションの再帰的保存

モデルとその関連リレーションをすべて`save`したい場合は、`push`メソッドを使用できます。この例では、`Post`モデルと、そのコメントおよびコメントの作成者が保存されます。

1$post = Post::find(1);
2 
3$post->comments[0]->message = 'Message';
4$post->comments[0]->author->name = 'Author Name';
5 
6$post->push();

`pushQuietly`メソッドは、イベントを発生させずにモデルとその関連リレーションを保存するために使用できます。

1$post->pushQuietly();

createメソッド

saveメソッドとsaveManyメソッドに加えて、createメソッドも使用できます。これは属性の配列を受け入れ、モデルを作成し、データベースに挿入します。savecreateの違いは、saveは完全なEloquentモデルインスタンスを受け入れるのに対し、createはプレーンなPHPのarrayを受け入れる点です。新しく作成されたモデルはcreateメソッドによって返されます。

1use App\Models\Post;
2 
3$post = Post::find(1);
4 
5$comment = $post->comments()->create([
6 'message' => 'A new comment.',
7]);

createManyメソッドを使用して、複数の関連モデルを作成できます。

1$post = Post::find(1);
2 
3$post->comments()->createMany([
4 ['message' => 'A new comment.'],
5 ['message' => 'Another new comment.'],
6]);

createQuietlycreateManyQuietlyメソッドは、イベントをディスパッチせずにモデルを作成するために使用できます。

1$user = User::find(1);
2 
3$user->posts()->createQuietly([
4 'title' => 'Post title.',
5]);
6 
7$user->posts()->createManyQuietly([
8 ['title' => 'First post.'],
9 ['title' => 'Second post.'],
10]);

リレーション上のモデルを作成および更新するために、findOrNewfirstOrNewfirstOrCreate、およびupdateOrCreateメソッドを使用することもできます。

createメソッドを使用する前に、マスアサインメントのドキュメントを必ず確認してください。

Belongs Toリレーション

子モデルを新しい親モデルに割り当てたい場合は、associateメソッドを使用できます。この例では、UserモデルはAccountモデルへのbelongsToリレーションを定義しています。このassociateメソッドは、子モデルの外部キーを設定します。

1use App\Models\Account;
2 
3$account = Account::find(10);
4 
5$user->account()->associate($account);
6 
7$user->save();

子モデルから親モデルを削除するには、`dissociate`メソッドを使用できます。このメソッドは、リレーションシップの外部キーを`null`に設定します。

1$user->account()->dissociate();
2 
3$user->save();

多対多リレーション

アタッチ/デタッチ

Eloquentは、多対多リレーションシップの操作をより便利にするためのメソッドも提供しています。例えば、あるユーザーが多くのロールを持ち、あるロールが多くのユーザーを持つことができるとします。attachメソッドを使用して、リレーションシップの中間テーブルにレコードを挿入することで、ユーザーにロールをアタッチすることができます。

1use App\Models\User;
2 
3$user = User::find(1);
4 
5$user->roles()->attach($roleId);

モデルにリレーションをアタッチする際、中間テーブルに挿入する追加データの配列を渡すこともできます。

1$user->roles()->attach($roleId, ['expires' => $expires]);

ユーザーからロールを削除する必要がある場合があります。多対多リレーションのレコードを削除するには、detachメソッドを使用します。detachメソッドは中間テーブルから適切なレコードを削除しますが、両方のモデルはデータベースに残ります。

1// Detach a single role from the user...
2$user->roles()->detach($roleId);
3 
4// Detach all roles from the user...
5$user->roles()->detach();

便利なことに、`attach`と`detach`はIDの配列も入力として受け付けます。

1$user = User::find(1);
2 
3$user->roles()->detach([1, 2, 3]);
4 
5$user->roles()->attach([
6 1 => ['expires' => $expires],
7 2 => ['expires' => $expires],
8]);

関連の同期

syncメソッドを使用して、多対多の関連付けを構築することもできます。syncメソッドは、中間テーブルに配置するIDの配列を受け入れます。指定された配列にないIDは、中間テーブルから削除されます。したがって、この操作が完了した後、指定された配列のIDのみが中間テーブルに存在します。

1$user->roles()->sync([1, 2, 3]);

IDと共に追加の中間テーブルの値を渡すこともできます。

1$user->roles()->sync([1 => ['expires' => true], 2, 3]);

同期された各モデルIDに同じ中間テーブルの値を挿入したい場合は、`syncWithPivotValues`メソッドを使用できます。

1$user->roles()->syncWithPivotValues([1, 2, 3], ['active' => true]);

指定された配列にない既存のIDをデタッチしたくない場合は、`syncWithoutDetaching`メソッドを使用できます。

1$user->roles()->syncWithoutDetaching([1, 2, 3]);

関連の切り替え

多対多リレーションは、指定された関連モデルIDのアタッチ状態を「トグル」するtoggleメソッドも提供します。指定されたIDが現在アタッチされている場合はデタッチされます。同様に、現在デタッチされている場合はアタッチされます。

1$user->roles()->toggle([1, 2, 3]);

IDと共に追加の中間テーブルの値を渡すこともできます。

1$user->roles()->toggle([
2 1 => ['expires' => true],
3 2 => ['expires' => true],
4]);

中間テーブルのレコードの更新

リレーションの中間テーブルにある既存の行を更新する必要がある場合は、`updateExistingPivot`メソッドを使用できます。このメソッドは、中間レコードの外部キーと更新する属性の配列を受け入れます。

1$user = User::find(1);
2 
3$user->roles()->updateExistingPivot($roleId, [
4 'active' => false,
5]);

親のタイムスタンプ更新

モデルが他のモデルへのbelongsToまたはbelongsToManyリレーションを定義している場合、たとえばPostに属するCommentのように、子モデルが更新されたときに親のタイムスタンプを更新すると便利なことがあります。

たとえば、Commentモデルが更新されたとき、所有するPostupdated_atタイムスタンプを自動的に「タッチ」して、現在の日時に設定したい場合があります。これを実現するには、子モデルにtouchesプロパティを追加し、子モデルが更新されたときにupdated_atタイムスタンプを更新すべきリレーションの名前を含めます。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6use Illuminate\Database\Eloquent\Relations\BelongsTo;
7 
8class Comment extends Model
9{
10 /**
11 * All of the relationships to be touched.
12 *
13 * @var array
14 */
15 protected $touches = ['post'];
16 
17 /**
18 * Get the post that the comment belongs to.
19 */
20 public function post(): BelongsTo
21 {
22 return $this->belongsTo(Post::class);
23 }
24}

親モデルのタイムスタンプは、子モデルがEloquentの`save`メソッドを使用して更新された場合にのみ更新されます。

Laravelは最も生産的な方法です
ソフトウェアを構築、デプロイ、監視します。