はじめに
データベースのテーブルはしばしば互いに関連しています。たとえば、ブログの投稿には多くのコメントがあるか、注文はそれを行ったユーザーに関連している可能性があります。Eloquentはこれらの関係を管理し、作業するのを簡単にし、さまざまな一般的な関係をサポートしています:
関係の定義
Eloquentの関係は、Eloquentモデルクラスのメソッドとして定義されます。関係は強力なクエリビルダーとしても機能するため、メソッドとして関係を定義することで、強力なメソッドチェーンとクエリ機能を提供します。たとえば、このposts
関係に追加のクエリ制約をチェーンすることができます:
$user->posts()->where('active', 1)->get();
しかし、関係を使用する前に、Eloquentがサポートする各タイプの関係を定義する方法を学びましょう。
一対一
一対一の関係は、データベース関係の非常に基本的なタイプです。たとえば、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);
}
}
``````php
$phone = User::find(1)->phone;
`
Eloquentは、親モデル名に基づいて関係の外部キーを決定します。この場合、Phone
モデルは自動的にuser_id
外部キーを持つと見なされます。この規則をオーバーライドしたい場合は、hasOne
メソッドに2番目の引数を渡すことができます:
return $this->hasOne(Phone::class, 'foreign_key');
さらに、Eloquentは外部キーが親の主キー列と一致する値を持つべきであると仮定します。言い換えれば、Eloquentはユーザーのid
列の値をuser_id
のPhone
レコードの列で探します。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);
}
}
Eloquentは、関係メソッドの名前を調べ、そのメソッド名に`````_id`````を付け加えることで外部キー名を決定します。したがって、この場合、Eloquentは`````Phone`````モデルが`````user_id`````列を持つと仮定します。ただし、`````Phone`````モデルの外部キーが`````user_id`````でない場合は、`````belongsTo`````メソッドにカスタムキー名を2番目の引数として渡すことができます:
``````php
/**
* Get the user that owns the phone.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'foreign_key');
}
`
親モデルがid
を主キーとして使用していない場合、または異なる列を使用して関連モデルを見つけたい場合は、belongsTo
メソッドに3番目の引数を渡して親テーブルのカスタムキーを指定できます:
/**
* Get the user that owns the phone.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'foreign_key', 'owner_key');
}
一対多
一対多の関係は、単一のモデルが1つ以上の子モデルの親である関係を定義するために使用されます。たとえば、ブログの投稿には無限の数のコメントがあるかもしれません。他のすべてのEloquent関係と同様に、一対多の関係は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();
``````php
return $this->hasMany(Comment::class, 'foreign_key');
return $this->hasMany(Comment::class, 'foreign_key', 'local_key');
`
子モデルの親モデルを自動的に水和する
Eloquentのイーガーローディングを利用している場合でも、子モデルをループしている間に子モデルから親モデルにアクセスしようとすると「N + 1」クエリの問題が発生する可能性があります:
$posts = Post::with('comments')->get();
foreach ($posts as $post) {
foreach ($post->comments as $comment) {
echo $comment->post->title;
}
}
上記の例では、コメントが各Post
モデルのためにイーガーロードされているにもかかわらず、Eloquentは各子Comment
モデルで親Post
を自動的に水和しないため、「N + 1」クエリの問題が発生しています。
Eloquentが親モデルを子モデルに自動的に水和するようにしたい場合は、hasMany
関係を定義するときにchaperone
メソッドを呼び出すことができます:
<?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();
}
}
または、実行時に自動的な親水和をオプトインしたい場合は、関係をイーガーロードするときにchaperone
モデルを呼び出すことができます:
use App\Models\Post;
$posts = Post::with([
'comments' => fn ($comments) => $comments->chaperone(),
])->get();
一対多(逆)/ 属する
投稿のすべてのコメントにアクセスできるようになったので、コメントがその親投稿にアクセスできるように関係を定義しましょう。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はid
を持つPost
モデルを見つけようとします。これは、Comment
モデルのpost_id
列と一致します。
Eloquentは、関係メソッドの名前を調べ、そのメソッド名に_
を付け加え、親モデルの主キー列の名前を付け加えることで、デフォルトの外部キー名を決定します。したがって、この例では、EloquentはPost
モデルの外部キーがcomments
テーブルの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');
}
デフォルトモデル
``````php
/**
* 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';
});
}
属する関係のクエリ
「属する」関係の子をクエリする場合、where
句を手動で構築して対応するEloquentモデルを取得できます:
use App\Models\Post;
$posts = Post::where('user_id', $user->id)->get();
ただし、whereBelongsTo
メソッドを使用する方が便利な場合があります。このメソッドは、指定されたモデルに対して適切な関係と外部キーを自動的に決定します:
$posts = Post::whereBelongsTo($user)->get();
``````php
$users = User::where('vip', true)->get();
$posts = Post::whereBelongsTo($users)->get();
`
デフォルトでは、Laravelはモデルのクラス名に基づいて指定されたモデルに関連する関係を決定します。ただし、whereBelongsTo
メソッドに2番目の引数として関係名を手動で指定することもできます:
$posts = Post::whereBelongsTo($user, 'author')->get();
多くの中の一つを持つ
モデルが多くの関連モデルを持つ場合がありますが、関係の「最新」または「最古」の関連モデルを簡単に取得したい場合があります。たとえば、User
モデルは多くのOrder
モデルに関連付けられているかもしれませんが、ユーザーが行った最新の注文と対話する便利な方法を定義したいとします。これは、hasOne
関係タイプをofMany
メソッドと組み合わせて使用することで実現できます:
/**
* 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
)を指定します:
/**
* Get the user's largest order.
*/
public function largestOrder(): HasOne
{
return $this->hasOne(Order::class)->ofMany('price', 'max');
}
PostgreSQLはUUID列に対してMAX
関数を実行することをサポートしていないため、現在、PostgreSQL UUID列と組み合わせて多くの中の一つの関係を使用することはできません。
「多く」を「一つ」に変換する関係
しばしば、latestOfMany
、oldestOfMany
、またはofMany
メソッドを使用して単一のモデルを取得する際に、同じモデルに対して「多くの関係」がすでに定義されています。便利さのために、Laravelはこの関係を「一つの関係」に簡単に変換できるように、関係に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');
}
高度な多くの中の一つの関係
より高度な「多くの中の一つ」の関係を構築することが可能です。たとえば、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());
});
}
一つを介して持つ
「一つを介して持つ」関係は、別のモデルとの一対一の関係を定義します。ただし、この関係は、宣言モデルが3番目のモデルを介して別のモデルのインスタンスと一致できることを示します。
たとえば、車両修理ショップアプリケーションでは、各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);
}
}
または、関係に関与するすべてのモデルで関連する関係がすでに定義されている場合は、`````through`````メソッドを呼び出してそれらの関係の名前を提供することで「一つを介して持つ」関係を流暢に定義できます。たとえば、`````Mechanic`````モデルが`````cars`````関係を持ち、`````Car`````モデルが`````owner`````関係を持つ場合、メカニックとオーナーを接続する「一つを介して持つ」関係を次のように定義できます:
``````php
// 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
メソッドを呼び出してそれらの関係の名前を提供することで「一つを介して持つ」関係を流暢に定義できます。このアプローチは、既存の関係で既に定義されたキーの慣例を再利用する利点を提供します:
// String based syntax...
return $this->through('cars')->has('owner');
// Dynamic syntax...
return $this->throughCars()->hasOwner();
多くを介して持つ
「多くを介して持つ」関係は、中間関係を介して遠くの関係にアクセスする便利な方法を提供します。たとえば、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);
}
}
または、関係に関与するすべてのモデルで関連する関係がすでに定義されている場合は、`````through`````メソッドを呼び出してそれらの関係の名前を提供することで「多くを介して持つ」関係を流暢に定義できます。たとえば、`````Project`````モデルが`````environments`````関係を持ち、`````Environment`````モデルが`````deployments`````関係を持つ場合、プロジェクトとデプロイメントを接続する「多くを介して持つ」関係を次のように定義できます:
``````php
// String based syntax...
return $this->through('environments')->has('deployments');
// Dynamic syntax...
return $this->throughEnvironments()->hasDeployments();
`
<a name="has-many-through-key-conventions"></a>
#### キーの慣例
関係のクエリを実行する際には、典型的なEloquent外部キーの慣例が使用されます。関係のキーをカスタマイズしたい場合は、`````hasManyThrough`````メソッドに3番目と4番目の引数として渡すことができます。3番目の引数は中間モデルの外部キーの名前です。4番目の引数は最終モデルの外部キーの名前です。5番目の引数はローカルキーであり、6番目の引数は中間モデルのローカルキーです:
``````php
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
メソッドを呼び出してそれらの関係の名前を提供することで「多くを介して持つ」関係を流暢に定義できます。このアプローチは、既存の関係で既に定義されたキーの慣例を再利用する利点を提供します:
// String based syntax...
return $this->through('environments')->has('deployments');
// Dynamic syntax...
return $this->throughEnvironments()->hasDeployments();
多対多の関係
多対多の関係は、hasOne
およびhasMany
の関係よりも少し複雑です。多対多の関係の例は、ユーザーが多くの役割を持ち、これらの役割がアプリケーション内の他のユーザーと共有される場合です。たとえば、ユーザーは「著者」および「編集者」の役割を割り当てられるかもしれませんが、これらの役割は他のユーザーにも割り当てられる可能性があります。したがって、ユーザーは多くの役割を持ち、役割は多くのユーザーを持っています。
テーブル構造
この関係を定義するには、3つのデータベーステーブルが必要です:users
、roles
、およびrole_user
。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);
}
}
ご覧のとおり、関係はUser
モデルの対応物とまったく同じように定義されており、App\Models\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');
中間テーブルにcreated_at
およびupdated_at
のタイムスタンプがあり、Eloquentによって自動的に維持されるようにしたい場合は、関係を定義するときにwithTimestamps
メソッドを呼び出します:
return $this->belongsToMany(Role::class)->withTimestamps();
Eloquentの自動的に維持されるタイムスタンプを利用する中間テーブルには、created_at
およびupdated_at
のタイムスタンプ列が必要です。
ピボット属性名のカスタマイズ
前述のように、中間テーブルの属性は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;
}
中間テーブル列を介したクエリのフィルタリング
関係を定義するときに、wherePivot
、wherePivotIn
、wherePivotNotIn
、wherePivotBetween
、wherePivotNotBetween
、wherePivotNull
、およびwherePivotNotNull
メソッドを使用して、belongsToMany
関係クエリによって返される結果をフィルタリングすることもできます:
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);
}
}
``````php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\Pivot;
class RoleUser extends Pivot
{
// ...
}
`
ピボットモデルはSoftDeletes
トレイトを使用できません。ピボットレコードをソフト削除する必要がある場合は、ピボットモデルを実際のEloquentモデルに変換することを検討してください。
カスタムピボットモデルと自動インクリメントID
カスタムピボットモデルを使用する多対多の関係を定義している場合、そのピボットモデルに自動インクリメントの主キーがある場合は、カスタムピボットモデルクラスがincrementing
プロパティをtrue
に設定して定義されていることを確認する必要があります。
/**
* 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
imageable_id
および imageable_type
列に注意してください images
テーブル上の。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
への呼び出しを実行するメソッドの名前にアクセスします。この場合、それは imageable
メソッドで、Image
モデルにあります。したがって、そのメソッドに動的関係プロパティとしてアクセスします:
use App\Models\Image;
$image = Image::find(1);
$imageable = $image->imageable;
imageable
関係は Image
モデル上で、画像を所有するモデルのタイプに応じて 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
への呼び出しを実行するメソッドの名前にアクセスします。この場合、それは commentable
メソッドで、Comment
モデルにあります。したがって、コメントの親モデルにアクセスするために、そのメソッドに動的関係プロパティとしてアクセスします:
use App\Models\Comment;
$comment = Comment::find(1);
$commentable = $comment->commentable;
commentable
関係は Comment
モデル上で、コメントの親のモデルのタイプに応じて Post
または Video
インスタンスを返します。
子モデルの親モデルを自動的に水分補給する
Eloquentのイーガーローディングを利用している場合でも、子モデルをループしている間に子モデルから親モデルにアクセスしようとすると「N + 1」クエリの問題が発生する可能性があります:
$posts = Post::with('comments')->get();
foreach ($posts as $post) {
foreach ($post->comments as $comment) {
echo $comment->commentable->title;
}
}
上記の例では、コメントが各Post
モデルのためにイーガーロードされているにもかかわらず、Eloquentは各子Post
モデルで親Comment
を自動的に水和しないため、「N + 1」クエリの問題が発生しています。
Eloquentが親モデルを子モデルに自動的に水和するようにしたい場合は、chaperone
関係を定義するときにhasMany
メソッドを呼び出すことができます:
class Post extends Model
{
/**
* Get all of the post's comments.
*/
public function comments(): MorphMany
{
return $this->morphMany(Comment::class, 'commentable')->chaperone();
}
}
または、実行時に自動的な親水和をオプトインしたい場合は、関係をイーガーロードするときにchaperone
モデルを呼び出すことができます:
use App\Models\Post;
$posts = Post::with([
'comments' => fn ($comments) => $comments->chaperone(),
])->get();
多くの中の一つ (多態性)
モデルが多くの関連モデルを持つ場合がありますが、関係の「最新」または「最古」の関連モデルを簡単に取得したい場合があります。たとえば、User
モデルは多くの Image
モデルに関連付けられる場合がありますが、ユーザーがアップロードした最新の画像と簡単に対話する方法を定義したいと考えています。これを morphOne
関係タイプと ofMany
メソッドを組み合わせて実現できます:
/**
* 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');
}
「多くの中の一つ」関係を構築することも可能です。詳細については、多くの中の一つのドキュメントを参照してください。
多対多 (多態性)
テーブル構造
多対多の多態性関係は、「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 モデルクラスによって提供される tags
メソッドを呼び出す morphToMany
メソッドを含みます。
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
への呼び出しを実行するメソッドの名前にアクセスします。この場合、それは posts
または videos
メソッドで、Tag
モデルにあります:
use App\Models\Tag;
$tag = Tag::find(1);
foreach ($tag->posts as $post) {
// ...
}
foreach ($tag->videos as $video) {
// ...
}
カスタム多態性タイプ
デフォルトでは、Laravel は関連モデルの「タイプ」を保存するために完全修飾クラス名を使用します。たとえば、上記の一対多の関係の例では、Comment
モデルが Post
または Video
モデルに属する場合、デフォルトの commentable_type
はそれぞれ App\Models\Post
または App\Models\Video
になります。ただし、これらの値をアプリケーションの内部構造から切り離したい場合があります。
たとえば、モデル名を「タイプ」として使用する代わりに、post
や video
のような単純な文字列を使用できます。これにより、モデルの名前が変更されても、データベース内の多態性「タイプ」列の値は有効のままになります:
use Illuminate\Database\Eloquent\Relations\Relation;
Relation::enforceMorphMap([
'post' => 'App\Models\Post',
'video' => 'App\Models\Video',
]);
enforceMorphMap
メソッドを boot
メソッドの App\Providers\AppServiceProvider
クラスで呼び出すか、別のサービスプロバイダーを作成することができます。
特定のモデルのモーフエイリアスを実行時に決定するには、モデルの getMorphClass
メソッドを使用します。逆に、モーフエイリアスに関連付けられた完全修飾クラス名を決定するには、Relation::getMorphedModel
メソッドを使用します:
use Illuminate\Database\Eloquent\Relations\Relation;
$alias = $post->getMorphClass();
$class = Relation::getMorphedModel($alias);
既存のアプリケーションに「モーフマップ」を追加する場合、データベース内のすべてのモーフ可能な *_type
列の値は、完全修飾クラスを含む場合、対応する「マップ」名に変換する必要があります。
動的関係
resolveRelationUsing
メソッドを使用して、実行時に Eloquent モデル間の関係を定義できます。通常のアプリケーション開発には推奨されませんが、Laravel パッケージを開発する際に役立つ場合があります。
resolveRelationUsing
メソッドは、最初の引数として希望する関係名を受け取ります。メソッドに渡される2番目の引数は、モデルインスタンスを受け取り、有効な Eloquent 関係定義を返すクロージャである必要があります。通常、動的関係は サービスプロバイダー のブートメソッド内で構成する必要があります:
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 posts
where 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 posts
where user_id = ? and (active = 1 or votes >= 100)
関係メソッドと動的プロパティ
Eloquent 関係クエリに追加の制約を追加する必要がない場合、関係にプロパティのようにアクセスできます。たとえば、User
および Post
の例モデルを使用し続けると、ユーザーのすべての投稿に次のようにアクセスできます:
use App\Models\User;
$user = User::find(1);
foreach ($user->posts as $post) {
// ...
}
動的関係プロパティは「遅延読み込み」を実行します。つまり、実際にアクセスするまで関係データは読み込まれません。このため、開発者は、モデルを読み込んだ後にアクセスされることがわかっている関係を事前に読み込むために イager loading を使用することがよくあります。イager 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 関係をクエリする
「morph 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();
関連多態性モデルの「タイプ」に基づいてクエリ制約を追加する必要がある場合があります。whereHasMorph
メソッドに渡されるクロージャは、2 番目の引数として $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();
すべての関連モデルをクエリする
可能な多態性モデルの配列を渡す代わりに、*
をワイルドカード値として提供できます。これにより、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);
}])
関係カウントとカスタム選択ステートメント
withCount
と select
ステートメントを組み合わせている場合、withCount
メソッドを select
メソッドの後に呼び出すことを確認してください:
$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 関係の関連モデルのカウント
「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 し、各親写真に関連付けられたタグの数と各親投稿に関連付けられたコメントの数を取得したいと考えています:
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'],
]);
イager loading
Eloquent 関係にプロパティとしてアクセスする場合、関連モデルは「遅延読み込み」されます。つまり、最初にプロパティにアクセスするまで、関係データは実際には読み込まれません。ただし、Eloquent は親モデルをクエリする際に関係を「イager load」できます。イager loading は「N + 1」クエリ問題を軽減します。N + 1 クエリ問題を説明するために、Book
モデルが Author
モデルに「属する」と仮定します:
<?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 のクエリを実行します: 元の本の 1 つと、各本の著者を取得するための 25 の追加クエリ。
幸いなことに、イager loading を使用して、この操作を 2 つのクエリに減らすことができます。クエリを構築する際に、with
メソッドを使用して、どの関係をイager load するかを指定できます:
$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, ...)
複数の関係をイager load
複数の異なる関係をイager load する必要がある場合があります。そのためには、with
メソッドに関係の配列を渡すだけです:
$books = Book::with(['author', 'publisher'])->get();
ネストされたイager loading
関係の関係をイager load するには、「ドット」構文を使用できます。たとえば、すべての本の著者とすべての著者の個人連絡先をイager load しましょう:
$books = Book::with('author.contacts')->get();
または、with
メソッドにネストされた配列を提供して、複数のネストされた関係をイager load することもできます:
$books = Book::with([
'author' => [
'contacts',
'publisher',
],
])->get();
ネストされたイager loading morphTo 関係
morphTo
関係をイager load し、その関係によって返されるさまざまなエンティティのネストされた関係をイager load するには、with
メソッドを morphTo
関係の morphWith
メソッドと組み合わせて使用します。このメソッドを説明するために、次のモデルを考えてみましょう:
<?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
モデルとそれぞれのネストされた関係をイager load できます:
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();
特定の列をイager load
取得している関係のすべての列が常に必要なわけではありません。このため、Eloquent では、取得したい関係の列を指定できます:
$books = Book::with('author:id,name,book_id')->get();
この機能を使用する場合は、id
列と、取得したい列のリストに関連する外部キー列を常に含める必要があります。
デフォルトでイager load
モデルを取得する際に、常にいくつかの関係を読み込む必要がある場合があります。これを実現するには、モデルに $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();
イager load の制約
関係をイager load したいが、イager load クエリに追加のクエリ条件を指定したい場合があります。これを実現するには、with
メソッドに関係の配列を渡します。配列のキーは関係名で、配列の値はイager load クエリに追加の制約を追加するクロージャです:
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
という単語が含まれている投稿のみをイager load します。他の クエリビルダー メソッドを呼び出して、イager load 操作をさらにカスタマイズできます:
$users = User::with(['posts' => function (Builder $query) {
$query->orderBy('created_at', 'desc');
}])->get();
morphTo 関係のイager load の制約
morphTo
関係をイager load する場合、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
値が「教育的」であるビデオのみをイager load します。
関係の存在を持つイager load の制約
関係の存在を確認しながら、同じ条件に基づいて関係をイager load する必要がある場合があります。たとえば、特定のクエリ条件に一致する子 Post
モデルを持つ User
モデルのみを取得し、同時に一致する投稿をイager load したい場合があります。これを withWhereHas
メソッドを使用して実現できます:
use App\Models\User;
$users = User::withWhereHas('posts', function ($query) {
$query->where('featured', true);
})->get();
レイジーイagerローディング
時には、親モデルがすでに取得された後にリレーションシップをイagerローディングする必要があるかもしれません。たとえば、関連モデルを動的にロードするかどうかを決定する必要がある場合に便利です:
use App\Models\Book;
$books = Book::all();
if ($someCondition) {
$books->load('author', 'publisher');
}
イagerローディングクエリに追加のクエリ制約を設定する必要がある場合は、ロードしたいリレーションシップをキーとする配列を渡すことができます。配列の値は、クエリインスタンスを受け取るクロージャインスタンスである必要があります:
$author->load(['books' => function (Builder $query) {
$query->orderBy('published_date', 'asc');
}]);
すでにロードされていない場合にのみリレーションシップをロードするには、loadMissing
メソッドを使用します:
$book->loadMissing('author');
ネストされたレイジーイagerローディングとmorphTo
リレーションシップmorphTo
をイagerローディングし、そのリレーションシップによって返されるさまざまなエンティティのネストされたリレーションシップもイagerローディングしたい場合は、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
モデルとそれぞれのネストされた関係をイager load できます:
$activities = ActivityFeed::with('parentable')
->get()
->loadMorph('parentable', [
Event::class => ['calendar'],
Photo::class => ['tags'],
Post::class => ['author'],
]);
レイジーローディングの防止
前述のように、リレーションシップをイagerローディングすることは、アプリケーションに大きなパフォーマンスの利点をもたらすことがよくあります。したがって、Laravelにリレーションシップのレイジーローディングを常に防止するよう指示することができます。これを実現するには、基本のEloquentモデルクラスが提供するpreventLazyLoading
メソッドを呼び出すことができます。通常、このメソッドはアプリケーションのboot
メソッド内で呼び出すべきです。
``````php
use Illuminate\Database\Eloquent\Model;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Model::preventLazyLoading(! $this->app->isProduction());
}
`
レイジーローディングを防止した後、Eloquentは、アプリケーションが任意のEloquentリレーションシップをレイジーロードしようとすると、Illuminate\Database\LazyLoadingViolationException
例外をスローします。
``````php
Model::handleLazyLoadingViolationUsing(function (Model $model, string $relation) {
$class = $model::class;
info("Attempted to lazy load [{$relation}] on model [{$class}].");
});
`
関連モデルの挿入と更新
saveメソッド
Eloquentは、リレーションシップに新しいモデルを追加するための便利なメソッドを提供します。たとえば、投稿に新しいコメントを追加する必要があるかもしれません。post_id
属性をComment
モデルに手動で設定する代わりに、リレーションシップのsave
メソッドを使用してコメントを挿入できます:
use App\Models\Comment;
use App\Models\Post;
$comment = new Comment(['message' => 'A new comment.']);
$post = Post::find(1);
$post->comments()->save($comment);
複数の関連モデルを保存する必要がある場合は、`````saveMany`````メソッドを使用できます:
``````php
$post = Post::find(1);
$post->comments()->saveMany([
new Comment(['message' => 'A new comment.']),
new Comment(['message' => 'Another new comment.']),
]);
`
``````php
$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();
``````php
$post->pushQuietly();
`
createメソッド
save
およびsaveMany
メソッドに加えて、create
メソッドも使用できます。このメソッドは属性の配列を受け取り、モデルを作成し、データベースに挿入します。save
とcreate
の違いは、save
が完全なEloquentモデルインスタンスを受け取るのに対し、create
はプレーンPHP array
を受け取ることです。新しく作成されたモデルは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.'],
]);
``````php
$user = User::find(1);
$user->posts()->createQuietly([
'title' => 'Post title.',
]);
$user->posts()->createManyQuietly([
['title' => 'First post.'],
['title' => 'Second post.'],
]);
`
`````create`````メソッドを使用する前に、[マスアサインメント](3a3cc29cc73636fb.md#mass-assignment)のドキュメントを確認してください。
<a name="updating-belongs-to-relationships"></a>
### Belongs Toリレーションシップ
子モデルを新しい親モデルに割り当てたい場合は、`````associate`````メソッドを使用できます。この例では、`````User`````モデルが`````belongsTo`````リレーションシップを`````Account`````モデルに定義しています。この`````associate`````メソッドは、子モデルの外部キーを設定します:
``````php
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],
]);
アソシエーションの同期
``````php
$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
リレーションシップを別のモデルに定義している場合、たとえばComment
がPost
に属している場合、子モデルが更新されるときに親のタイムスタンプを更新することが役立つことがあります。
たとえば、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
メソッドを使用して更新された場合にのみ更新されます。