Eloquent: リレーションシップ
- はじめに
- リレーションシップの定義
- 多対多リレーションシップ
- ポリモーフィックリレーションシップ
- 動的リレーションシップ
- リレーションのクエリ
- 関連モデルの集計
- Eager Loading
- 関連モデルの挿入と更新
- 親タイムスタンプのタッチ
はじめに
データベーステーブルはしばしば互いに関連しています。例えば、ブログ記事には多くのコメントがあり、注文はそれを注文したユーザーに関連している可能性があります。Eloquentはこれらのリレーションシップの管理と操作を容易にし、さまざまな一般的なリレーションシップをサポートしています。
リレーションシップの定義
Eloquentリレーションシップは、Eloquentモデルクラスのメソッドとして定義されます。リレーションシップは強力なクエリビルダーとしても機能するため、メソッドとしてリレーションシップを定義することで、強力なメソッドチェーンとクエリ機能が提供されます。例えば、このposts
リレーションシップにさらにクエリ制約を連鎖させることができます。
$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
ベースクラスを介してモデルで使用できます。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\HasOne; class User extends Model{ /** * Get the phone associated with the user. */ public function phone(): HasOne { return $this->hasOne(Phone::class); }}
hasOne
メソッドに渡される最初の引数は、関連モデルクラスの名前です。リレーションシップが定義されると、Eloquentの動的プロパティを使用して関連レコードを取得できます。動的プロパティを使用すると、リレーションシップメソッドを、モデルに定義されているプロパティのようにアクセスできます。
$phone = User::find(1)->phone;
Eloquentは、親モデル名に基づいてリレーションシップの外来キーを決定します。この場合、Phone
モデルにはuser_id
外来キーがあると自動的に想定されます。この規則をオーバーライドする場合は、hasOne
メソッドに2番目の引数を渡すことができます。
return $this->hasOne(Phone::class, 'foreign_key');
さらに、Eloquentは、外来キーの値が親の主キーカラムの値と一致する必要があると想定します。つまり、Eloquentは、Phone
レコードのuser_id
カラムでユーザーのid
カラムの値を検索します。id
以外の主キー値を使用したい場合、またはモデルの$primaryKey
プロパティを使用したい場合は、hasOne
メソッドに3番目の引数を渡すことができます。
return $this->hasOne(Phone::class, 'foreign_key', 'local_key');
リレーションシップの逆の定義
したがって、User
モデルからPhone
モデルにアクセスできます。次に、電話を所有するユーザーにアクセスできるように、Phone
モデルにリレーションシップを定義しましょう。hasOne
リレーションシップの逆は、belongsTo
メソッドを使用して定義できます。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\BelongsTo; class Phone extends Model{ /** * Get the user that owns the phone. */ public function user(): BelongsTo { return $this->belongsTo(User::class); }}
user
メソッドを呼び出すと、Eloquentはid
がPhone
モデルのuser_id
カラムと一致するUser
モデルを検索しようとします。
Eloquentは、リレーションシップメソッドの名前を調べ、メソッド名に_id
を付加することで、外来キー名を決定します。したがって、この場合、EloquentはPhone
モデルにuser_id
カラムがあると想定します。ただし、Phone
モデルの外来キーがuser_id
ではない場合は、belongsTo
メソッドに2番目の引数としてカスタムキー名を渡すことができます。
/** * Get the user that owns the phone. */public function user(): BelongsTo{ return $this->belongsTo(User::class, 'foreign_key');}
親モデルがid
を主キーとして使用していない場合、または別のカラムを使用して関連モデルを検索したい場合は、親テーブルのカスタムキーを指定する3番目の引数をbelongsTo
メソッドに渡すことができます。
/** * Get the user that owns the phone. */public function user(): BelongsTo{ return $this->belongsTo(User::class, 'foreign_key', 'owner_key');}
1対多 / Has Many
1対多リレーションシップは、単一のモデルが1つ以上の子モデルの親であるリレーションシップを定義するために使用されます。例えば、ブログ記事には無数のコメントがある可能性があります。他のすべてのEloquentリレーションシップと同様に、1対多リレーションシップは、Eloquentモデルにメソッドを定義することで定義されます。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\HasMany; class Post extends Model{ /** * Get the comments for the blog post. */ public function comments(): HasMany { return $this->hasMany(Comment::class); }}
Eloquentは、Comment
モデルに適切な外来キーカラムを自動的に決定することを覚えておいてください。慣例により、Eloquentは親モデルの「スネークケース」名を取り、_id
を接尾辞として付け加えます。したがって、この例では、EloquentはComment
モデルの外来キーカラムがpost_id
であると想定します。
リレーションシップメソッドが定義されると、コレクションの関連コメントに、comments
プロパティにアクセスすることでアクセスできます。Eloquentは「動的リレーションシッププロパティ」を提供するため、リレーションシップメソッドを、モデルに定義されているプロパティのようにアクセスできます。
use App\Models\Post; $comments = Post::find(1)->comments; foreach ($comments as $comment) { // ...}
すべてのリレーションシップはクエリビルダーとしても機能するため、comments
メソッドを呼び出し、クエリに条件をさらに連鎖させることで、リレーションシップクエリに制約を追加できます。
$comment = Post::find(1)->comments() ->where('title', 'foo') ->first();
hasOne
メソッドと同様に、hasMany
メソッドに追加の引数を渡すことによって、外来キーとローカルキーをオーバーライドすることもできます。
return $this->hasMany(Comment::class, 'foreign_key'); return $this->hasMany(Comment::class, 'foreign_key', 'local_key');
子モデルでの親モデルの自動ハイドレーション
Eloquent の eager loading を使用していても、子モデルをループ処理中に親モデルにアクセスしようとすると、「N + 1」クエリの問題が発生する可能性があります。
$posts = Post::with('comments')->get(); foreach ($posts as $post) { foreach ($post->comments as $comment) { echo $comment->post->title; }}
上記の例では、「N + 1」クエリの問題が発生しています。これは、すべての `Post` モデルに対してコメントが eager loading されていたにもかかわらず、Eloquent が各子 `Comment` モデルに親 `Post` を自動的に hydration しないためです。
`hasMany` リレーションシップを定義する際に `chaperone` メソッドを呼び出すと、Eloquent が子モデルに親モデルを自動的に hydration するようになります。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\HasMany; class Post extends Model{ /** * Get the comments for the blog post. */ public function comments(): HasMany { return $this->hasMany(Comment::class)->chaperone(); }}
あるいは、実行時に自動的な親モデルの hydration を有効にするには、リレーションシップを eager loading する際に `chaperone` メソッドを呼び出すことができます。
use App\Models\Post; $posts = Post::with([ 'comments' => fn ($comments) => $comments->chaperone(),])->get();
1対多(逆)/ Belongs To
投稿のすべてのコメントにアクセスできるようになったので、コメントが親の投稿にアクセスできるようにリレーションシップを定義しましょう。 `hasMany` リレーションシップの逆を定義するには、子モデルに `belongsTo` メソッドを呼び出すリレーションシップメソッドを定義します。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\BelongsTo; class Comment extends Model{ /** * Get the post that owns the comment. */ public function post(): BelongsTo { return $this->belongsTo(Post::class); }}
リレーションシップを定義したら、`post` という「動的なリレーションシッププロパティ」にアクセスすることで、コメントの親投稿を取得できます。
use App\Models\Comment; $comment = Comment::find(1); return $comment->post->title;
上記の例では、Eloquent は `Comment` モデルの `post_id` カラムと一致する `id` を持つ `Post` モデルを検索しようとします。
Eloquent は、リレーションシップメソッドの名前を調べ、メソッド名にアンダースコア `_` と親モデルの主キーカラム名を付加することで、デフォルトの外部キー名を決定します。そのため、この例では、Eloquent は `comments` テーブル上の `Post` モデルの外部キーが `post_id` であると想定します。
ただし、リレーションシップの外部キーがこれらの規則に従っていない場合は、 `belongsTo` メソッドの第2引数としてカスタムの外部キー名を渡すことができます。
/** * Get the post that owns the comment. */public function post(): BelongsTo{ return $this->belongsTo(Post::class, 'foreign_key');}
親モデルが主キーとして `id` を使用していない場合、または別のカラムを使用して関連モデルを検索する場合は、 `belongsTo` メソッドに第3引数を渡して、親テーブルのカスタムキーを指定できます。
/** * Get the post that owns the comment. */public function post(): BelongsTo{ return $this->belongsTo(Post::class, 'foreign_key', 'owner_key');}
デフォルトモデル
`belongsTo` 、 `hasOne` 、 `hasOneThrough` 、および `morphOne` リレーションシップでは、指定されたリレーションシップが `null` の場合に返されるデフォルトモデルを定義できます。このパターンは、多くの場合、 Null Object パターン と呼ばれ、コード内の条件チェックを減らすのに役立ちます。次の例では、ユーザーが `Post` モデルにアタッチされていない場合、 `user` リレーションは空の `App\Models\User` モデルを返します。
/** * Get the author of the post. */public function user(): BelongsTo{ return $this->belongsTo(User::class)->withDefault();}
デフォルトモデルに属性を設定するには、配列またはクロージャを `withDefault` メソッドに渡します。
/** * Get the author of the post. */public function user(): BelongsTo{ return $this->belongsTo(User::class)->withDefault([ 'name' => 'Guest Author', ]);} /** * Get the author of the post. */public function user(): BelongsTo{ return $this->belongsTo(User::class)->withDefault(function (User $user, Post $post) { $user->name = 'Guest Author'; });}
Belongs To リレーションシップのクエリ
「belongs to」リレーションシップの子をクエリする際には、対応する Eloquent モデルを取得するために `where` 句を手動で構築できます。
use App\Models\Post; $posts = Post::where('user_id', $user->id)->get();
ただし、 `whereBelongsTo` メソッドを使用する方が便利です。このメソッドは、指定されたモデルに対する適切なリレーションシップと外部キーを自動的に決定します。
$posts = Post::whereBelongsTo($user)->get();
`whereBelongsTo` メソッドには、 コレクション インスタンスも渡すことができます。その場合、Laravel はコレクション内のいずれかの親モデルに属するモデルを取得します。
$users = User::where('vip', true)->get(); $posts = Post::whereBelongsTo($users)->get();
デフォルトでは、Laravel はモデルのクラス名に基づいて、指定されたモデルに関連付けられたリレーションシップを決定しますが、 `whereBelongsTo` メソッドの第2引数としてリレーションシップ名を手動で指定することもできます。
$posts = Post::whereBelongsTo($user, 'author')->get();
Has One of Many
モデルには多くの関連モデルがある場合がありますが、「最新」または「最古」の関連モデルを簡単に取得したい場合があります。たとえば、 `User` モデルは多くの `Order` モデルに関連付けられている可能性がありますが、ユーザーが最後に配置した最新の注文とやり取りする便利な方法を定義したい場合があります。これは、 `ofMany` メソッドと組み合わせて `hasOne` リレーションシップタイプを使用することで実現できます。
/** * Get the user's most recent order. */public function latestOrder(): HasOne{ return $this->hasOne(Order::class)->latestOfMany();}
同様に、「最古」または最初の関連モデルを取得するメソッドを定義することもできます。
/** * Get the user's oldest order. */public function oldestOrder(): HasOne{ return $this->hasOne(Order::class)->oldestOfMany();}
デフォルトでは、 `latestOfMany` メソッドと `oldestOfMany` メソッドは、ソート可能なモデルの主キーに基づいて最新または最古の関連モデルを取得します。ただし、場合によっては、異なるソート基準を使用して、より大きなリレーションシップから単一のモデルを取得したい場合があります。
たとえば、 `ofMany` メソッドを使用して、ユーザーの最も高価な注文を取得できます。 `ofMany` メソッドは、ソート可能なカラムを最初の引数として、関連モデルのクエリに適用する集計関数( `min` または `max` )を第2引数として受け取ります。
/** * Get the user's largest order. */public function largestOrder(): HasOne{ return $this->hasOne(Order::class)->ofMany('price', 'max');}
PostgreSQL は UUID カラムに対して `MAX` 関数を実行できないため、現在、PostgreSQL の UUID カラムと one-of-many リレーションシップを組み合わせることはできません。
「Many」リレーションシップを Has One リレーションシップに変換する
`latestOfMany` 、 `oldestOfMany` 、または `ofMany` メソッドを使用して単一のモデルを取得する場合、多くの場合、同じモデルに対して「has many」リレーションシップが既に定義されています。便宜上、Laravel では、リレーションシップに対して `one` メソッドを呼び出すことで、このリレーションシップを「has one」リレーションシップに簡単に変換できます。
/** * Get the user's orders. */public function orders(): HasMany{ return $this->hasMany(Order::class);} /** * Get the user's largest order. */public function largestOrder(): HasOne{ return $this->orders()->one()->ofMany('price', 'max');}
高度な Has One of Many リレーションシップ
より高度な「has one of many」リレーションシップを構築できます。たとえば、 `Product` モデルには、新しい価格が公開された後もシステムに保持される多くの関連 `Price` モデルがある可能性があります。さらに、製品の新しい価格データは、 `published_at` カラムを介して将来の有効日になるように事前に公開できます。
つまり、公開日が将来ではない最新の公開価格を取得する必要があります。さらに、2つの価格が同じ公開日を持つ場合、IDが大きい価格を優先します。これを実現するには、最新の価格を決定するソート可能なカラムを含む配列を `ofMany` メソッドに渡す必要があります。さらに、クロージャが `ofMany` メソッドの第2引数として提供されます。このクロージャは、リレーションシップクエリに追加の公開日制約を追加する役割を果たします。
/** * Get the current pricing for the product. */public function currentPricing(): HasOne{ return $this->hasOne(Price::class)->ofMany([ 'published_at' => 'max', 'id' => 'max', ], function (Builder $query) { $query->where('published_at', '<', now()); });}
Has One Through
「has-one-through」リレーションシップは、別のモデルとの一対一の関連を定義します。ただし、このリレーションシップは、宣言するモデルを、第3モデルを介して別のモデルの1つのインスタンスと一致させることができることを示しています。
たとえば、車両修理ショップアプリケーションでは、各 `Mechanic` モデルは1つの `Car` モデルに関連付けられ、各 `Car` モデルは1つの `Owner` モデルに関連付けられています。メカニックとオーナーはデータベース内に直接的な関係がありませんが、メカニックは `Car` モデルを介してオーナーにアクセスできます。このリレーションシップを定義するために必要なテーブルを見てみましょう。
mechanics id - integer name - string cars id - integer model - string mechanic_id - integer owners id - integer name - string car_id - integer
リレーションシップのテーブル構造を調べたので、 `Mechanic` モデルにリレーションシップを定義しましょう。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\HasOneThrough; class Mechanic extends Model{ /** * Get the car's owner. */ public function carOwner(): HasOneThrough { return $this->hasOneThrough(Owner::class, Car::class); }}
`hasOneThrough` メソッドに渡される最初の引数は、アクセスする最終モデルの名前であり、2番目の引数は中間モデルの名前です。
または、関連するリレーションシップが関係するすべてのモデルに既に定義されている場合は、 `through` メソッドを呼び出してそれらのリレーションシップの名前を指定することで、「has-one-through」リレーションシップを流暢に定義できます。たとえば、 `Mechanic` モデルに `cars` リレーションシップがあり、 `Car` モデルに `owner` リレーションシップがある場合、「has-one-through」リレーションシップを次のように定義してメカニックとオーナーを接続できます。
// String based syntax...return $this->through('cars')->has('owner'); // Dynamic syntax...return $this->throughCars()->hasOwner();
キーの規則
リレーションシップのクエリを実行する際には、一般的な Eloquent 外部キーの規則が使用されます。リレーションシップのキーをカスタマイズする場合は、 `hasOneThrough` メソッドの第3引数と第4引数として渡すことができます。第3引数は中間モデルの外部キーの名前です。第4引数は最終モデルの外部キーの名前です。第5引数はローカルキー、第6引数は中間モデルのローカルキーです。
class Mechanic extends Model{ /** * Get the car's owner. */ public function carOwner(): HasOneThrough { return $this->hasOneThrough( Owner::class, Car::class, 'mechanic_id', // Foreign key on the cars table... 'car_id', // Foreign key on the owners table... 'id', // Local key on the mechanics table... 'id' // Local key on the cars table... ); }}
または、前述のように、関連するリレーションシップが関係するすべてのモデルに既に定義されている場合は、 `through` メソッドを呼び出してそれらのリレーションシップの名前を指定することで、「has-one-through」リレーションシップを流暢に定義できます。このアプローチは、既存のリレーションシップに既に定義されているキーの規則を再利用するという利点があります。
// String based syntax...return $this->through('cars')->has('owner'); // Dynamic syntax...return $this->throughCars()->hasOwner();
Has Many Through
「has-many-through」リレーションシップは、中間リレーションを介して遠隔関係にアクセスする便利な方法を提供します。たとえば、 Laravel Vapor のようなデプロイメントプラットフォームを構築しているとしましょう。 `Project` モデルは、中間 `Environment` モデルを介して多くの `Deployment` モデルにアクセスできます。この例を使用すると、特定のプロジェクトのすべてのデプロイメントを簡単に収集できます。このリレーションシップを定義するために必要なテーブルを見てみましょう。
projects id - integer name - string environments id - integer project_id - integer name - string deployments id - integer environment_id - integer commit_hash - string
リレーションシップのテーブル構造を調べたので、 `Project` モデルにリレーションシップを定義しましょう。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\HasManyThrough; class Project extends Model{ /** * Get all of the deployments for the project. */ public function deployments(): HasManyThrough { return $this->hasManyThrough(Deployment::class, Environment::class); }}
`hasManyThrough` メソッドに渡される最初の引数は、アクセスする最終モデルの名前であり、2番目の引数は中間モデルの名前です。
または、関連するリレーションシップが関係するすべてのモデルに既に定義されている場合は、 `through` メソッドを呼び出してそれらのリレーションシップの名前を指定することで、「has-many-through」リレーションシップを流暢に定義できます。たとえば、 `Project` モデルに `environments` リレーションシップがあり、 `Environment` モデルに `deployments` リレーションシップがある場合、「has-many-through」リレーションシップを次のように定義してプロジェクトとデプロイメントを接続できます。
// String based syntax...return $this->through('environments')->has('deployments'); // Dynamic syntax...return $this->throughEnvironments()->hasDeployments();
`Deployment` モデルのテーブルには `project_id` カラムが含まれていませんが、 `hasManyThrough` リレーションシップは `$project->deployments` を介してプロジェクトのデプロイメントへのアクセスを提供します。これらのモデルを取得するために、Eloquent は中間 `Environment` モデルのテーブルの `project_id` カラムを調べます。関連する環境IDが見つかった後、それらを使用して `Deployment` モデルのテーブルをクエリします。
キーの規則
リレーションシップのクエリを実行する際には、一般的な Eloquent 外部キーの規則が使用されます。リレーションシップのキーをカスタマイズする場合は、 `hasManyThrough` メソッドの第3引数と第4引数として渡すことができます。第3引数は中間モデルの外部キーの名前です。第4引数は最終モデルの外部キーの名前です。第5引数はローカルキー、第6引数は中間モデルのローカルキーです。
class Project extends Model{ public function deployments(): HasManyThrough { return $this->hasManyThrough( Deployment::class, Environment::class, 'project_id', // Foreign key on the environments table... 'environment_id', // Foreign key on the deployments table... 'id', // Local key on the projects table... 'id' // Local key on the environments table... ); }}
または、前述のように、関連するリレーションシップが関係するすべてのモデルに既に定義されている場合は、 `through` メソッドを呼び出してそれらのリレーションシップの名前を指定することで、「has-many-through」リレーションシップを流暢に定義できます。このアプローチは、既存のリレーションシップに既に定義されているキーの規則を再利用するという利点があります。
// String based syntax...return $this->through('environments')->has('deployments'); // Dynamic syntax...return $this->throughEnvironments()->hasDeployments();
多対多リレーションシップ
多対多のリレーションシップは、 `hasOne` と `hasMany` リレーションシップよりも少し複雑です。多対多のリレーションシップの例としては、多くの役割を持つユーザーと、その役割がアプリケーション内の他のユーザーと共有されるユーザーがあります。たとえば、ユーザーには「作成者」と「編集者」の役割が割り当てられる可能性がありますが、これらの役割は他のユーザーにも割り当てられる可能性があります。したがって、ユーザーには多くの役割があり、役割には多くのユーザーがいます。
テーブル構造
このリレーションシップを定義するには、 `users` 、 `roles` 、および `role_user` の3つのデータベーステーブルが必要です。 `role_user` テーブルは、関連モデル名のアルファベット順から派生し、 `user_id` と `role_id` カラムが含まれています。このテーブルは、ユーザーと役割をリンクする中間テーブルとして使用されます。
役割は多くのユーザーに属しているので、 `roles` テーブルに `user_id` カラムを配置することはできません。これは、役割が単一のユーザーのみに属することを意味します。役割が複数のユーザーに割り当てられるようにするには、 `role_user` テーブルが必要です。リレーションシップのテーブル構造は次のようになります。
users id - integer name - string roles id - integer name - string role_user user_id - integer role_id - integer
モデル構造
多対多のリレーションシップは、belongsToMany
メソッドの結果を返すメソッドを記述することで定義されます。belongsToMany
メソッドは、アプリケーションのすべてのEloquentモデルで使用されるIlluminate\Database\Eloquent\Model
ベースクラスによって提供されます。例として、User
モデルにroles
メソッドを定義してみましょう。このメソッドに渡される最初の引数は、関連モデルクラスの名前です。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\BelongsToMany; class User extends Model{ /** * The roles that belong to the user. */ public function roles(): BelongsToMany { return $this->belongsToMany(Role::class); }}
リレーションシップが定義されたら、roles
動的リレーションシッププロパティを使用してユーザーのロールにアクセスできます。
use App\Models\User; $user = User::find(1); foreach ($user->roles as $role) { // ...}
すべてのリレーションシップはクエリビルダーの役割も果たすため、roles
メソッドを呼び出して、クエリに条件を連鎖させることで、リレーションシップクエリにさらに制約を追加できます。
$roles = User::find(1)->roles()->orderBy('name')->get();
リレーションシップの中間テーブルのテーブル名を決定するために、Eloquentは2つの関連モデル名をアルファベット順に結合します。ただし、この規則をオーバーライドすることもできます。belongsToMany
メソッドに2番目の引数を渡すことで、これを行うことができます。
return $this->belongsToMany(Role::class, 'role_user');
中間テーブルの名前をカスタマイズすることに加えて、belongsToMany
メソッドに追加の引数を渡すことで、テーブル上のキーの列名もカスタマイズできます。3番目の引数は、リレーションシップを定義しているモデルの外部キー名であり、4番目の引数は、結合しているモデルの外部キー名です。
return $this->belongsToMany(Role::class, 'role_user', 'user_id', 'role_id');
リレーションシップの逆の定義
多対多のリレーションシップの「逆」を定義するには、belongsToMany
メソッドの結果も返す関連モデルにメソッドを定義する必要があります。ユーザー/ロールの例を完成させるために、Role
モデルにusers
メソッドを定義しましょう。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\BelongsToMany; class Role extends Model{ /** * The users that belong to the role. */ public function users(): BelongsToMany { return $this->belongsToMany(User::class); }}
ご覧のとおり、App\Models\User
モデルを参照する点を除いて、リレーションシップはUser
モデルの対応物とまったく同じように定義されています。belongsToMany
メソッドを再利用しているため、多対多のリレーションシップの「逆」を定義する際にも、通常のテーブルとキーのカスタマイズオプションを利用できます。
中間テーブルカラムの取得
既に学習したように、多対多のリレーションシップを操作するには、中間テーブルの存在が必要です。Eloquentは、このテーブルを操作するための非常に便利な方法を提供しています。たとえば、User
モデルには、関連付けられている多くのRole
モデルがあるとします。このリレーションシップにアクセスした後、モデルのpivot
属性を使用して中間テーブルにアクセスできます。
use App\Models\User; $user = User::find(1); foreach ($user->roles as $role) { echo $role->pivot->created_at;}
取得する各Role
モデルには、pivot
属性が自動的に割り当てられていることに注意してください。この属性には、中間テーブルを表すモデルが含まれています。
デフォルトでは、pivot
モデルにはモデルキーのみが存在します。中間テーブルに追加の属性が含まれている場合は、リレーションシップを定義する際にそれらを指定する必要があります。
return $this->belongsToMany(Role::class)->withPivot('active', 'created_by');
Eloquentによって自動的に管理されるcreated_at
とupdated_at
のタイムスタンプを中間テーブルに含めたい場合は、リレーションシップを定義する際にwithTimestamps
メソッドを呼び出します。
return $this->belongsToMany(Role::class)->withTimestamps();
Eloquentによって自動的に管理されるタイムスタンプを使用する中間テーブルには、created_at
とupdated_at
のタイムスタンプ列の両方が必要です。
pivot
属性名のカスタマイズ
前述のように、中間テーブルの属性は、pivot
属性を介してモデル上でアクセスできます。ただし、アプリケーション内での目的をより適切に反映するように、この属性の名前をカスタマイズできます。
たとえば、アプリケーションにポッドキャストを購読できるユーザーが含まれている場合、ユーザーとポッドキャスト間に多対多のリレーションシップが存在する可能性があります。この場合、中間テーブル属性の名前をpivot
ではなくsubscription
に変更することをお勧めします。これは、リレーションシップを定義する際にas
メソッドを使用して行うことができます。
return $this->belongsToMany(Podcast::class) ->as('subscription') ->withTimestamps();
カスタム中間テーブル属性を指定したら、カスタマイズされた名前を使用して中間テーブルデータにアクセスできます。
$users = User::with('podcasts')->get(); foreach ($users->flatMap->podcasts as $podcast) { echo $podcast->subscription->created_at;}
中間テーブルカラムによるクエリフィルタリング
belongsToMany
リレーションシップクエリによって返される結果を、リレーションシップを定義する際にwherePivot
、wherePivotIn
、wherePivotNotIn
、wherePivotBetween
、wherePivotNotBetween
、wherePivotNull
、およびwherePivotNotNull
メソッドを使用してフィルタリングすることもできます。
return $this->belongsToMany(Role::class) ->wherePivot('approved', 1); return $this->belongsToMany(Role::class) ->wherePivotIn('priority', [1, 2]); return $this->belongsToMany(Role::class) ->wherePivotNotIn('priority', [1, 2]); return $this->belongsToMany(Podcast::class) ->as('subscriptions') ->wherePivotBetween('created_at', ['2020-01-01 00:00:00', '2020-12-31 00:00:00']); return $this->belongsToMany(Podcast::class) ->as('subscriptions') ->wherePivotNotBetween('created_at', ['2020-01-01 00:00:00', '2020-12-31 00:00:00']); return $this->belongsToMany(Podcast::class) ->as('subscriptions') ->wherePivotNull('expired_at'); return $this->belongsToMany(Podcast::class) ->as('subscriptions') ->wherePivotNotNull('expired_at');
中間テーブルカラムによるクエリ順序付け
belongsToMany
リレーションシップクエリによって返される結果を、orderByPivot
メソッドを使用して並べ替えることができます。次の例では、ユーザーの最新のバッジをすべて取得します。
return $this->belongsToMany(Badge::class) ->where('rank', 'gold') ->orderByPivot('created_at', 'desc');
カスタム中間テーブルモデルの定義
多対多のリレーションシップの中間テーブルを表すカスタムモデルを定義する場合は、リレーションシップを定義する際にusing
メソッドを呼び出すことができます。カスタムピボットモデルを使用すると、メソッドやキャストなど、ピボットモデルに追加の動作を定義できます。
カスタムの多対多ピボットモデルはIlluminate\Database\Eloquent\Relations\Pivot
クラスを拡張する必要がありますが、カスタムのポリモーフィック多対多ピボットモデルはIlluminate\Database\Eloquent\Relations\MorphPivot
クラスを拡張する必要があります。例として、カスタムRoleUser
ピボットモデルを使用するRole
モデルを定義できます。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\BelongsToMany; class Role extends Model{ /** * The users that belong to the role. */ public function users(): BelongsToMany { return $this->belongsToMany(User::class)->using(RoleUser::class); }}
RoleUser
モデルを定義する際には、Illuminate\Database\Eloquent\Relations\Pivot
クラスを拡張する必要があります。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Relations\Pivot; class RoleUser extends Pivot{ // ...}
ピボットモデルはSoftDeletes
トレイトを使用できません。ピボットレコードをソフト削除する必要がある場合は、ピボットモデルを実際のEloquentモデルに変換することを検討してください。
カスタムピボットモデルと自動増分ID
カスタムピボットモデルを使用し、そのピボットモデルが自動増分主キーを持つ多対多のリレーションシップを定義した場合は、カスタムピボットモデルクラスがtrue
に設定されたincrementing
プロパティを定義していることを確認する必要があります。
/** * Indicates if the IDs are auto-incrementing. * * @var bool */public $incrementing = true;
ポリモーフィックリレーションシップ
ポリモーフィックリレーションシップでは、子モデルは単一の関連付けを使用して複数のタイプのモデルに属することができます。たとえば、ユーザーがブログ記事とビデオを共有できるアプリケーションを構築しているとします。このようなアプリケーションでは、Comment
モデルはPost
モデルとVideo
モデルの両方に属する可能性があります。
一対一(ポリモーフィック)
テーブル構造
一対一のポリモーフィックリレーションシップは、典型的な一対一のリレーションシップに似ていますが、子モデルは単一の関連付けを使用して複数のタイプのモデルに属することができます。たとえば、ブログのPost
とUser
は、Image
モデルとのポリモーフィックリレーションシップを共有できます。一対一のポリモーフィックリレーションシップを使用すると、投稿やユーザーに関連付けることができる一意の画像の単一のテーブルを持つことができます。まず、テーブル構造を見てみましょう。
posts id - integer name - string users id - integer name - string images id - integer url - string imageable_id - integer imageable_type - string
images
テーブルのimageable_id
列とimageable_type
列に注目してください。imageable_id
列には投稿またはユーザーのID値が含まれ、imageable_type
列には親モデルのクラス名が含まれます。imageable_type
列は、Eloquentによって、imageable
リレーションにアクセスする際に返す親モデルの「タイプ」を決定するために使用されます。この場合、列にはApp\Models\Post
またはApp\Models\User
のいずれかが含まれます。
モデル構造
次に、このリレーションシップを構築するために必要なモデル定義を見てみましょう。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\MorphTo; class Image extends Model{ /** * Get the parent imageable model (user or post). */ public function imageable(): MorphTo { return $this->morphTo(); }} use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\MorphOne; class Post extends Model{ /** * Get the post's image. */ public function image(): MorphOne { return $this->morphOne(Image::class, 'imageable'); }} use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\MorphOne; class User extends Model{ /** * Get the user's image. */ public function image(): MorphOne { return $this->morphOne(Image::class, 'imageable'); }}
リレーションシップの取得
データベーステーブルとモデルが定義されたら、モデルを介してリレーションシップにアクセスできます。たとえば、投稿の画像を取得するには、image
動的リレーションシッププロパティにアクセスできます。
use App\Models\Post; $post = Post::find(1); $image = $post->image;
morphTo
を呼び出すメソッドの名前をアクセスすることで、ポリモーフィックモデルの親を取得できます。この場合は、Image
モデルのimageable
メソッドです。そのため、動的リレーションシッププロパティとしてそのメソッドにアクセスします。
use App\Models\Image; $image = Image::find(1); $imageable = $image->imageable;
Image
モデルのimageable
リレーションは、どのタイプのモデルが画像を所有しているかに応じて、Post
インスタンスまたはUser
インスタンスを返します。
キーの規則
必要に応じて、ポリモーフィック子モデルで使用される「id」列と「type」列の名前を指定できます。その場合は、常にリレーションシップの名前をmorphTo
メソッドの最初の引数として渡してください。通常、この値はメソッド名と一致する必要があるため、PHPの__FUNCTION__
定数を使用できます。
/** * Get the model that the image belongs to. */public function imageable(): MorphTo{ return $this->morphTo(__FUNCTION__, 'imageable_type', 'imageable_id');}
一対多(ポリモーフィック)
テーブル構造
一対多のポリモーフィックリレーションシップは、典型的な一対多のリレーションシップに似ていますが、子モデルは単一の関連付けを使用して複数のタイプのモデルに属することができます。たとえば、アプリケーションのユーザーが投稿とビデオに「コメント」できるとします。ポリモーフィックリレーションシップを使用すると、単一のcomments
テーブルを使用して、投稿とビデオの両方のコメントを含めることができます。まず、このリレーションシップを構築するために必要なテーブル構造を見てみましょう。
posts id - integer title - string body - text videos id - integer title - string url - string comments id - integer body - text commentable_id - integer commentable_type - string
モデル構造
次に、このリレーションシップを構築するために必要なモデル定義を見てみましょう。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\MorphTo; class Comment extends Model{ /** * Get the parent commentable model (post or video). */ public function commentable(): MorphTo { return $this->morphTo(); }} use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\MorphMany; class Post extends Model{ /** * Get all of the post's comments. */ public function comments(): MorphMany { return $this->morphMany(Comment::class, 'commentable'); }} use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\MorphMany; class Video extends Model{ /** * Get all of the video's comments. */ public function comments(): MorphMany { return $this->morphMany(Comment::class, 'commentable'); }}
リレーションシップの取得
データベーステーブルとモデルが定義されたら、モデルの動的リレーションシッププロパティを介してリレーションシップにアクセスできます。たとえば、投稿のすべてのコメントにアクセスするには、comments
動的プロパティを使用できます。
use App\Models\Post; $post = Post::find(1); foreach ($post->comments as $comment) { // ...}
morphTo
を呼び出すメソッドの名前をアクセスすることで、ポリモーフィック子モデルの親を取得することもできます。この場合は、Comment
モデルのcommentable
メソッドです。そのため、コメントの親モデルにアクセスするために、動的リレーションシッププロパティとしてそのメソッドにアクセスします。
use App\Models\Comment; $comment = Comment::find(1); $commentable = $comment->commentable;
Comment
モデルのcommentable
リレーションは、どのタイプのモデルがコメントの親であるかに応じて、Post
インスタンスまたはVideo
インスタンスを返します。
子モデルでの親モデルの自動ハイドレーション
Eloquent の eager loading を使用していても、子モデルをループ処理中に親モデルにアクセスしようとすると、「N + 1」クエリの問題が発生する可能性があります。
$posts = Post::with('comments')->get(); foreach ($posts as $post) { foreach ($post->comments as $comment) { echo $comment->commentable->title; }}
上記の例では、「N + 1」クエリの問題が発生しています。これは、すべての `Post` モデルに対してコメントが eager loading されていたにもかかわらず、Eloquent が各子 `Comment` モデルに親 `Post` を自動的に hydration しないためです。
Eloquentに親モデルを子モデルに自動的にハイドレートさせたい場合は、morphMany
リレーションシップを定義する際にchaperone
メソッドを呼び出すことができます。
class Post extends Model{ /** * Get all of the post's comments. */ public function comments(): MorphMany { return $this->morphMany(Comment::class, 'commentable')->chaperone(); }}
あるいは、実行時に自動的な親モデルの hydration を有効にするには、リレーションシップを eager loading する際に `chaperone` メソッドを呼び出すことができます。
use App\Models\Post; $posts = Post::with([ 'comments' => fn ($comments) => $comments->chaperone(),])->get();
多対一(ポリモーフィック)
モデルには多くの関連モデルがある場合がありますが、「最新」または「最古」の関連モデルを簡単に取得したい場合があります。たとえば、User
モデルは多くのImage
モデルに関連付けられている場合がありますが、ユーザーがアップロードした最新の画像とやり取りするための便利な方法を定義したい場合があります。これは、ofMany
メソッドと組み合わせてmorphOne
リレーションシップタイプを使用することで実現できます。
/** * Get the user's most recent image. */public function latestImage(): MorphOne{ return $this->morphOne(Image::class, 'imageable')->latestOfMany();}
同様に、「最古」または最初の関連モデルを取得するメソッドを定義することもできます。
/** * Get the user's oldest image. */public function oldestImage(): MorphOne{ return $this->morphOne(Image::class, 'imageable')->oldestOfMany();}
デフォルトでは、 `latestOfMany` メソッドと `oldestOfMany` メソッドは、ソート可能なモデルの主キーに基づいて最新または最古の関連モデルを取得します。ただし、場合によっては、異なるソート基準を使用して、より大きなリレーションシップから単一のモデルを取得したい場合があります。
たとえば、ofMany
メソッドを使用して、ユーザーの「最もいいね」された画像を取得できます。ofMany
メソッドは、ソート可能な列を最初の引数として受け取り、関連モデルのクエリに適用する集計関数(min
またはmax
)を受け取ります。
/** * Get the user's most popular image. */public function bestImage(): MorphOne{ return $this->morphOne(Image::class, 'imageable')->ofMany('likes', 'max');}
より高度な「多対一」リレーションシップを構築することは可能です。詳細については、has one of manyドキュメントを参照してください。
多対多(ポリモーフィック)
テーブル構造
多対多のポリモーフィックリレーションシップは、「morph one」や「morph many」リレーションシップよりもやや複雑です。たとえば、Post
モデルとVideo
モデルは、Tag
モデルとのポリモーフィックリレーションシップを共有できます。このような状況で多対多のポリモーフィックリレーションシップを使用すると、投稿またはビデオに関連付けることができる一意のタグの単一のテーブルを持つことができます。まず、このリレーションシップを構築するために必要なテーブル構造を見てみましょう。
posts id - integer name - string videos id - integer name - string tags id - integer name - string taggables tag_id - integer taggable_id - integer taggable_type - string
ポリモーフィック多対多のリレーションシップについて詳しく説明する前に、一般的な多対多のリレーションシップに関するドキュメントを読むと役立つ場合があります。
モデル構造
次に、モデルのリレーションシップを定義する準備ができました。Post
モデルとVideo
モデルには、Eloquentモデルベースクラスによって提供されるmorphToMany
メソッドを呼び出すtags
メソッドが含まれます。
morphToMany
メソッドは、関連モデルの名前と「リレーションシップ名」を受け取ります。中間テーブル名とそれに含まれるキーに割り当てた名前に基づいて、リレーションシップを「taggable」と呼びます。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\MorphToMany; class Post extends Model{ /** * Get all of the tags for the post. */ public function tags(): MorphToMany { return $this->morphToMany(Tag::class, 'taggable'); }}
リレーションシップの逆の定義
次に、Tag
モデルで、その可能な親モデルごとにメソッドを定義する必要があります。そのため、この例では、posts
メソッドとvideos
メソッドを定義します。これらのメソッドはどちらも、morphedByMany
メソッドの結果を返す必要があります。
morphedByMany
メソッドは、関連モデルの名前と「リレーションシップ名」を受け取ります。中間テーブル名とそれに含まれるキーに割り当てた名前に基づいて、リレーションシップを「taggable」と呼びます。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\MorphToMany; class Tag extends Model{ /** * Get all of the posts that are assigned this tag. */ public function posts(): MorphToMany { return $this->morphedByMany(Post::class, 'taggable'); } /** * Get all of the videos that are assigned this tag. */ public function videos(): MorphToMany { return $this->morphedByMany(Video::class, 'taggable'); }}
リレーションシップの取得
データベーステーブルとモデルが定義されたら、モデルを介してリレーションシップにアクセスできます。たとえば、投稿のすべてのタグにアクセスするには、tags
動的リレーションシッププロパティを使用できます。
use App\Models\Post; $post = Post::find(1); foreach ($post->tags as $tag) { // ...}
ポリモーフィック子モデルからポリモーフィックリレーションの親を取得するには、morphedByMany
を呼び出すメソッドの名前をアクセスします。この場合は、Tag
モデルのposts
メソッドまたはvideos
メソッドです。
use App\Models\Tag; $tag = Tag::find(1); foreach ($tag->posts as $post) { // ...} foreach ($tag->videos as $video) { // ...}
カスタムポリモーフィックタイプ
デフォルトでは、Laravel は関連モデルの「type」を格納するために完全修飾クラス名を使用します。例えば、上記の1対多のリレーションシップの例では、Comment
モデルはPost
モデルまたはVideo
モデルのどちらかに属している可能性がありますが、デフォルトのcommentable_type
はそれぞれApp\Models\Post
またはApp\Models\Video
になります。ただし、これらの値をアプリケーションの内部構造から切り離したい場合があります。
例えば、モデル名ではなくpost
やvideo
のような単純な文字列を「type」として使用できます。これにより、モデル名が変更されても、データベース内の多態的「type」列の値は有効なままです。
use Illuminate\Database\Eloquent\Relations\Relation; Relation::enforceMorphMap([ 'post' => 'App\Models\Post', 'video' => 'App\Models\Video',]);
App\Providers\AppServiceProvider
クラスのboot
メソッドでenforceMorphMap
メソッドを呼び出すか、必要に応じて別のサービスプロバイダを作成できます。
特定のモデルのモーフエイリアスは、モデルのgetMorphClass
メソッドを使用して実行時に決定できます。逆に、モーフエイリアスに関連付けられた完全修飾クラス名は、Relation::getMorphedModel
メソッドを使用して決定できます。
use Illuminate\Database\Eloquent\Relations\Relation; $alias = $post->getMorphClass(); $class = Relation::getMorphedModel($alias);
既存のアプリケーションに「モーフマップ」を追加する場合、完全修飾クラス名を含むデータベース内のすべてのモーフ可能な*_type
列の値をその「マップ」名に変換する必要があります。
動的リレーションシップ
resolveRelationUsing
メソッドを使用して、Eloquentモデル間のリレーションシップを実行時に定義できます。通常のアプリケーション開発では推奨されませんが、Laravelパッケージの開発時には便利になる場合があります。
resolveRelationUsing
メソッドは、最初の引数として目的のリレーションシップ名を受け取ります。メソッドに渡される2番目の引数は、モデルインスタンスを受け取り、有効なEloquentリレーションシップ定義を返すクロージャである必要があります。通常、動的なリレーションシップは、サービスプロバイダのboot
メソッド内で設定する必要があります。
use App\Models\Order;use App\Models\Customer; Order::resolveRelationUsing('customer', function (Order $orderModel) { return $orderModel->belongsTo(Customer::class, 'customer_id');});
動的なリレーションシップを定義する際には、常にEloquentリレーションシップメソッドに明示的なキー名引数を指定してください。
リレーションのクエリ
すべてのEloquentリレーションシップはメソッドによって定義されているため、関連モデルをロードするためのクエリを実行せずに、これらのメソッドを呼び出してリレーションシップのインスタンスを取得できます。さらに、すべてのタイプのEloquentリレーションシップはクエリビルダーとしても機能するため、データベースに対してSQLクエリを実行する前に、リレーションシップクエリに制約をチェーンし続けることができます。
例えば、User
モデルに多くの関連するPost
モデルがあるブログアプリケーションを考えてみましょう。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\HasMany; class User extends Model{ /** * Get all of the posts for the user. */ public function posts(): HasMany { return $this->hasMany(Post::class); }}
posts
リレーションシップをクエリし、次のようにリレーションシップに追加の制約を追加できます。
use App\Models\User; $user = User::find(1); $user->posts()->where('active', 1)->get();
Laravelのクエリビルダーのメソッドをリレーションシップで使用できますので、利用可能なすべてのメソッドについてクエリビルダーのドキュメントを参照してください。
リレーションシップ後のorWhere
句のチェーン
上記の例で示されているように、クエリ時にリレーションシップに追加の制約を追加できます。ただし、リレーションシップにorWhere
句をチェーンする場合は注意してください。orWhere
句は、リレーションシップ制約と同じレベルで論理的にグループ化されます。
$user->posts() ->where('active', 1) ->orWhere('votes', '>=', 100) ->get();
上記の例は次のSQLを生成します。ご覧のとおり、or
句は、100票を超える投稿をすべて返すようにクエリに指示します。クエリは特定のユーザーに制約されなくなります。
select *from postswhere user_id = ? and active = 1 or votes >= 100
ほとんどの場合、論理グループを使用して、条件チェックを括弧でグループ化する必要があります。
use Illuminate\Database\Eloquent\Builder; $user->posts() ->where(function (Builder $query) { return $query->where('active', 1) ->orWhere('votes', '>=', 100); }) ->get();
上記の例は次のSQLを生成します。論理グループによって制約が適切にグループ化され、クエリは特定のユーザーに制約されたままです。
select *from postswhere user_id = ? and (active = 1 or votes >= 100)
リレーションメソッドと動的プロパティ
Eloquentリレーションシップクエリに追加の制約を追加する必要がない場合は、プロパティのようにリレーションシップにアクセスできます。例えば、User
とPost
の例モデルを引き続き使用して、ユーザーのすべての投稿に次のようにアクセスできます。
use App\Models\User; $user = User::find(1); foreach ($user->posts as $post) { // ...}
動的なリレーションシッププロパティは「遅延読み込み」を実行します。つまり、実際にアクセスするまでリレーションシップデータを読み込みません。このため、開発者はしばしば eager loadingを使用して、モデルのロード後にアクセスされることがわかっているリレーションシップを事前にロードします。 eager loading は、モデルのリレーションをロードするために実行する必要があるSQLクエリの数を大幅に削減します。
リレーションの存在クエリ
モデルレコードを取得する際に、リレーションシップの存在に基づいて結果を制限したい場合があります。例えば、少なくとも1つのコメントがあるブログ投稿をすべて取得したいとします。そのためには、リレーションシップの名前をhas
メソッドとorHas
メソッドに渡します。
use App\Models\Post; // Retrieve all posts that have at least one comment...$posts = Post::has('comments')->get();
演算子とカウント値を指定して、クエリをさらにカスタマイズすることもできます。
// Retrieve all posts that have three or more comments...$posts = Post::has('comments', '>=', 3)->get();
ネストされたhas
ステートメントは、「ドット」表記を使用して構築できます。例えば、少なくとも1つの画像があるコメントが少なくとも1つあるすべての投稿を取得できます。
// Retrieve posts that have at least one comment with images...$posts = Post::has('comments.images')->get();
さらに強力な機能が必要な場合は、whereHas
メソッドとorWhereHas
メソッドを使用して、コメントの内容を検査するなど、has
クエリに追加のクエリ制約を定義できます。
use Illuminate\Database\Eloquent\Builder; // Retrieve posts with at least one comment containing words like code%...$posts = Post::whereHas('comments', function (Builder $query) { $query->where('content', 'like', 'code%');})->get(); // Retrieve posts with at least ten comments containing words like code%...$posts = Post::whereHas('comments', function (Builder $query) { $query->where('content', 'like', 'code%');}, '>=', 10)->get();
Eloquentは現在、データベース間でのリレーションシップの存在に関するクエリをサポートしていません。リレーションシップは同じデータベース内に存在する必要があります。
インラインリレーションシップ存在クエリ
リレーションシップクエリに添付された単一の単純なwhere条件でリレーションシップの存在をクエリしたい場合は、whereRelation
、orWhereRelation
、whereMorphRelation
、orWhereMorphRelation
メソッドを使用する方が便利な場合があります。例えば、承認されていないコメントがあるすべての投稿をクエリできます。
use App\Models\Post; $posts = Post::whereRelation('comments', 'is_approved', false)->get();
もちろん、クエリビルダーのwhere
メソッドへの呼び出しと同様に、演算子を指定することもできます。
$posts = Post::whereRelation( 'comments', 'created_at', '>=', now()->subHour())->get();
リレーションの非存在クエリ
モデルレコードを取得する際に、リレーションシップの不在に基づいて結果を制限したい場合があります。例えば、コメントがないブログ投稿をすべて取得したいとします。そのためには、リレーションシップの名前をdoesntHave
メソッドとorDoesntHave
メソッドに渡します。
use App\Models\Post; $posts = Post::doesntHave('comments')->get();
さらに強力な機能が必要な場合は、whereDoesntHave
メソッドとorWhereDoesntHave
メソッドを使用して、コメントの内容を検査するなど、doesntHave
クエリに追加のクエリ制約を追加できます。
use Illuminate\Database\Eloquent\Builder; $posts = Post::whereDoesntHave('comments', function (Builder $query) { $query->where('content', 'like', 'code%');})->get();
ネストされたリレーションシップに対してクエリを実行するには、「ドット」表記を使用できます。例えば、次のクエリはコメントのないすべての投稿を取得しますが、禁止されていない作成者からのコメントがある投稿は結果に含まれます。
use Illuminate\Database\Eloquent\Builder; $posts = Post::whereDoesntHave('comments.author', function (Builder $query) { $query->where('banned', 0);})->get();
Morph Toリレーションのクエリ
「モーフto」リレーションシップの存在をクエリするには、whereHasMorph
メソッドとwhereDoesntHaveMorph
メソッドを使用できます。これらのメソッドは、最初の引数としてリレーションシップの名前を受け取ります。次に、メソッドはクエリに含める関連モデルの名前を受け取ります。最後に、リレーションシップクエリをカスタマイズするクロージャを提供できます。
use App\Models\Comment;use App\Models\Post;use App\Models\Video;use Illuminate\Database\Eloquent\Builder; // Retrieve comments associated to posts or videos with a title like code%...$comments = Comment::whereHasMorph( 'commentable', [Post::class, Video::class], function (Builder $query) { $query->where('title', 'like', 'code%'); })->get(); // Retrieve comments associated to posts with a title not like code%...$comments = Comment::whereDoesntHaveMorph( 'commentable', Post::class, function (Builder $query) { $query->where('title', 'like', 'code%'); })->get();
関連する多態的モデルの「type」に基づいてクエリ制約を追加する必要がある場合があります。whereHasMorph
メソッドに渡されるクロージャは、2番目の引数として$type
値を受け取ることができます。この引数を使用すると、構築されているクエリの「type」を検査できます。
use Illuminate\Database\Eloquent\Builder; $comments = Comment::whereHasMorph( 'commentable', [Post::class, Video::class], function (Builder $query, string $type) { $column = $type === Post::class ? 'content' : 'title'; $query->where($column, 'like', 'code%'); })->get();
「モーフto」リレーションシップの親の子をクエリしたい場合があります。これは、whereMorphedTo
メソッドとwhereNotMorphedTo
メソッドを使用して行うことができます。これらのメソッドは、指定されたモデルの適切なモーフタイプマッピングを自動的に決定します。これらのメソッドは、最初の引数としてmorphTo
リレーションシップの名前、2番目の引数として関連する親モデルを受け取ります。
$comments = Comment::whereMorphedTo('commentable', $post) ->orWhereMorphedTo('commentable', $video) ->get();
すべての関連モデルのクエリ
可能な多態的モデルの配列を渡す代わりに、ワイルドカード値として*
を提供できます。これにより、Laravelはデータベースからすべての可能な多態的タイプを取得するように指示されます。Laravelはこの操作を実行するために追加のクエリを実行します。
use Illuminate\Database\Eloquent\Builder; $comments = Comment::whereHasMorph('commentable', '*', function (Builder $query) { $query->where('title', 'like', 'foo%');})->get();
関連モデルの集計
関連モデルのカウント
モデルを実際にロードせずに、特定のリレーションシップの関連モデルの数をカウントしたい場合があります。そのためには、withCount
メソッドを使用できます。withCount
メソッドは、結果のモデルに{relation}_count
属性を配置します。
use App\Models\Post; $posts = Post::withCount('comments')->get(); foreach ($posts as $post) { echo $post->comments_count;}
withCount
メソッドに配列を渡すことで、複数のリレーションの「カウント」を追加し、クエリに追加の制約を追加することもできます。
use Illuminate\Database\Eloquent\Builder; $posts = Post::withCount(['votes', 'comments' => function (Builder $query) { $query->where('content', 'like', 'code%');}])->get(); echo $posts[0]->votes_count;echo $posts[0]->comments_count;
リレーションシップカウントの結果にエイリアスを付けることで、同じリレーションシップに対して複数のカウントを追加することもできます。
use Illuminate\Database\Eloquent\Builder; $posts = Post::withCount([ 'comments', 'comments as pending_comments_count' => function (Builder $query) { $query->where('approved', false); },])->get(); echo $posts[0]->comments_count;echo $posts[0]->pending_comments_count;
遅延カウント読み込み
loadCount
メソッドを使用すると、親モデルの取得後にリレーションシップカウントをロードできます。
$book = Book::first(); $book->loadCount('genres');
カウントクエリに追加のクエリ制約を設定する必要がある場合は、カウントするリレーションシップをキーとする配列を渡します。配列の値は、クエリビルダーインスタンスを受け取るクロージャである必要があります。
$book->loadCount(['reviews' => function (Builder $query) { $query->where('rating', 5);}])
リレーションシップカウントとカスタムSELECT文
withCount
をselect
文と組み合わせる場合は、select
メソッドの後にwithCount
を呼び出すようにしてください。
$posts = Post::select(['title', 'body']) ->withCount('comments') ->get();
その他の集計関数
withCount
メソッドに加えて、EloquentはwithMin
、withMax
、withAvg
、withSum
、withExists
メソッドを提供します。これらのメソッドは、結果のモデルに{relation}_{function}_{column}
属性を配置します。
use App\Models\Post; $posts = Post::withSum('comments', 'votes')->get(); foreach ($posts as $post) { echo $post->comments_sum_votes;}
集計関数の結果を別の名前でアクセスしたい場合は、独自のエイリアスを指定できます。
$posts = Post::withSum('comments as total_comments', 'votes')->get(); foreach ($posts as $post) { echo $post->total_comments;}
loadCount
メソッドと同様に、これらのメソッドの遅延バージョンも利用できます。これらの追加の集計操作は、既に取得されているEloquentモデルに対して実行できます。
$post = Post::first(); $post->loadSum('comments', 'votes');
これらの集計メソッドをselect
文と組み合わせる場合は、select
メソッドの後に集計メソッドを呼び出すようにしてください。
$posts = Post::select(['title', 'body']) ->withExists('comments') ->get();
Morph Toリレーションでの関連モデルのカウント
「モーフto」リレーションシップを eager load し、そのリレーションシップによって返される可能性のあるさまざまなエンティティの関連モデルカウントも eager load したい場合は、with
メソッドとmorphTo
リレーションシップのmorphWithCount
メソッドを組み合わせて使用できます。
この例では、Photo
モデルとPost
モデルがActivityFeed
モデルを作成できると仮定します。ActivityFeed
モデルは、parentable
という名前の「モーフto」リレーションシップを定義し、特定のActivityFeed
インスタンスの親Photo
またはPost
モデルを取得できるようにすると仮定します。さらに、Photo
モデルは多くのTag
モデルを持ち、Post
モデルは多くのComment
モデルを持つと仮定します。
ここで、ActivityFeed
インスタンスを取得し、各ActivityFeed
インスタンスのparentable
親モデルを eager load するとします。さらに、各親写真に関連付けられているタグの数と、各親投稿に関連付けられているコメントの数も取得したいとします。
use Illuminate\Database\Eloquent\Relations\MorphTo; $activities = ActivityFeed::with([ 'parentable' => function (MorphTo $morphTo) { $morphTo->morphWithCount([ Photo::class => ['tags'], Post::class => ['comments'], ]); }])->get();
遅延カウント読み込み
既に一連のActivityFeed
モデルを取得しており、アクティビティフィードに関連付けられているさまざまなparentable
モデルのネストされたリレーションシップカウントをロードしたいとします。これを実現するには、loadMorphCount
メソッドを使用できます。
$activities = ActivityFeed::with('parentable')->get(); $activities->loadMorphCount('parentable', [ Photo::class => ['tags'], Post::class => ['comments'],]);
Eager Loading
Eloquentのリレーションシップをプロパティとしてアクセスする場合、関連モデルは「遅延ロード」されます。これは、プロパティに初めてアクセスするまで、リレーションシップデータが実際にロードされないことを意味します。しかし、Eloquentでは、親モデルをクエリする際にリレーションシップを「早期ロード」することができます。早期ロードは「N + 1」クエリ問題を軽減します。「N + 1」クエリ問題を説明するために、Author
モデルに「属する」Book
モデルを考えてみましょう。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\BelongsTo; class Book extends Model{ /** * Get the author that wrote the book. */ public function author(): BelongsTo { return $this->belongsTo(Author::class); }}
では、すべての書籍とその著者を取得してみましょう。
use App\Models\Book; $books = Book::all(); foreach ($books as $book) { echo $book->author->name;}
このループは、データベーステーブル内のすべての書籍を取得するために1つのクエリを実行し、次に書籍の著者を取得するために各書籍ごとに別のクエリを実行します。したがって、25冊の本がある場合、上記のコードは26個のクエリを実行します。最初の書籍のクエリと、各書籍の著者を取得するための追加の25個のクエリです。
ありがたいことに、早期ロードを使用してこの操作をわずか2つのクエリに減らすことができます。クエリを作成する際に、with
メソッドを使用して、どのリレーションシップを早期ロードするかを指定できます。
$books = Book::with('author')->get(); foreach ($books as $book) { echo $book->author->name;}
この操作では、2つのクエリしか実行されません。1つはすべての書籍を取得するためのクエリ、もう1つはすべての書籍のすべての著者を取得するためのクエリです。
select * from books select * from authors where id in (1, 2, 3, 4, 5, ...)
複数のリレーションシップの早期ロード
複数の異なるリレーションシップを早期ロードする必要がある場合があります。そのためには、with
メソッドにリレーションシップの配列を渡すだけです。
$books = Book::with(['author', 'publisher'])->get();
ネストされた早期ロード
リレーションシップのリレーションシップを早期ロードするには、「ドット」構文を使用できます。たとえば、書籍のすべての著者と、著者のすべての個人連絡先を早期ロードしてみましょう。
$books = Book::with('author.contacts')->get();
あるいは、with
メソッドにネストされた配列を提供することで、ネストされた早期ロードリレーションシップを指定することもできます。これは、複数のネストされたリレーションシップを早期ロードする場合に便利です。
$books = Book::with([ 'author' => [ 'contacts', 'publisher', ],])->get();
ネストされた早期ロード `morphTo` リレーションシップ
morphTo
リレーションシップと、そのリレーションシップによって返される可能性のあるさまざまなエンティティ上のネストされたリレーションシップを早期ロードしたい場合は、morphTo
リレーションシップのmorphWith
メソッドと組み合わせてwith
メソッドを使用できます。このメソッドを説明するために、次のモデルを考えてみましょう。
<?php use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\MorphTo; class ActivityFeed extends Model{ /** * Get the parent of the activity feed record. */ public function parentable(): MorphTo { return $this->morphTo(); }}
この例では、Event
、Photo
、Post
モデルがActivityFeed
モデルを作成できると仮定します。さらに、Event
モデルはCalendar
モデルに属し、Photo
モデルはTag
モデルに関連付けられ、Post
モデルはAuthor
モデルに属すると仮定します。
これらのモデル定義とリレーションシップを使用して、ActivityFeed
モデルインスタンスを取得し、すべてのparentable
モデルとそのそれぞれのネストされたリレーションシップを早期ロードできます。
use Illuminate\Database\Eloquent\Relations\MorphTo; $activities = ActivityFeed::query() ->with(['parentable' => function (MorphTo $morphTo) { $morphTo->morphWith([ Event::class => ['calendar'], Photo::class => ['tags'], Post::class => ['author'], ]); }])->get();
特定の列の早期ロード
取得するリレーションシップからすべての列が必要とは限りません。このため、Eloquentでは、取得するリレーションシップのどの列を指定するかを指定できます。
$books = Book::with('author:id,name,book_id')->get();
この機能を使用する場合は、常にid
列と関連する外部キー列を、取得する列のリストに含める必要があります。
デフォルトでの早期ロード
モデルを取得する際に、常にいくつかのリレーションシップをロードしたい場合があります。これを実現するには、モデルに$with
プロパティを定義できます。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\BelongsTo; class Book extends Model{ /** * The relationships that should always be loaded. * * @var array */ protected $with = ['author']; /** * Get the author that wrote the book. */ public function author(): BelongsTo { return $this->belongsTo(Author::class); } /** * Get the genre of the book. */ public function genre(): BelongsTo { return $this->belongsTo(Genre::class); }}
単一のクエリに対して$with
プロパティからアイテムを削除したい場合は、without
メソッドを使用できます。
$books = Book::without('author')->get();
単一のクエリに対して$with
プロパティ内のすべてのアイテムを上書きしたい場合は、withOnly
メソッドを使用できます。
$books = Book::withOnly('genre')->get();
Eager Loadの制約
リレーションシップを早期ロードし、早期ロードクエリに追加のクエリ条件を指定したい場合があります。これは、with
メソッドにリレーションシップの配列を渡すことで実現できます。配列のキーはリレーションシップ名、配列の値は早期ロードクエリに追加の制約を追加するクロージャです。
use App\Models\User;use Illuminate\Contracts\Database\Eloquent\Builder; $users = User::with(['posts' => function (Builder $query) { $query->where('title', 'like', '%code%');}])->get();
この例では、Eloquentは、投稿のtitle
列に「code」という単語が含まれている投稿のみを早期ロードします。早期ロード操作をさらにカスタマイズするために、他のクエリビルダーメソッドを呼び出すことができます。
$users = User::with(['posts' => function (Builder $query) { $query->orderBy('created_at', 'desc');}])->get();
morphTo
リレーションシップの早期ロードの制約
morphTo
リレーションシップを早期ロードする場合、Eloquentは複数のクエリを実行して、各タイプの関連モデルを取得します。MorphTo
リレーションのconstrain
メソッドを使用して、これらのクエリのそれぞれに追加の制約を追加できます。
use Illuminate\Database\Eloquent\Relations\MorphTo; $comments = Comment::with(['commentable' => function (MorphTo $morphTo) { $morphTo->constrain([ Post::class => function ($query) { $query->whereNull('hidden_at'); }, Video::class => function ($query) { $query->where('type', 'educational'); }, ]);}])->get();
この例では、Eloquentは非表示になっていない投稿と、type
値が「educational」のビデオのみを早期ロードします。
リレーションシップの存在による早期ロードの制約
リレーションシップの存在を確認し、同時に同じ条件に基づいてリレーションシップをロードする必要がある場合があります。たとえば、指定されたクエリ条件に一致する子Post
モデルを持つUser
モデルのみを取得し、一致する投稿を早期ロードする場合があります。これは、withWhereHas
メソッドを使用して実現できます。
use App\Models\User; $users = User::withWhereHas('posts', function ($query) { $query->where('featured', true);})->get();
Lazy Eager Loading
親モデルが既に取得された後に、リレーションシップを早期ロードする必要がある場合があります。たとえば、関連モデルをロードするかどうかを動的に決定する必要がある場合に便利です。
use App\Models\Book; $books = Book::all(); if ($someCondition) { $books->load('author', 'publisher');}
早期ロードクエリに追加のクエリ制約を設定する必要がある場合は、ロードするリレーションシップをキーとする配列を渡すことができます。配列の値は、クエリインスタンスを受け取るクロージャインスタンスである必要があります。
$author->load(['books' => function (Builder $query) { $query->orderBy('published_date', 'asc');}]);
リレーションシップがまだロードされていない場合にのみロードするには、loadMissing
メソッドを使用します。
$book->loadMissing('author');
ネストされた遅延早期ロードとmorphTo
morphTo
リレーションシップと、そのリレーションシップによって返される可能性のあるさまざまなエンティティ上のネストされたリレーションシップを早期ロードしたい場合は、loadMorph
メソッドを使用できます。
このメソッドは、最初の引数としてmorphTo
リレーションシップの名前を受け取り、2番目の引数としてモデル/リレーションシップのペアの配列を受け取ります。このメソッドを説明するために、次のモデルを考えてみましょう。
<?php use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\MorphTo; class ActivityFeed extends Model{ /** * Get the parent of the activity feed record. */ public function parentable(): MorphTo { return $this->morphTo(); }}
この例では、Event
、Photo
、Post
モデルがActivityFeed
モデルを作成できると仮定します。さらに、Event
モデルはCalendar
モデルに属し、Photo
モデルはTag
モデルに関連付けられ、Post
モデルはAuthor
モデルに属すると仮定します。
これらのモデル定義とリレーションシップを使用して、ActivityFeed
モデルインスタンスを取得し、すべてのparentable
モデルとそのそれぞれのネストされたリレーションシップを早期ロードできます。
$activities = ActivityFeed::with('parentable') ->get() ->loadMorph('parentable', [ Event::class => ['calendar'], Photo::class => ['tags'], Post::class => ['author'], ]);
Lazy Loadingの防止
前述のように、リレーションシップを早期ロードすると、アプリケーションのパフォーマンスが大幅に向上することがよくあります。したがって、必要に応じて、リレーションシップの遅延ロードを常に防止するようにLaravelに指示できます。これを実現するには、基本的なEloquentモデルクラスで提供されるpreventLazyLoading
メソッドを呼び出すことができます。通常、これはアプリケーションのAppServiceProvider
クラスのboot
メソッド内で呼び出す必要があります。
preventLazyLoading
メソッドは、遅延ロードを防止するかどうかを示すオプションのブール値引数を受け取ります。たとえば、非本番環境でのみ遅延ロードを無効にし、本番環境が遅延ロードされたリレーションシップが本番コードに誤って存在する場合でも正常に機能し続けるようにすることができます。
use Illuminate\Database\Eloquent\Model; /** * Bootstrap any application services. */public function boot(): void{ Model::preventLazyLoading(! $this->app->isProduction());}
遅延ロードを防止した後、アプリケーションがEloquentリレーションシップを遅延ロードしようとすると、EloquentはIlluminate\Database\LazyLoadingViolationException
例外をスローします。
handleLazyLoadingViolationsUsing
メソッドを使用して、遅延ロード違反の動作をカスタマイズできます。たとえば、このメソッドを使用して、遅延ロード違反を例外でアプリケーションの実行を中断するのではなく、ログに記録するだけにすることができます。
Model::handleLazyLoadingViolationUsing(function (Model $model, string $relation) { $class = $model::class; info("Attempted to lazy load [{$relation}] on model [{$class}].");});
関連モデルの挿入と更新
save
メソッド
Eloquentは、リレーションシップに新しいモデルを追加するための便利なメソッドを提供します。たとえば、投稿に新しいコメントを追加する必要があるとします。Comment
モデルにpost_id
属性を手動で設定する代わりに、リレーションシップのsave
メソッドを使用してコメントを挿入できます。
use App\Models\Comment;use App\Models\Post; $comment = new Comment(['message' => 'A new comment.']); $post = Post::find(1); $post->comments()->save($comment);
動的なプロパティとしてcomments
リレーションシップにアクセスしませんでした。代わりに、comments
メソッドを呼び出して、リレーションシップのインスタンスを取得しました。save
メソッドは、新しいComment
モデルに適切なpost_id
値を自動的に追加します。
複数の関連モデルを保存する必要がある場合は、saveMany
メソッドを使用できます。
$post = Post::find(1); $post->comments()->saveMany([ new Comment(['message' => 'A new comment.']), new Comment(['message' => 'Another new comment.']),]);
save
メソッドとsaveMany
メソッドは、指定されたモデルインスタンスを永続化しますが、新しく永続化されたモデルを、既に親モデルにロードされているメモリ内のリレーションシップに追加しません。save
メソッドまたはsaveMany
メソッドを使用した後にリレーションシップにアクセスする予定がある場合は、refresh
メソッドを使用してモデルとそのリレーションシップをリロードすることをお勧めします。
$post->comments()->save($comment); $post->refresh(); // All comments, including the newly saved comment...$post->comments;
モデルとリレーションシップの再帰的な保存
モデルとその関連するすべてのリレーションシップをsave
する場合は、push
メソッドを使用できます。この例では、Post
モデルとそのコメント、コメントの著者が保存されます。
$post = Post::find(1); $post->comments[0]->message = 'Message';$post->comments[0]->author->name = 'Author Name'; $post->push();
pushQuietly
メソッドを使用して、イベントを発生させずにモデルとその関連するリレーションシップを保存できます。
$post->pushQuietly();
create
メソッド
save
メソッドとsaveMany
メソッドに加えて、属性の配列を受け取り、モデルを作成してデータベースに挿入するcreate
メソッドも使用できます。save
とcreate
の違いは、save
が完全なEloquentモデルインスタンスを受け取るのに対し、create
はプレーンなPHParray
を受け取ることです。新しく作成されたモデルは、create
メソッドによって返されます。
use App\Models\Post; $post = Post::find(1); $comment = $post->comments()->create([ 'message' => 'A new comment.',]);
createMany
メソッドを使用して、複数の関連モデルを作成できます。
$post = Post::find(1); $post->comments()->createMany([ ['message' => 'A new comment.'], ['message' => 'Another new comment.'],]);
createQuietly
メソッドとcreateManyQuietly
メソッドを使用して、イベントをディスパッチせずにモデルを作成できます。
$user = User::find(1); $user->posts()->createQuietly([ 'title' => 'Post title.',]); $user->posts()->createManyQuietly([ ['title' => 'First post.'], ['title' => 'Second post.'],]);
findOrNew
、firstOrNew
、firstOrCreate
、updateOrCreate
メソッドを使用して、リレーションシップでモデルを作成および更新することもできます。
create
メソッドを使用する前に、一括代入のドキュメントを確認してください。
Belongs Toリレーションシップ
子モデルを新しい親モデルに割り当てる場合は、associate
メソッドを使用できます。この例では、User
モデルはAccount
モデルへのbelongsTo
リレーションシップを定義しています。このassociate
メソッドは、子モデルの外部キーを設定します。
use App\Models\Account; $account = Account::find(10); $user->account()->associate($account); $user->save();
親モデルを子モデルから削除するには、dissociate
メソッドを使用できます。このメソッドは、リレーションシップの外部キーをnull
に設定します。
$user->account()->dissociate(); $user->save();
多対多リレーションシップ
アタッチ/デタッチ
Eloquentは、多対多のリレーションシップの処理をより便利にするメソッドも提供します。たとえば、ユーザーが複数のロールを持つことができ、ロールが複数のユーザーを持つことができるとします。attach
メソッドを使用して、リレーションシップの中間テーブルにレコードを挿入することで、ロールをユーザーにアタッチできます。
use App\Models\User; $user = User::find(1); $user->roles()->attach($roleId);
モデルにリレーションシップを追加する際、中間テーブルに挿入する追加データの配列を渡すこともできます。
$user->roles()->attach($roleId, ['expires' => $expires]);
ユーザーからロールを削除する必要がある場合があります。多対多のリレーションシップレコードを削除するには、detach
メソッドを使用します。detach
メソッドは中間テーブルから適切なレコードを削除しますが、両方のモデルはデータベースに残ります。
// Detach a single role from the user...$user->roles()->detach($roleId); // Detach all roles from the user...$user->roles()->detach();
便宜上、attach
とdetach
はIDの配列も入力として受け付けます。
$user = User::find(1); $user->roles()->detach([1, 2, 3]); $user->roles()->attach([ 1 => ['expires' => $expires], 2 => ['expires' => $expires],]);
関連付けの同期
sync
メソッドを使用して多対多の関連付けを作成することもできます。sync
メソッドは、中間テーブルに配置するIDの配列を受け取ります。指定された配列にないIDは、中間テーブルから削除されます。そのため、この操作が完了した後、指定された配列内のIDのみが中間テーブルに存在します。
$user->roles()->sync([1, 2, 3]);
IDと共に追加の中間テーブルの値を渡すこともできます。
$user->roles()->sync([1 => ['expires' => true], 2, 3]);
同期されたモデルIDごとに同じ中間テーブルの値を挿入する場合は、syncWithPivotValues
メソッドを使用できます。
$user->roles()->syncWithPivotValues([1, 2, 3], ['active' => true]);
指定された配列にない既存のIDを削除しない場合は、syncWithoutDetaching
メソッドを使用できます。
$user->roles()->syncWithoutDetaching([1, 2, 3]);
関連付けの切り替え
多対多のリレーションシップは、指定された関連モデルIDの添付状態を「切り替える」toggle
メソッドも提供します。指定されたIDが現在添付されている場合、それは削除されます。同様に、現在削除されている場合、それは添付されます。
$user->roles()->toggle([1, 2, 3]);
IDと共に追加の中間テーブルの値を渡すこともできます。
$user->roles()->toggle([ 1 => ['expires' => true], 2 => ['expires' => true],]);
中間テーブルのレコードの更新
リレーションシップの中間テーブルにある既存の行を更新する必要がある場合は、updateExistingPivot
メソッドを使用できます。このメソッドは、中間レコードの外部キーと更新する属性の配列を受け取ります。
$user = User::find(1); $user->roles()->updateExistingPivot($roleId, [ 'active' => false,]);
親タイムスタンプのタッチ
モデルが別のモデルへのbelongsTo
またはbelongsToMany
リレーションシップを定義する場合(例:Post
に属するComment
)、子モデルが更新されたときに親のタイムスタンプを更新することが役立つ場合があります。
たとえば、Comment
モデルが更新されたときに、所有するPost
のupdated_at
タイムスタンプを自動的に「タッチ」して、現在の日時になるように設定できます。これを実現するには、子モデルにtouches
プロパティを追加し、子モデルが更新されたときにupdated_at
タイムスタンプを更新する必要があるリレーションシップの名前を含めます。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\BelongsTo; class Comment extends Model{ /** * All of the relationships to be touched. * * @var array */ protected $touches = ['post']; /** * Get the post that the comment belongs to. */ public function post(): BelongsTo { return $this->belongsTo(Post::class); }}
親モデルのタイムスタンプは、Eloquentのsave
メソッドを使用して子モデルが更新された場合にのみ更新されます。