はじめに

データベースのテーブルはしばしば互いに関連しています。たとえば、ブログの投稿には多くのコメントがあるか、注文はそれを行ったユーザーに関連している可能性があります。Eloquentはこれらの関係を管理し、作業するのを簡単にし、さまざまな一般的な関係をサポートしています:

関係の定義

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

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

しかし、関係を使用する前に、Eloquentがサポートする各タイプの関係を定義する方法を学びましょう。

一対一

一対一の関係は、データベース関係の非常に基本的なタイプです。たとえば、Userモデルは1つのPhoneモデルに関連付けられる可能性があります。この関係を定義するには、Userモデルにphoneメソッドを配置します。phoneメソッドはhasOneメソッドを呼び出し、その結果を返す必要があります。hasOneメソッドは、モデルのIlluminate\Database\Eloquent\Model基底クラスを介してモデルに利用可能です:

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

Eloquentは、親モデル名に基づいて関係の外部キーを決定します。この場合、Phoneモデルは自動的にuser_id外部キーを持つと見なされます。この規則をオーバーライドしたい場合は、hasOneメソッドに2番目の引数を渡すことができます:

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

さらに、Eloquentは外部キーが親の主キー列と一致する値を持つべきであると仮定します。言い換えれば、Eloquentはユーザーのid列の値をuser_idPhoneレコードの列で探します。idまたはモデルの$primaryKeyプロパティ以外の主キー値を使用するように関係を設定したい場合は、hasOneメソッドに3番目の引数を渡すことができます:

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

関係の逆を定義する

したがって、UserモデルからPhoneモデルにアクセスできます。次に、電話を所有するユーザーにアクセスできるようにPhoneモデルに関係を定義しましょう。hasOne関係の逆をbelongsToメソッドを使用して定義できます:

  1. <?php
  2. namespace App\Models;
  3. use Illuminate\Database\Eloquent\Model;
  4. use Illuminate\Database\Eloquent\Relations\BelongsTo;
  5. class Phone extends Model
  6. {
  7. /**
  8. * Get the user that owns the phone.
  9. */
  10. public function user(): BelongsTo
  11. {
  12. return $this->belongsTo(User::class);
  13. }
  14. }
  1. Eloquentは、関係メソッドの名前を調べ、そのメソッド名に`````_id`````を付け加えることで外部キー名を決定します。したがって、この場合、Eloquent`````Phone`````モデルが`````user_id`````列を持つと仮定します。ただし、`````Phone`````モデルの外部キーが`````user_id`````でない場合は、`````belongsTo`````メソッドにカスタムキー名を2番目の引数として渡すことができます:
  2. ``````php
  3. /**
  4. * Get the user that owns the phone.
  5. */
  6. public function user(): BelongsTo
  7. {
  8. return $this->belongsTo(User::class, 'foreign_key');
  9. }
  10. `

親モデルがidを主キーとして使用していない場合、または異なる列を使用して関連モデルを見つけたい場合は、belongsToメソッドに3番目の引数を渡して親テーブルのカスタムキーを指定できます:

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

一対多

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

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

EloquentはCommentモデルの適切な外部キー列を自動的に決定します。慣例として、Eloquentは親モデルの「スネークケース」名を取り、_idでサフィックスを付けます。したがって、この例では、EloquentはCommentモデルの外部キー列がpost_idであると仮定します。

関係メソッドが定義されると、commentsプロパティにアクセスすることで関連するコメントのコレクションにアクセスできます。Eloquentは「動的関係プロパティ」を提供するため、関係メソッドにモデル上で定義されたプロパティのようにアクセスできます:

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

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

  1. $comment = Post::find(1)->comments()
  2. ->where('title', 'foo')
  3. ->first();
  1. ``````php
  2. return $this->hasMany(Comment::class, 'foreign_key');
  3. return $this->hasMany(Comment::class, 'foreign_key', 'local_key');
  4. `

子モデルの親モデルを自動的に水和する

Eloquentのイーガーローディングを利用している場合でも、子モデルをループしている間に子モデルから親モデルにアクセスしようとすると「N + 1」クエリの問題が発生する可能性があります:

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

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

Eloquentが親モデルを子モデルに自動的に水和するようにしたい場合は、hasMany関係を定義するときにchaperoneメソッドを呼び出すことができます:

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

または、実行時に自動的な親水和をオプトインしたい場合は、関係をイーガーロードするときにchaperoneモデルを呼び出すことができます:

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

一対多(逆)/ 属する

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

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

関係が定義されると、post「動的関係プロパティ」にアクセスすることでコメントの親投稿を取得できます:

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

上記の例では、Eloquentはidを持つPostモデルを見つけようとします。これは、Commentモデルのpost_id列と一致します。

Eloquentは、関係メソッドの名前を調べ、そのメソッド名に_を付け加え、親モデルの主キー列の名前を付け加えることで、デフォルトの外部キー名を決定します。したがって、この例では、EloquentはPostモデルの外部キーがcommentsテーブルのpost_idであると仮定します。

ただし、関係の外部キーがこれらの慣例に従わない場合は、belongsToメソッドにカスタム外部キー名を2番目の引数として渡すことができます:

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

デフォルトモデル

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

デフォルトモデルに属性を設定するには、withDefaultメソッドに配列またはクロージャを渡すことができます:

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

属する関係のクエリ

「属する」関係の子をクエリする場合、where句を手動で構築して対応するEloquentモデルを取得できます:

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

ただし、whereBelongsToメソッドを使用する方が便利な場合があります。このメソッドは、指定されたモデルに対して適切な関係と外部キーを自動的に決定します:

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

デフォルトでは、Laravelはモデルのクラス名に基づいて指定されたモデルに関連する関係を決定します。ただし、whereBelongsToメソッドに2番目の引数として関係名を手動で指定することもできます:

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

多くの中の一つを持つ

モデルが多くの関連モデルを持つ場合がありますが、関係の「最新」または「最古」の関連モデルを簡単に取得したい場合があります。たとえば、Userモデルは多くのOrderモデルに関連付けられているかもしれませんが、ユーザーが行った最新の注文と対話する便利な方法を定義したいとします。これは、hasOne関係タイプをofManyメソッドと組み合わせて使用することで実現できます:

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

同様に、関係の「最古」または最初の関連モデルを取得するメソッドを定義できます:

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

デフォルトでは、latestOfManyおよびoldestOfManyメソッドは、モデルの主キーに基づいて最新または最古の関連モデルを取得します。これはソート可能でなければなりません。ただし、時には異なるソート基準を使用して大きな関係から単一のモデルを取得したい場合があります。

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

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

PostgreSQLはUUID列に対してMAX関数を実行することをサポートしていないため、現在、PostgreSQL UUID列と組み合わせて多くの中の一つの関係を使用することはできません。

「多く」を「一つ」に変換する関係

しばしば、latestOfManyoldestOfMany、またはofManyメソッドを使用して単一のモデルを取得する際に、同じモデルに対して「多くの関係」がすでに定義されています。便利さのために、Laravelはこの関係を「一つの関係」に簡単に変換できるように、関係にoneメソッドを呼び出すことを許可します:

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

高度な多くの中の一つの関係

より高度な「多くの中の一つ」の関係を構築することが可能です。たとえば、Productモデルは多くの関連Priceモデルを持ち、新しい価格が公開された後もシステムに保持されます。さらに、製品の新しい価格データは、published_at列を介して将来の日付に発効するように事前に公開できる場合があります。

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

  1. /**
  2. * Get the current pricing for the product.
  3. */
  4. public 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. }

一つを介して持つ

「一つを介して持つ」関係は、別のモデルとの一対一の関係を定義します。ただし、この関係は、宣言モデルが3番目のモデルを介して別のモデルのインスタンスと一致できることを示します。

たとえば、車両修理ショップアプリケーションでは、各Mechanicモデルは1つのCarモデルに関連付けられ、各Carモデルは1つのOwnerモデルに関連付けられます。メカニックとオーナーはデータベース内で直接の関係を持たないが、メカニックはCarモデルを介してオーナーにアクセスできます。この関係を定義するために必要なテーブルを見てみましょう:

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

関係のテーブル構造を調べたので、Mechanicモデルに関係を定義しましょう:

  1. <?php
  2. namespace App\Models;
  3. use Illuminate\Database\Eloquent\Model;
  4. use Illuminate\Database\Eloquent\Relations\HasOneThrough;
  5. class Mechanic extends Model
  6. {
  7. /**
  8. * Get the car's owner.
  9. */
  10. public function carOwner(): HasOneThrough
  11. {
  12. return $this->hasOneThrough(Owner::class, Car::class);
  13. }
  14. }
  1. または、関係に関与するすべてのモデルで関連する関係がすでに定義されている場合は、`````through`````メソッドを呼び出してそれらの関係の名前を提供することで「一つを介して持つ」関係を流暢に定義できます。たとえば、`````Mechanic`````モデルが`````cars`````関係を持ち、`````Car`````モデルが`````owner`````関係を持つ場合、メカニックとオーナーを接続する「一つを介して持つ」関係を次のように定義できます:
  2. ``````php
  3. // String based syntax...
  4. return $this->through('cars')->has('owner');
  5. // Dynamic syntax...
  6. return $this->throughCars()->hasOwner();
  7. `

キーの慣例

関係のクエリを実行する際には、典型的なEloquent外部キーの慣例が使用されます。関係のキーをカスタマイズしたい場合は、hasOneThroughメソッドに3番目と4番目の引数として渡すことができます。3番目の引数は中間モデルの外部キーの名前です。4番目の引数は最終モデルの外部キーの名前です。5番目の引数はローカルキーであり、6番目の引数は中間モデルのローカルキーです:

  1. class 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メソッドを呼び出してそれらの関係の名前を提供することで「一つを介して持つ」関係を流暢に定義できます。このアプローチは、既存の関係で既に定義されたキーの慣例を再利用する利点を提供します:

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

多くを介して持つ

「多くを介して持つ」関係は、中間関係を介して遠くの関係にアクセスする便利な方法を提供します。たとえば、Laravel Vaporのようなデプロイメントプラットフォームを構築していると仮定しましょう。Projectモデルは、中間Environmentモデルを介して多くのDeploymentモデルにアクセスするかもしれません。この例を使用すると、特定のプロジェクトのすべてのデプロイメントを簡単に集めることができます。この関係を定義するために必要なテーブルを見てみましょう:

  1. projects
  2. id - integer
  3. name - string
  4. environments
  5. id - integer
  6. project_id - integer
  7. name - string
  8. deployments
  9. id - integer
  10. environment_id - integer
  11. commit_hash - string

関係のテーブル構造を調べたので、Projectモデルに関係を定義しましょう:

  1. <?php
  2. namespace App\Models;
  3. use Illuminate\Database\Eloquent\Model;
  4. use Illuminate\Database\Eloquent\Relations\HasManyThrough;
  5. class Project extends Model
  6. {
  7. /**
  8. * Get all of the deployments for the project.
  9. */
  10. public function deployments(): HasManyThrough
  11. {
  12. return $this->hasManyThrough(Deployment::class, Environment::class);
  13. }
  14. }
  1. または、関係に関与するすべてのモデルで関連する関係がすでに定義されている場合は、`````through`````メソッドを呼び出してそれらの関係の名前を提供することで「多くを介して持つ」関係を流暢に定義できます。たとえば、`````Project`````モデルが`````environments`````関係を持ち、`````Environment`````モデルが`````deployments`````関係を持つ場合、プロジェクトとデプロイメントを接続する「多くを介して持つ」関係を次のように定義できます:
  2. ``````php
  3. // String based syntax...
  4. return $this->through('environments')->has('deployments');
  5. // Dynamic syntax...
  6. return $this->throughEnvironments()->hasDeployments();
  7. `
  1. <a name="has-many-through-key-conventions"></a>
  2. #### キーの慣例
  3. 関係のクエリを実行する際には、典型的なEloquent外部キーの慣例が使用されます。関係のキーをカスタマイズしたい場合は、`````hasManyThrough`````メソッドに3番目と4番目の引数として渡すことができます。3番目の引数は中間モデルの外部キーの名前です。4番目の引数は最終モデルの外部キーの名前です。5番目の引数はローカルキーであり、6番目の引数は中間モデルのローカルキーです:
  4. ``````php
  5. class Project extends Model
  6. {
  7. public function deployments(): HasManyThrough
  8. {
  9. return $this->hasManyThrough(
  10. Deployment::class,
  11. Environment::class,
  12. 'project_id', // Foreign key on the environments table...
  13. 'environment_id', // Foreign key on the deployments table...
  14. 'id', // Local key on the projects table...
  15. 'id' // Local key on the environments table...
  16. );
  17. }
  18. }
  19. `

また、前述のように、関係に関与するすべてのモデルで関連する関係がすでに定義されている場合は、throughメソッドを呼び出してそれらの関係の名前を提供することで「多くを介して持つ」関係を流暢に定義できます。このアプローチは、既存の関係で既に定義されたキーの慣例を再利用する利点を提供します:

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

多対多の関係

多対多の関係は、hasOneおよびhasManyの関係よりも少し複雑です。多対多の関係の例は、ユーザーが多くの役割を持ち、これらの役割がアプリケーション内の他のユーザーと共有される場合です。たとえば、ユーザーは「著者」および「編集者」の役割を割り当てられるかもしれませんが、これらの役割は他のユーザーにも割り当てられる可能性があります。したがって、ユーザーは多くの役割を持ち、役割は多くのユーザーを持っています。

テーブル構造

この関係を定義するには、3つのデータベーステーブルが必要です:usersroles、およびrole_userrole_userテーブルは、関連するモデル名のアルファベット順から派生し、user_idおよびrole_id列を含みます。このテーブルは、ユーザーと役割をリンクする中間テーブルとして使用されます。

役割は多くのユーザーに属する可能性があるため、rolesテーブルにuser_id列を単純に置くことはできません。これは、役割が単一のユーザーにしか属せないことを意味します。役割が複数のユーザーに割り当てられることをサポートするために、role_userテーブルが必要です。関係のテーブル構造を次のように要約できます:

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

モデル構造

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

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

関係が定義されると、roles動的関係プロパティを使用してユーザーの役割にアクセスできます:

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

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

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

関係の中間テーブルのテーブル名を決定するために、Eloquentは2つの関連モデル名をアルファベット順に結合します。ただし、この慣例をオーバーライドすることもできます。belongsToManyメソッドに2番目の引数を渡すことで、これを行うことができます:

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

中間テーブルの名前をカスタマイズするだけでなく、belongsToManyメソッドに追加の引数を渡すことで、テーブル上のキーの列名をカスタマイズすることもできます。3番目の引数は、関係を定義しているモデルの外部キー名であり、4番目の引数は、結合するモデルの外部キー名です:

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

関係の逆を定義する

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

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

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

中間テーブル列の取得

すでに学んだように、多対多の関係を扱うには中間テーブルが必要です。Eloquentはこのテーブルと対話するための非常に便利な方法を提供します。たとえば、Userモデルが多くのRoleモデルに関連付けられていると仮定しましょう。この関係にアクセスした後、モデルのpivot属性を使用して中間テーブルにアクセスできます:

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

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

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

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

中間テーブルにcreated_atおよびupdated_atのタイムスタンプがあり、Eloquentによって自動的に維持されるようにしたい場合は、関係を定義するときにwithTimestampsメソッドを呼び出します:

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

Eloquentの自動的に維持されるタイムスタンプを利用する中間テーブルには、created_atおよびupdated_atのタイムスタンプ列が必要です。

ピボット属性名のカスタマイズ

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

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

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

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

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

中間テーブル列を介したクエリのフィルタリング

関係を定義するときに、wherePivotwherePivotInwherePivotNotInwherePivotBetweenwherePivotNotBetweenwherePivotNull、およびwherePivotNotNullメソッドを使用して、belongsToMany関係クエリによって返される結果をフィルタリングすることもできます:

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

中間テーブル列を介したクエリの順序付け

中間テーブル列を介したbelongsToMany関係クエリによって返される結果をorderByPivotメソッドを使用して順序付けることができます。次の例では、ユーザーの最新のバッジをすべて取得します:

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

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

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

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

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

多態的関係

多態的関係は、子モデルが単一の関連を使用して複数のタイプのモデルに属することを可能にします。たとえば、ユーザーがブログ投稿やビデオを共有できるアプリケーションを構築していると想像してください。このようなアプリケーションでは、CommentモデルはPostおよびVideoモデルの両方に属する可能性があります。

一対一(多態的)

テーブル構造

一対一の多態性関係は、典型的な一対一の関係に似ていますが、子モデルは単一の関連を使用して複数のタイプのモデルに属することができます。たとえば、ブログ PostUser は、Image モデルに対して多態性の関係を共有することがあります。一対一の多態性関係を使用すると、投稿やユーザーに関連付けられる可能性のあるユニークな画像の単一のテーブルを持つことができます。まず、テーブル構造を確認しましょう:

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

imageable_id および imageable_type 列に注意してください images テーブル上の。imageable_id 列には投稿またはユーザーの ID 値が含まれ、imageable_type 列には親モデルのクラス名が含まれます。imageable_type 列は、Eloquent が imageable 関係にアクセスする際に返すべき「タイプ」の親モデルを決定するために使用されます。この場合、列には App\Models\Post または App\Models\User のいずれかが含まれます。

モデル構造

次に、この関係を構築するために必要なモデル定義を確認しましょう:

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

関係の取得

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

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

多態性モデルの親を取得するには、morphTo への呼び出しを実行するメソッドの名前にアクセスします。この場合、それは imageable メソッドで、Image モデルにあります。したがって、そのメソッドに動的関係プロパティとしてアクセスします:

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

imageable 関係は Image モデル上で、画像を所有するモデルのタイプに応じて Post または User インスタンスを返します。

キーの慣習

必要に応じて、多態性子モデルで使用される「id」と「type」列の名前を指定できます。そうする場合は、morphTo メソッドに最初の引数として常に関係の名前を渡すことを確認してください。通常、この値はメソッド名と一致する必要があるため、PHP の FUNCTION 定数を使用できます:

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

一対多 (多態性)

テーブル構造

一対多の多態性関係は、典型的な一対多の関係に似ていますが、子モデルは単一の関連を使用して複数のタイプのモデルに属することができます。たとえば、アプリケーションのユーザーが投稿やビデオに「コメント」できると想像してください。多態性関係を使用すると、単一の comments テーブルを使用して、投稿とビデオの両方のコメントを含めることができます。まず、この関係を構築するために必要なテーブル構造を確認しましょう:

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

モデル構造

次に、この関係を構築するために必要なモデル定義を確認しましょう:

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

関係の取得

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

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

多態性子モデルの親を取得するには、morphTo への呼び出しを実行するメソッドの名前にアクセスします。この場合、それは commentable メソッドで、Comment モデルにあります。したがって、コメントの親モデルにアクセスするために、そのメソッドに動的関係プロパティとしてアクセスします:

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

commentable 関係は Comment モデル上で、コメントの親のモデルのタイプに応じて Post または Video インスタンスを返します。

子モデルの親モデルを自動的に水分補給する

Eloquentのイーガーローディングを利用している場合でも、子モデルをループしている間に子モデルから親モデルにアクセスしようとすると「N + 1」クエリの問題が発生する可能性があります:

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

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

Eloquentが親モデルを子モデルに自動的に水和するようにしたい場合は、chaperone関係を定義するときにhasManyメソッドを呼び出すことができます:

  1. class 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. }

または、実行時に自動的な親水和をオプトインしたい場合は、関係をイーガーロードするときにchaperoneモデルを呼び出すことができます:

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

多くの中の一つ (多態性)

モデルが多くの関連モデルを持つ場合がありますが、関係の「最新」または「最古」の関連モデルを簡単に取得したい場合があります。たとえば、User モデルは多くの Image モデルに関連付けられる場合がありますが、ユーザーがアップロードした最新の画像と簡単に対話する方法を定義したいと考えています。これを morphOne 関係タイプと ofMany メソッドを組み合わせて実現できます:

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

同様に、関係の「最古」または最初の関連モデルを取得するメソッドを定義できます:

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

デフォルトでは、latestOfManyおよびoldestOfManyメソッドは、モデルの主キーに基づいて最新または最古の関連モデルを取得します。これはソート可能でなければなりません。ただし、時には異なるソート基準を使用して大きな関係から単一のモデルを取得したい場合があります。

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

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

「多くの中の一つ」関係を構築することも可能です。詳細については、多くの中の一つのドキュメントを参照してください。

多対多 (多態性)

テーブル構造

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

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

多態性の多対多関係に飛び込む前に、典型的な 多対多の関係 に関するドキュメントを読むと良いでしょう。

モデル構造

次に、モデル上の関係を定義する準備が整いました。Post および Video モデルは、両方ともベースの Eloquent モデルクラスによって提供される tags メソッドを呼び出す morphToMany メソッドを含みます。

morphToMany メソッドは、関連モデルの名前と「関係名」を受け取ります。中間テーブル名とそれに含まれるキーに基づいて、関係を「taggable」と呼びます:

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

関係の逆を定義する

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

morphedByMany メソッドは、関連モデルの名前と「関係名」を受け取ります。中間テーブル名とそれに含まれるキーに基づいて、関係を「taggable」と呼びます:

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

関係の取得

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

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

多態性関係の親を多態性子モデルから取得するには、morphedByMany への呼び出しを実行するメソッドの名前にアクセスします。この場合、それは posts または videos メソッドで、Tag モデルにあります:

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

カスタム多態性タイプ

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

たとえば、モデル名を「タイプ」として使用する代わりに、postvideo のような単純な文字列を使用できます。これにより、モデルの名前が変更されても、データベース内の多態性「タイプ」列の値は有効のままになります:

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

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

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

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

既存のアプリケーションに「モーフマップ」を追加する場合、データベース内のすべてのモーフ可能な *_type 列の値は、完全修飾クラスを含む場合、対応する「マップ」名に変換する必要があります。

動的関係

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

resolveRelationUsing メソッドは、最初の引数として希望する関係名を受け取ります。メソッドに渡される2番目の引数は、モデルインスタンスを受け取り、有効な Eloquent 関係定義を返すクロージャである必要があります。通常、動的関係は サービスプロバイダー のブートメソッド内で構成する必要があります:

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

動的関係を定義する際は、Eloquent 関係メソッドに明示的なキー名引数を常に提供してください。

関係のクエリ

すべての Eloquent 関係はメソッドを介して定義されているため、実際に関連モデルを読み込むためにクエリを実行せずに、関係のインスタンスを取得するためにそれらのメソッドを呼び出すことができます。さらに、すべてのタイプの Eloquent 関係は クエリビルダー としても機能し、最終的にデータベースに対して SQL クエリを実行する前に、関係クエリに制約を追加し続けることができます。

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

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

posts 関係をクエリし、次のように関係に追加の制約を追加できます:

  1. use App\Models\User;
  2. $user = User::find(1);
  3. $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 票を超える 任意の 投稿を返すよう指示します。クエリはもはや特定のユーザーに制約されていません:

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

ほとんどの状況では、論理グループ を使用して、条件チェックを括弧でグループ化する必要があります:

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

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

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

関係メソッドと動的プロパティ

Eloquent 関係クエリに追加の制約を追加する必要がない場合、関係にプロパティのようにアクセスできます。たとえば、User および Post の例モデルを使用し続けると、ユーザーのすべての投稿に次のようにアクセスできます:

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

動的関係プロパティは「遅延読み込み」を実行します。つまり、実際にアクセスするまで関係データは読み込まれません。このため、開発者は、モデルを読み込んだ後にアクセスされることがわかっている関係を事前に読み込むために イager loading を使用することがよくあります。イager loading は、モデルの関係を読み込むために実行される SQL クエリの数を大幅に削減します。

関係の存在をクエリする

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

  1. use App\Models\Post;
  2. // Retrieve all posts that have at least one comment...
  3. $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 クエリに追加のクエリ制約を定義できます:

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

Eloquent は、データベース間での関係の存在をクエリすることは現在サポートしていません。関係は同じデータベース内に存在する必要があります。

インライン関係存在クエリ

関係の存在を、関係クエリに単一のシンプルな where 条件を付けてクエリしたい場合、whereRelationorWhereRelationwhereMorphRelation、および orWhereMorphRelation メソッドを使用する方が便利です。たとえば、承認されていないコメントがあるすべての投稿をクエリできます:

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

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

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

関係の不在をクエリする

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

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

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

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

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

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

morph To 関係をクエリする

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

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

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

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

すべての関連モデルをクエリする

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

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

関連モデルの集約

関連モデルのカウント

時には、モデルを実際に読み込むことなく、特定の関係の関連モデルの数をカウントしたい場合があります。これを実現するには、withCount メソッドを使用します。withCount メソッドは、結果モデルに {relation}_count 属性を追加します:

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

withCount メソッドに配列を渡すことで、複数の関係の「カウント」を追加し、クエリに追加の制約を追加できます:

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

同じ関係に対して複数のカウントを許可するために、関係カウント結果にエイリアスを付けることもできます:

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

遅延カウントの読み込み

loadCount メソッドを使用して、親モデルがすでに取得された後に関係カウントを読み込むことができます:

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

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

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

関係カウントとカスタム選択ステートメント

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

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

その他の集約関数

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

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

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

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

loadCount メソッドのように、これらのメソッドの遅延バージョンも利用可能です。これらの追加の集約操作は、すでに取得された Eloquent モデルに対して実行できます:

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

select ステートメントとこれらの集約メソッドを組み合わせている場合、select メソッドの後に集約メソッドを呼び出すことを確認してください:

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

morph To 関係の関連モデルのカウント

「morph to」関係をイager load し、その関係によって返されるさまざまなエンティティの関連モデルカウントを取得するには、with メソッドを morphTo 関係の morphWithCount メソッドと組み合わせて使用します。

この例では、Photo および Post モデルが ActivityFeed モデルを作成できると仮定します。ActivityFeed モデルが、parentable という名前の「morph to」関係を定義し、特定の ActivityFeed インスタンスに対して親 Photo または Post モデルを取得できると仮定します。さらに、Photo モデルが「多くの」Tag モデルを持ち、Post モデルが「多くの」Comment モデルを持つと仮定します。

さて、ActivityFeed インスタンスを取得し、各 ActivityFeed インスタンスの親モデル parentable をイager load し、各親写真に関連付けられたタグの数と各親投稿に関連付けられたコメントの数を取得したいと考えています:

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

遅延カウントの読み込み

すでに一連の ActivityFeed モデルを取得していると仮定し、アクティビティフィードに関連付けられたさまざまな parentable モデルのネストされた関係カウントを読み込む必要がある場合、loadMorphCount メソッドを使用してこれを実現できます:

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

イager loading

Eloquent 関係にプロパティとしてアクセスする場合、関連モデルは「遅延読み込み」されます。つまり、最初にプロパティにアクセスするまで、関係データは実際には読み込まれません。ただし、Eloquent は親モデルをクエリする際に関係を「イager load」できます。イager loading は「N + 1」クエリ問題を軽減します。N + 1 クエリ問題を説明するために、Book モデルが Author モデルに「属する」と仮定します:

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

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

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

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

幸いなことに、イager loading を使用して、この操作を 2 つのクエリに減らすことができます。クエリを構築する際に、with メソッドを使用して、どの関係をイager load するかを指定できます:

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

この操作では、2 つのクエリのみが実行されます - すべての本を取得するための 1 つのクエリと、すべての本の著者を取得するための 1 つのクエリ:

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

複数の関係をイager load

複数の異なる関係をイager load する必要がある場合があります。そのためには、with メソッドに関係の配列を渡すだけです:

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

ネストされたイager loading

関係の関係をイager load するには、「ドット」構文を使用できます。たとえば、すべての本の著者とすべての著者の個人連絡先をイager load しましょう:

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

または、with メソッドにネストされた配列を提供して、複数のネストされた関係をイager load することもできます:

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

ネストされたイager loading morphTo 関係

morphTo 関係をイager load し、その関係によって返されるさまざまなエンティティのネストされた関係をイager load するには、with メソッドを morphTo 関係の morphWith メソッドと組み合わせて使用します。このメソッドを説明するために、次のモデルを考えてみましょう:

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

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

これらのモデル定義と関係を使用して、ActivityFeed モデルインスタンスを取得し、すべての parentable モデルとそれぞれのネストされた関係をイager load できます:

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

特定の列をイager load

取得している関係のすべての列が常に必要なわけではありません。このため、Eloquent では、取得したい関係の列を指定できます:

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

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

デフォルトでイager load

モデルを取得する際に、常にいくつかの関係を読み込む必要がある場合があります。これを実現するには、モデルに $with プロパティを定義します:

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

単一のクエリのために $with プロパティからアイテムを削除したい場合は、without メソッドを使用できます:

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

単一のクエリのために $with プロパティ内のすべてのアイテムをオーバーライドしたい場合は、withOnly メソッドを使用できます:

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

イager load の制約

関係をイager load したいが、イager load クエリに追加のクエリ条件を指定したい場合があります。これを実現するには、with メソッドに関係の配列を渡します。配列のキーは関係名で、配列の値はイager load クエリに追加の制約を追加するクロージャです:

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

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

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

morphTo 関係のイager load の制約

morphTo 関係をイager load する場合、Eloquent は各タイプの関連モデルを取得するために複数のクエリを実行します。これらのクエリのそれぞれに追加の制約を追加するには、MorphTo 関係の constrain メソッドを使用できます:

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

この例では、Eloquent は、非表示でない投稿と、type 値が「教育的」であるビデオのみをイager load します。

関係の存在を持つイager load の制約

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

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

レイジーイagerローディング

時には、親モデルがすでに取得された後にリレーションシップをイagerローディングする必要があるかもしれません。たとえば、関連モデルを動的にロードするかどうかを決定する必要がある場合に便利です:

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

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

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

すでにロードされていない場合にのみリレーションシップをロードするには、loadMissingメソッドを使用します:

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

ネストされたレイジーイagerローディングとmorphTo

リレーションシップmorphToをイagerローディングし、そのリレーションシップによって返されるさまざまなエンティティのネストされたリレーションシップもイagerローディングしたい場合は、loadMorphメソッドを使用できます。

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

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

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

これらのモデル定義と関係を使用して、ActivityFeed モデルインスタンスを取得し、すべての parentable モデルとそれぞれのネストされた関係をイager load できます:

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

レイジーローディングの防止

前述のように、リレーションシップをイagerローディングすることは、アプリケーションに大きなパフォーマンスの利点をもたらすことがよくあります。したがって、Laravelにリレーションシップのレイジーローディングを常に防止するよう指示することができます。これを実現するには、基本のEloquentモデルクラスが提供するpreventLazyLoadingメソッドを呼び出すことができます。通常、このメソッドはアプリケーションのbootメソッド内で呼び出すべきです。

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

レイジーローディングを防止した後、Eloquentは、アプリケーションが任意のEloquentリレーションシップをレイジーロードしようとすると、Illuminate\Database\LazyLoadingViolationException例外をスローします。

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

関連モデルの挿入と更新

saveメソッド

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

  1. use App\Models\Comment;
  2. use App\Models\Post;
  3. $comment = new Comment(['message' => 'A new comment.']);
  4. $post = Post::find(1);
  5. $post->comments()->save($comment);
  1. 複数の関連モデルを保存する必要がある場合は、`````saveMany`````メソッドを使用できます:
  2. ``````php
  3. $post = Post::find(1);
  4. $post->comments()->saveMany([
  5. new Comment(['message' => 'A new comment.']),
  6. new Comment(['message' => 'Another new comment.']),
  7. ]);
  8. `
  1. ``````php
  2. $post->comments()->save($comment);
  3. $post->refresh();
  4. // All comments, including the newly saved comment...
  5. $post->comments;
  6. `

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

モデルとその関連リレーションシップをsaveしたい場合は、pushメソッドを使用できます。この例では、Postモデルが保存され、そのコメントとコメントの著者も保存されます:

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

createメソッド

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

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

複数の関連モデルを作成するには、createManyメソッドを使用できます:

  1. $post = Post::find(1);
  2. $post->comments()->createMany([
  3. ['message' => 'A new comment.'],
  4. ['message' => 'Another new comment.'],
  5. ]);
  1. ``````php
  2. $user = User::find(1);
  3. $user->posts()->createQuietly([
  4. 'title' => 'Post title.',
  5. ]);
  6. $user->posts()->createManyQuietly([
  7. ['title' => 'First post.'],
  8. ['title' => 'Second post.'],
  9. ]);
  10. `
  1. `````create`````メソッドを使用する前に、[マスアサインメント](3a3cc29cc73636fb.md#mass-assignment)のドキュメントを確認してください。
  2. <a name="updating-belongs-to-relationships"></a>
  3. ### Belongs Toリレーションシップ
  4. 子モデルを新しい親モデルに割り当てたい場合は、`````associate`````メソッドを使用できます。この例では、`````User`````モデルが`````belongsTo`````リレーションシップを`````Account`````モデルに定義しています。この`````associate`````メソッドは、子モデルの外部キーを設定します:
  5. ``````php
  6. use App\Models\Account;
  7. $account = Account::find(10);
  8. $user->account()->associate($account);
  9. $user->save();
  10. `

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

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

多対多リレーションシップ

アタッチ/デタッチ

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

  1. use App\Models\User;
  2. $user = User::find(1);
  3. $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. // Detach all roles from the user...
  4. $user->roles()->detach();

便利なことに、attachおよびdetachもIDの配列を入力として受け取ります:

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

アソシエーションの同期

  1. ``````php
  2. $user->roles()->sync([1, 2, 3]);
  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. $user->roles()->updateExistingPivot($roleId, [
  3. 'active' => false,
  4. ]);

親のタイムスタンプを更新する

モデルがbelongsToまたはbelongsToManyリレーションシップを別のモデルに定義している場合、たとえばCommentPostに属している場合、子モデルが更新されるときに親のタイムスタンプを更新することが役立つことがあります。

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

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

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