メインコンテンツまでスキップ

エロクエント: リレーションシップ

はじめに

データベースのテーブルはしばしば関連しています。たとえば、ブログ投稿には多くのコメントが付いているか、注文はそれを行ったユーザーと関連しているかもしれません。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);
}
}

hasOne メソッドに渡される最初の引数は関連するモデルクラスの名前です。関係が定義されると、Eloquentのダイナミックプロパティを使用して関連レコードを取得できます。ダイナミックプロパティを使用すると、モデルで定義されたプロパティのように関係メソッドにアクセスできます。

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

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

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

さらに、Eloquentは外部キーが親の主キー列と一致する値を持つものと仮定します。つまり、EloquentはPhoneレコードのuser_id列にユーザーのid列の値を探します。関係をid以外の主キー値やモデルの$primaryKeyプロパティを使用したい場合は、hasOneメソッドに第3引数を渡すことができます:

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

関係の逆を定義する

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

    <?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Phone extends Model
{
/**
* Get the user that owns the phone.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}

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

Eloquentは、関係メソッドの名前を調べ、メソッド名に_idを付け加えたものを外部キー名として決定します。したがって、この場合、EloquentはPhoneモデルがuser_id列を持つものと仮定します。ただし、Phoneモデルの外部キーがuser_idでない場合は、belongsToメソッドの第2引数としてカスタムキー名を渡すことができます:

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

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

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

一対多

一対多の関係は、1つのモデルが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();

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

    return $this->hasMany(Comment::class, 'foreign_key');

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

一対多(逆)/ Belongs To

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

    <?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Comment extends Model
{
/**
* Get the post that owns the comment.
*/
public function post(): BelongsTo
{
return $this->belongsTo(Post::class);
}
}

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

    use App\Models\Comment;

$comment = Comment::find(1);

return $comment->post->title;

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

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

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

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

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

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

デフォルトモデル

belongsTohasOnehasOneThrough、およびmorphOneの関係を使用すると、指定された関係がnullの場合に返されるデフォルトモデルを定義できます。このパターンはしばしばNull Objectパターンと呼ばれ、コード内の条件付きチェックを削除するのに役立ちます。次の例では、user関係は、Postモデルにユーザーが関連付けられていない場合に、空のApp\Models\Userモデルを返します:

    /**
* Get the author of the post.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class)->withDefault();
}

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

    /**
* Get the author of the post.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class)->withDefault([
'name' => 'Guest Author',
]);
}

/**
* Get the author of the post.
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class)->withDefault(function (User $user, Post $post) {
$user->name = 'Guest Author';
});
}

Belongs To関係のクエリ

"belongs to"関係の子をクエリする場合、対応するEloquentモデルを取得するためにwhere句を手動で構築することができます:

    use App\Models\Post;

$posts = Post::where('user_id', $user->id)->get();

ただし、指定されたモデルに適切な関係と外部キーを自動的に決定するwhereBelongsToメソッドを使用する方が便利かもしれません:

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

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

    $users = User::where('vip', true)->get();

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

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

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

1つの中の1つ

時には、モデルには多くの関連モデルがあるかもしれませんが、関係の中で「最新」または「最古」の関連モデルを簡単に取得したい場合があります。たとえば、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 列との組み合わせで 1 つの中の多数の関係を使用することはできません。

"多数" の関係を 1 つの関係に変換する

しばしば、latestOfManyoldestOfMany、または ofMany メソッドを使用して単一のモデルを取得する際に、同じモデルに対して "has many" 関係が既に定義されている場合があります。便宜上、Laravel では、この関係を簡単に "has one" 関係に変換することができるように、関係に 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');
}

より高度な1対多の関係

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

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

/**
* 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());
});
}

1対1を通じた関係

「1対1を通じた」関係は、別のモデルとの1対1の関係を定義します。ただし、この関係は、宣言モデルが第三のモデルを介して進むことで、別のモデルの1つのインスタンスと一致することを示します。

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

    mechanics
id - integer
name - string

cars
id - integer
model - string
mechanic_id - integer

owners
id - integer
name - string
car_id - integer

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

    <?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasOneThrough;

class Mechanic extends Model
{
/**
* Get the car's owner.
*/
public function carOwner(): HasOneThrough
{
return $this->hasOneThrough(Owner::class, Car::class);
}
}

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

または、関連する関係がすでに関係するすべてのモデルで定義されている場合、through メソッドを呼び出してこれらの関係の名前を指定することで、「has-one-through」関係をスムーズに定義することができます。たとえば、Mechanic モデルに cars 関係があり、Car モデルに owner 関係がある場合、次のようにメカニックと所有者を接続する「has-one-through」関係を定義できます:

// String based syntax...
return $this->through('cars')->has('owner');

// Dynamic syntax...
return $this->throughCars()->hasOwner();

キーの規則

関係のクエリを実行する際には、通常の Eloquent 外部キーの規則が使用されます。関係のキーをカスタマイズしたい場合は、hasOneThrough メソッドの第三引数と第四引数としてそれらを渡すことができます。第三引数は中間モデルの外部キーの名前です。第四引数は最終モデルの外部キーの名前です。第五引数はローカルキーであり、第六引数は中間モデルのローカルキーです:

    class Mechanic extends Model
{
/**
* Get the car's owner.
*/
public function carOwner(): HasOneThrough
{
return $this->hasOneThrough(
Owner::class,
Car::class,
'mechanic_id', // Foreign key on the cars table...
'car_id', // Foreign key on the owners table...
'id', // Local key on the mechanics table...
'id' // Local key on the cars table...
);
}
}

または、先に述べたように、関連する関係がすでに関係するすべてのモデルで定義されている場合、through メソッドを呼び出してこれらの関係の名前を指定することで、「has-one-through」関係をスムーズに定義することができます。このアプローチは、既存の関係で定義されたキーの規則を再利用する利点を提供します:

// String based syntax...
return $this->through('cars')->has('owner');

// Dynamic syntax...
return $this->throughCars()->hasOwner();

Has Many Through

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

    projects
id - integer
name - string

environments
id - integer
project_id - integer
name - string

deployments
id - integer
environment_id - integer
commit_hash - string

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

    <?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;

class Project extends Model
{
/**
* Get all of the deployments for the project.
*/
public function deployments(): HasManyThrough
{
return $this->hasManyThrough(Deployment::class, Environment::class);
}
}

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

また、関係するモデルすべてで関係がすでに定義されている場合は、through メソッドを呼び出してこれらの関係の名前を指定することで、「has-many-through」関係を流暢に定義することもできます。例えば、Project モデルに environments 関係があり、Environment モデルに deployments 関係がある場合、プロジェクトとデプロイメントを接続する「has-many-through」関係を次のように定義できます:

// String based syntax...
return $this->through('environments')->has('deployments');

// Dynamic syntax...
return $this->throughEnvironments()->hasDeployments();

Deployment モデルのテーブルに project_id カラムが含まれていない場合でも、hasManyThrough 関係を使用すると、$project->deployments を介してプロジェクトのデプロイメントにアクセスできます。これらのモデルを取得するために、Eloquent は中間の Environment モデルのテーブルの project_id カラムを調査します。関連する環境 ID を見つけた後、それらは Deployment モデルのテーブルをクエリするために使用されます。

キーの規則

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

    class Project extends Model
{
public function deployments(): HasManyThrough
{
return $this->hasManyThrough(
Deployment::class,
Environment::class,
'project_id', // Foreign key on the environments table...
'environment_id', // Foreign key on the deployments table...
'id', // Local key on the projects table...
'id' // Local key on the environments table...
);
}
}

また、先に述べたように、関係するモデルすべてで関係がすでに定義されている場合は、through メソッドを呼び出してこれらの関係の名前を指定することで、「has-many-through」関係を流暢に定義することもできます。このアプローチは、既存の関係で定義されているキーの規則を再利用する利点があります:```

// String based syntax...
return $this->through('environments')->has('deployments');

// Dynamic syntax...
return $this->throughEnvironments()->hasDeployments();

多対多の関係

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

テーブル構造

この関係を定義するためには、usersrolesrole_user の 3 つのデータベーステーブルが必要です。role_user テーブルは、関連するモデル名のアルファベット順に派生し、user_idrole_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');

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

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

警告

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

pivot 属性名のカスタマイズ

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

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

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

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

    $users = User::with('podcasts')->get();

foreach ($users->flatMap->podcasts as $podcast) {
echo $podcast->subscription->created_at;
}

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

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

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

return $this->belongsToMany(Role::class)
->wherePivotIn('priority', [1, 2]);

return $this->belongsToMany(Role::class)
->wherePivotNotIn('priority', [1, 2]);

return $this->belongsToMany(Podcast::class)
->as('subscriptions')
->wherePivotBetween('created_at', ['2020-01-01 00:00:00', '2020-12-31 00:00:00']);

return $this->belongsToMany(Podcast::class)
->as('subscriptions')
->wherePivotNotBetween('created_at', ['2020-01-01 00:00:00', '2020-12-31 00:00:00']);

return $this->belongsToMany(Podcast::class)
->as('subscriptions')
->wherePivotNull('expired_at');

return $this->belongsToMany(Podcast::class)
->as('subscriptions')
->wherePivotNotNull('expired_at');

中間テーブルの列を介したクエリの並べ替え

belongsToManyリレーションシップクエリの結果をorderByPivotメソッドを使用して順序付けすることができます。次の例では、ユーザーの最新のバッジをすべて取得します:

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

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

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

カスタム多対多のピボットモデルは、Illuminate\Database\Eloquent\Relations\Pivotクラスを拡張するべきです。カスタム多態多対多のピボットモデルは、Illuminate\Database\Eloquent\Relations\MorphPivotクラスを拡張するべきです。例えば、カスタムRoleUserピボットモデルを使用するRoleモデルを定義することができます:

    <?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

class Role extends Model
{
/**
* The users that belong to the role.
*/
public function users(): BelongsToMany
{
return $this->belongsToMany(User::class)->using(RoleUser::class);
}
}

RoleUserモデルを定義する際には、Illuminate\Database\Eloquent\Relations\Pivotクラスを拡張するべきです:

    <?php

namespace App\Models;

use Illuminate\Database\Eloquent\Relations\Pivot;

class RoleUser extends Pivot
{
// ...
}

警告

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

カスタムピボットモデルと増分ID

カスタムピボットモデルを使用する多対多のリレーションシップを定義し、そのピボットモデルに自動増分プライマリキーがある場合は、カスタムピボットモデルクラスがincrementingプロパティをtrueに設定することを確認してください。

    /**
* Indicates if the IDs are auto-incrementing.
*
* @var bool
*/
public $incrementing = true;

多態リレーションシップ

多態リレーションシップは、1つの関連付けを使用して子モデルが1つ以上のモデルタイプに属することを可能にします。例えば、ブログ投稿と動画を共有できるアプリケーションを構築しているとします。このようなアプリケーションでは、CommentモデルはPostモデルとVideoモデルの両方に属する可能性があります。

一対一(多態)

テーブル構造

一対一の多態関係は、通常の一対一の関係に似ていますが、子モデルは1つの関連付けを使用して複数の種類のモデルに属することができます。例えば、ブログの PostUserImage モデルに対して多態関係を共有する可能性があります。一対一の多態関係を使用すると、投稿やユーザーに関連付けられる一意の画像の単一のテーブルを持つことができます。まず、テーブル構造を見てみましょう:

    posts
id - integer
name - string

users
id - integer
name - string

images
id - integer
url - string
imageable_id - integer
imageable_type - string

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

モデル構造

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

    <?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;

class Image extends Model
{
/**
* Get the parent imageable model (user or post).
*/
public function imageable(): MorphTo
{
return $this->morphTo();
}
}

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphOne;

class Post extends Model
{
/**
* Get the post's image.
*/
public function image(): MorphOne
{
return $this->morphOne(Image::class, 'imageable');
}
}

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphOne;

class User extends Model
{
/**
* Get the user's image.
*/
public function image(): MorphOne
{
return $this->morphOne(Image::class, 'imageable');
}
}

関係の取得

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

    use App\Models\Post;

$post = Post::find(1);

$image = $post->image;

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

    use App\Models\Image;

$image = Image::find(1);

$imageable = $image->imageable;

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

キーの規則

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

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

一対多(多態性)

テーブル構造

一対多の多態性関係は、通常の一対多の関係に似ていますが、子モデルは1つの関連付けを使用して複数の種類のモデルに属することができます。例えば、アプリケーションのユーザーが投稿や動画に「コメント」をすることができるとします。多態性の関係を使用すると、1つのcommentsテーブルを使用して投稿と動画の両方のコメントを含めることができます。まず、この関係を構築するために必要なテーブル構造を調べてみましょう:

    posts
id - integer
title - string
body - text

videos
id - integer
title - string
url - string

comments
id - integer
body - text
commentable_id - integer
commentable_type - string

モデル構造

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

    <?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;

class Comment extends Model
{
/**
* Get the parent commentable model (post or video).
*/
public function commentable(): MorphTo
{
return $this->morphTo();
}
}

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphMany;

class Post extends Model
{
/**
* Get all of the post's comments.
*/
public function comments(): MorphMany
{
return $this->morphMany(Comment::class, 'commentable');
}
}

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphMany;

class Video extends Model
{
/**
* Get all of the video's comments.
*/
public function comments(): MorphMany
{
return $this->morphMany(Comment::class, 'commentable');
}
}

関係の取得

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

    use App\Models\Post;

$post = Post::find(1);

foreach ($post->comments as $comment) {
// ...
}

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

    use App\Models\Comment;

$comment = Comment::find(1);

$commentable = $comment->commentable;

Commentモデルのcommentable関係は、コメントの親モデルがPostまたはVideoのいずれかを返します。

1つの中の1つ(多態)

時には、モデルには多くの関連モデルがあるかもしれませんが、関連するモデルの「最新」または「最古」を簡単に取得したい場合があります。たとえば、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');
}
注記

より高度な「1つの中の1つ」の関係を構築することも可能です。詳細については、has one of many documentationを参照してください。

多対多(多態)

テーブル構造

多対多の多態関係は、「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

注記

多対多のポリモーフィック関係に取り組む前に、典型的な多対多の関係に関するドキュメントを読むと役立つかもしれません。

モデル構造

次に、モデル上で関係を定義する準備が整いました。PostVideo モデルの両方に、ベースの Eloquent モデルクラスで提供される morphToMany メソッドを呼び出す tags メソッドが含まれます。

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

    <?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphToMany;

class Post extends Model
{
/**
* Get all of the tags for the post.
*/
public function tags(): MorphToMany
{
return $this->morphToMany(Tag::class, 'taggable');
}
}

関係の逆を定義する

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

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

    <?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphToMany;

class Tag extends Model
{
/**
* Get all of the posts that are assigned this tag.
*/
public function posts(): MorphToMany
{
return $this->morphedByMany(Post::class, 'taggable');
}

/**
* Get all of the videos that are assigned this tag.
*/
public function videos(): MorphToMany
{
return $this->morphedByMany(Video::class, 'taggable');
}
}

関係の取得

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

    use App\Models\Post;

$post = Post::find(1);

foreach ($post->tags as $tag) {
// ...
}

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

    use App\Models\Tag;

$tag = Tag::find(1);

foreach ($tag->posts as $post) {
// ...
}

foreach ($tag->videos as $video) {
// ...
}

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

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

たとえば、モデル名を "type" として使用する代わりに、postvideo のような単純な文字列を使用することができます。これにより、モデルがリネームされても、データベース内の多態 "type" カラムの値は有効のままとなります:

    use Illuminate\Database\Eloquent\Relations\Relation;

Relation::enforceMorphMap([
'post' => 'App\Models\Post',
'video' => 'App\Models\Video',
]);

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

モデルの getMorphClass メソッドを使用して、実行時に指定されたモデルの多態エイリアスを決定することができます。逆に、多態エイリアスに関連付けられた完全修飾クラス名を取得するには、Relation::getMorphedModel メソッドを使用できます:

    use Illuminate\Database\Eloquent\Relations\Relation;

$alias = $post->getMorphClass();

$class = Relation::getMorphedModel($alias);

警告

既存のアプリケーションに "morph map" を追加する場合、データベース内の各多態可能な *_type カラムの値がまだ完全修飾クラスを含んでいる場合は、それを "map" 名に変換する必要があります。

動的リレーション

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

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

    use App\Models\Order;
use App\Models\Customer;

Order::resolveRelationUsing('customer', function (Order $orderModel) {
return $orderModel->belongsTo(Customer::class, 'customer_id');
});

警告

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

リレーションのクエリ

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

例えば、Userモデルに多くの関連するPostモデルがあるブログアプリケーションを想像してください:

    <?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;

class User extends Model
{
/**
* Get all of the posts for the user.
*/
public function posts(): HasMany
{
return $this->hasMany(Post::class);
}
}

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

    use App\Models\User;

$user = User::find(1);

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

リレーションに対してLaravelのクエリビルダのメソッドを使用できるため、使用可能なすべてのメソッドについて学ぶためにクエリビルダのドキュメントを確認してください。

リレーションの後にorWhere句を連結する

上記の例で示されているように、リレーションに追加の制約を追加することができます。ただし、リレーションにorWhere句を連結する際には注意が必要です。なぜなら、orWhere句はリレーションの制約と同じレベルで論理的にグループ化されるからです:

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

上記の例は次のSQLを生成します。or句により、100以上の投票を持つ_どの_投稿でも返すようにクエリが指示されています。クエリは特定のユーザーに制約されなくなります:

select *
from 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)

関係メソッド vs. ダイナミックプロパティ

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

    use App\Models\User;

$user = User::find(1);

foreach ($user->posts as $post) {
// ...
}

ダイナミックな関係プロパティは「遅延読み込み」を行い、実際にアクセスしたときに関係データを読み込みます。このため、開発者はしばしばイーガーローディングを使用して、モデルを読み込んだ後にアクセスされるとわかっている関係を事前に読み込みます。イーガーローディングは、モデルの関係を読み込むために実行する必要がある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条件を付加して関係の存在をクエリしたい場合、whereRelationorWhereRelationwhereMorphRelationorWhereMorphRelationメソッドを使用すると便利です。例えば、未承認のコメントを持つすべての投稿をクエリできます。

    use App\Models\Post;

$posts = Post::whereRelation('comments', 'is_approved', false)->get();

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

モーフ・トゥ・リレーションシップをクエリする

"モーフ・トゥ" リレーションシップの存在をクエリするには、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 メソッドに渡されるクロージャは、第二引数として $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 メソッドに配列を渡すことで、複数の関係の "counts" を追加したり、クエリに追加の制約を加えることができます:

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

関係のカウントとカスタムセレクトステートメント

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

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

その他の集計関数

withCount メソッドに加えて、Eloquent は withMinwithMaxwithAvgwithSumwithExists メソッドを提供しています。これらのメソッドは、結果のモデルに {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();

モーフ・トゥ・リレーションシップで関連モデルの数をカウントする

"モーフ・トゥ" リレーションシップをイーガーロードし、そのリレーションシップによって返されるさまざまなエンティティの関連モデルの数を取得したい場合は、with メソッドを morphTo リレーションシップの morphWithCount メソッドと組み合わせて使用できます。

この例では、Photo モデルと Post モデルが ActivityFeed モデルを作成する可能性があると仮定します。ActivityFeed モデルは、与えられた ActivityFeed インスタンスの親 Photo または Post モデルを取得できる "モーフ・トゥ" リレーションシップであるとします。さらに、Photo モデルは多数の Tag モデルを持ち、Post モデルは多数の Comment モデルを持つと仮定します。

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

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

イーガーローディング

Eloquent リレーションシップをプロパティとしてアクセスすると、関連モデルは "遅延ロード" されます。つまり、プロパティに最初にアクセスするまで、リレーションシップデータは実際にロードされません。ただし、Eloquent は親モデルのクエリ時にリレーションシップを "イーガーロード" することができます。イーガーローディングは "N + 1" クエリ問題を緩和します。N + 1 クエリ問題を説明するために、Book モデルが Author モデルに "belongs to" していると考えてください:

    <?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の追加クエリを実行します。

幸いなことに、この操作を2つのクエリに減らすためにイーガーローディングを使用することができます。クエリを構築する際に、withメソッドを使用してイーガーローディングする関連を指定できます:

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

foreach ($books as $book) {
echo $book->author->name;
}

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

select * from books

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

複数のリレーションシップをイーガーローディング

時には、複数の異なるリレーションシップをイーガーローディングする必要があるかもしれません。その場合は、withメソッドにリレーションシップの配列を渡すだけです:

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

ネストされたイーガーローディング

リレーションシップのリレーションシップをイーガーローディングするには、"ドット"構文を使用できます。例えば、すべての書籍の著者と著者の個人的な連絡先をイーガーローディングする場合は次のようにします:

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

また、複数のネストされたリレーションシップをイーガーローディングする場合は、withメソッドにネストされた配列を指定することもできます。これは、複数のネストされたリレーションシップをイーガーローディングする際に便利です:

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

ネストされたイーガーローディング morphTo リレーションシップ

morphTo リレーションシップをイーガーローディングし、そのリレーションシップによって返されるさまざまなエンティティに対してネストされたリレーションシップをイーガーローディングしたい場合は、withメソッドをmorphToリレーションシップのmorphWithメソッドと組み合わせて使用します。このメソッドを説明するために、次のモデルを考えてみましょう:

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

これらのモデル定義と関係を使用して、ActivityFeedモデルのインスタンスを取得し、すべてのparentableモデルとそれらのネストされた関係を一括読み込みすることができます:

    use Illuminate\Database\Eloquent\Relations\MorphTo;

$activities = ActivityFeed::query()
->with(['parentable' => function (MorphTo $morphTo) {
$morphTo->morphWith([
Event::class => ['calendar'],
Photo::class => ['tags'],
Post::class => ['author'],
]);
}])->get();

特定の列を一括読み込み

取得する関係から常にすべての列が必要とは限りません。そのため、Eloquentでは、取得したい関係の列を指定できます:

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

警告

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

デフォルトでの一括読み込み

モデルを取得するときに常にいくつかの関係を読み込みたい場合があります。これを実現するために、モデルに$withプロパティを定義できます:

    <?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Book extends Model
{
/**
* The relationships that should always be loaded.
*
* @var array
*/
protected $with = ['author'];

/**
* Get the author that wrote the book.
*/
public function author(): BelongsTo
{
return $this->belongsTo(Author::class);
}

/**
* Get the genre of the book.
*/
public function genre(): BelongsTo
{
return $this->belongsTo(Genre::class);
}
}

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

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

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

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

一括読み込みの制約

関係を一括読み込みしたいが、一括読み込みクエリに追加のクエリ条件を指定したい場合があります。これは、withメソッドに関係の配列を渡すことで達成できます。配列のキーは関係名であり、配列の値は追加の制約を一括読み込みクエリに追加するクロージャです:

    use App\Models\User;
use Illuminate\Contracts\Database\Eloquent\Builder;

$users = User::with(['posts' => function (Builder $query) {
$query->where('title', 'like', '%code%');
}])->get();

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

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

morphTo 関係のイーガーローディングの制約

morphTo 関係をイーガーローディングしている場合、Eloquent は各種類の関連モデルを取得するために複数のクエリを実行します。これらのクエリに追加の制約を追加するには、MorphTo 関係の constrain メソッドを使用できます:

    use Illuminate\Database\Eloquent\Relations\MorphTo;

$comments = Comment::with(['commentable' => function (MorphTo $morphTo) {
$morphTo->constrain([
Post::class => function ($query) {
$query->whereNull('hidden_at');
},
Video::class => function ($query) {
$query->where('type', 'educational');
},
]);
}])->get();

この例では、Eloquent は非表示にされていない投稿と type 値が "educational" であるビデオのみをイーガーロードします。

関係の存在を制約するイーガーロード

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

    use App\Models\User;

$users = User::withWhereHas('posts', function ($query) {
$query->where('featured', true);
})->get();

遅延イーガーローディング

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

    use App\Models\Book;

$books = Book::all();

if ($someCondition) {
$books->load('author', 'publisher');
}

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

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

すでにロードされていない場合にのみ関係をロードするには、loadMissing メソッドを使用します:

    $book->loadMissing('author');

ネストされた遅延イーガーローディングと morphTo

morphTo 関係をイーガーロードし、その関係によって返されるさまざまなエンティティにネストされた関係をロードする場合は、loadMorph メソッドを使用できます。

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

    <?php

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;

class ActivityFeed extends Model
{
/**
* Get the parent of the activity feed record.
*/
public function parentable(): MorphTo
{
return $this->morphTo();
}
}

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

これらのモデル定義と関係を使用して、ActivityFeed モデルインスタンスを取得し、すべての parentable モデルとそれらのネストされた関係を一括読み込みすることができます:

    $activities = ActivityFeed::with('parentable')
->get()
->loadMorph('parentable', [
Event::class => ['calendar'],
Photo::class => ['tags'],
Post::class => ['author'],
]);

遅延読み込みの防止

前述のように、イーガーローディング関係を使用すると、アプリケーションに重要なパフォーマンス上の利点が得られることがあります。したがって、必要であれば、Laravel に常に遅延読み込みを防止するよう指示することができます。これを実現するには、ベースの Eloquent モデルクラスが提供する preventLazyLoading メソッドを呼び出すことができます。通常、このメソッドは、アプリケーションの AppServiceProvider クラスの boot メソッド内で呼び出すべきです。

preventLazyLoading メソッドは、遅延読み込みを防止するかどうかを示すオプションのブール引数を受け入れます。たとえば、本番環境では遅延読み込みを無効にしたい場合は、本番コードに遅延読み込みの関係が誤って含まれていても、本番環境が通常通り機能するようにすることができます:

use Illuminate\Database\Eloquent\Model;

/**
* Bootstrap any application services.
*/
public function boot(): void
{
Model::preventLazyLoading(! $this->app->isProduction());
}

遅延読み込みを防止した後、Eloquent はアプリケーションが遅延読み込みを試みると Illuminate\Database\LazyLoadingViolationException 例外をスローします。

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

Model::handleLazyLoadingViolationUsing(function (Model $model, string $relation) {
$class = $model::class;

info("Attempted to lazy load [{$relation}] on model [{$class}].");
});

関連するモデルの挿入と更新

save メソッド

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

    use App\Models\Comment;
use App\Models\Post;

$comment = new Comment(['message' => 'A new comment.']);

$post = Post::find(1);

$post->comments()->save($comment);

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

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

    $post = Post::find(1);

$post->comments()->saveMany([
new Comment(['message' => 'A new comment.']),
new Comment(['message' => 'Another new comment.']),
]);

save メソッドと saveMany メソッドは、指定されたモデルインスタンスを永続化しますが、既に親モデルに読み込まれているインメモリ関連に新しく永続化されたモデルを追加しません。save メソッドまたは saveMany メソッドを使用した後に関連にアクセスする予定がある場合は、モデルとその関連を再読み込むために refresh メソッドを使用することを検討してください:

    $post->comments()->save($comment);

$post->refresh();

// All comments, including the newly saved comment...
$post->comments;

モデルと関連の再帰的な保存

モデルとその関連をすべて保存したい場合は、push メソッドを使用できます。この例では、Post モデルだけでなく、そのコメントとコメントの著者も保存されます:

    $post = Post::find(1);

$post->comments[0]->message = 'Message';
$post->comments[0]->author->name = 'Author Name';

$post->push();

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

    $post->pushQuietly();

create メソッド

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

    use App\Models\Post;

$post = Post::find(1);

$comment = $post->comments()->create([
'message' => 'A new comment.',
]);

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

    $post = Post::find(1);

$post->comments()->createMany([
['message' => 'A new comment.'],
['message' => 'Another new comment.'],
]);

createQuietly および createManyQuietly メソッドを使用して、イベントをディスパッチせずにモデルを作成することもできます:

    $user = User::find(1);

$user->posts()->createQuietly([
'title' => 'Post title.',
]);

$user->posts()->createManyQuietly([
['title' => 'First post.'],
['title' => 'Second post.'],
]);

また、findOrNewfirstOrNewfirstOrCreate、および updateOrCreate メソッドを使用して、関連するモデルの作成と更新を行うこともできます。

注記

create メソッドを使用する前に、一括代入のドキュメントを確認してください。

Belongs To リレーションシップ

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

    use App\Models\Account;

$account = Account::find(10);

$user->account()->associate($account);

$user->save();

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

    $user->account()->dissociate();

$user->save();

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

アタッチ / デタッチ

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

    use App\Models\User;

$user = User::find(1);

$user->roles()->attach($roleId);

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

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

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

    // Detach a single role from the user...
$user->roles()->detach($roleId);

// Detach all roles from the user...
$user->roles()->detach();

便宜上、attachdetach は ID の配列も入力として受け入れます:

    $user = User::find(1);

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

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

関連の同期

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

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

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

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

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

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

指定された配列に存在しない既存の ID を削除したくない場合は、syncWithoutDetaching メソッドを使用できます:

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

関連の切り替え

多対多の関連には toggle メソッドも提供されており、指定された関連モデルの ID の添付状態を「切り替え」ます。指定された 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 の関係を定義している場合、例えば CommentPost に属している場合、子モデルが更新されたときに親のタイムスタンプを更新すると便利な場合があります。

例えば、Comment モデルが更新されたとき、親である Postupdated_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 メソッドを使用して更新された場合にのみ、親モデルのタイムスタンプが更新されます。