イントロダクション
多くの場合、データベーステーブルは相互に関連(リレーション)しています。たとえば、ブログ投稿に多くのコメントが含まれている場合や、注文がそれを行ったユーザーと関連している場合などです。Eloquentはこれらの関係の管理と操作を容易にし、さまざまな一般的なリレーションをサポートします。
リレーションの定義
Eloquentリレーションは、Eloquentモデルクラスのメソッドとして定義します。リレーションは強力なクエリビルダとしても機能するため、リレーションをメソッドとして定義すると、強力なメソッドチェーンとクエリ機能が使用できます。たとえば以下のように、posts
リレーションに追加のクエリ制約をチェーンできます。
$user->posts()->where('active', 1)->get();
ただし、リレーションの使用について深く掘り下げる前に、Eloquentがサポートしている各タイプのリレーションを定義する方法を学びましょう。
1対1
1対1の関係はもっとも基本的なタイプのデータベースリレーションです。たとえば、User
モデルが1つのPhone
モデルに関連付けられている場合があります。この関係を定義するために、User
モデルにphone
メソッドを配置します。phone
メソッドはhasOne
メソッドを呼び出し、その結果を返す必要があります。hasOne
メソッドは、モデルのIlluminate\Database\Eloquent\Model
基本クラスを介してモデルで使用可能です。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasOne;
class User extends Model
{
/**
* ユーザーに関連している電話の取得
*/
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
プロパティ以外の主キー値を使用する場合は、3番目の引数をhasOne
メソッドに渡してください。
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
{
/**
* この電話を所有しているユーザーの取得
*/
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番目の引数として渡してください。
/**
* この電話を所有しているユーザーの取得
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'foreign_key');
}
親モデルが主キーとしてid
を使用しない場合、または別のカラムを使用して関連モデルを検索する場合は、belongsTo
メソッドへ親テーブルのカスタムキーを指定する3番目の引数を渡してください。
/**
* この電話を所有しているユーザーの取得
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'foreign_key', 'owner_key');
}
1対多
1対多の関係は、単一のモデルが1つ以上の子モデルの親である関係を定義するために使用されます。たとえば、ブログ投稿はいくつもコメントを持つ場合があります。他のすべてのEloquent関係と同様に、1対多の関係はEloquentモデルでメソッドを定義することにより定義します。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Post extends Model
{
/**
* ブログポストのコメントを取得
*/
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');
1対多(逆)/所属
投稿のすべてのコメントへアクセスできるようになったので、今度はコメントからその親投稿へアクセスできるようにする関係を定義しましょう。hasMany
関係の逆を定義するには、belongsTo
メソッドを呼び出す子モデルで関係メソッドを定義します。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Comment extends Model
{
/**
* コメントを所有している投稿を取得
*/
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番目の引数へ指定できます。
/**
* コメントを所有している投稿を取得
*/
public function post(): BelongsTo
{
return $this->belongsTo(Post::class, 'foreign_key');
}
親モデルが主キーとしてid
を使用していない場合、または別のカラムを使用して関連モデルを検索する場合は、belongsTo
メソッドへ親テーブルのカスタムキーを指定する3番目の引数を渡せます。
/**
* コメントを所有している投稿を取得
*/
public function post(): BelongsTo
{
return $this->belongsTo(Post::class, 'foreign_key', 'owner_key');
}
デフォルトモデル
belongsTo
、hasOne
、hasOneThrough
、morphOne
リレーションを使用する場合、指定する関係がnull
の場合に返すデフォルトモデルを定義できます。このパターンは、Nullオブジェクトパターンと呼ばれることが多く、コード内の条件付きチェックを省略するのに役立ちます。以下の例では、Post
モデルにユーザーがアタッチされていない場合、user
リレーションは空のApp\Models\User
モデルを返します。
/**
* 投稿の作成者を取得
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class)->withDefault();
}
デフォルトモデルに属性を設定するには、配列またはクロージャをwithDefault
メソッドに渡します。
/**
* 投稿の作成者を取得
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class)->withDefault([
'name' => 'Guest Author',
]);
}
/**
* 投稿の作成者を取得
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class)->withDefault(function (User $user, Post $post) {
$user->name = 'Guest Author';
});
}
Belongs toリレーションのクエリ
"belongs
to"リレーションの子モデルをクエリする場合は、対応するEloquentモデルを取得するwhere
句を手作業で構築してください。
use App\Models\Post;
$posts = Post::where('user_id', $user->id)->get();
しかし、whereBelongsTo
メソッドを使う方が便利でしょう。このメソッドは、与えられたモデルに対して適切なリレーションと外部キーを自動的に決定します。
$posts = Post::whereBelongsTo($user)->get();
また、whereBelongsTo
メソッドへ、コレクションインスタンスも指定可能です。その場合、Laravelはコレクション内から、親モデルに所属する全モデルを取得します。
$users = User::where('vip', true)->get();
$posts = Post::whereBelongsTo($users)->get();
デフォルトでLaravelはモデルのクラス名に基づいて、与えられたモデルに関連するリレーションを決定しますが、リレーション名をwhereBelongsTo
メソッドの第2引数に渡すことで、手作業で指定できます。
$posts = Post::whereBelongsTo($user, 'author')->get();
Has One Of Many
あるモデルが多くの関連モデルを持つことがありますが、そのリレーションにおける「最新」または「最も古い」関連モデルを簡単に取得したい場合があります。たとえば、User
モデルは多くのOrder
モデルと関連しており、ユーザーが発注した最新の注文を操作する便利な方法を定義したいとします。これの実現には、hasOne
のリレーションタイプとofMany
メソッドを組み合わせて使います。
/**
* ユーザーの最新注文の取得
*/
public function latestOrder(): HasOne
{
return $this->hasOne(Order::class)->latestOfMany();
}
同様に、あるリレーションの「最も古い」、つまり最初の関連モデルを取得するメソッドを定義することもできます。
/**
* ユーザーの最も古い注文を取得
*/
public function oldestOrder(): HasOne
{
return $this->hasOne(Order::class)->oldestOfMany();
}
latestOfMany
とoldestOfMany
メソッドはデフォルトで、ソート可能なモデルの主キーに基づいて、最新または最古の関連モデルを取得します。しかし、時には、別のソート基準を使って、より大きなリレーションから単一モデルを取得したい場合も起きるでしょう。
例えば、ofMany
メソッドを使って、ユーザーの最も高い注文を取得することができます。ofMany
メソッドは、ソート可能なカラムを第一引数として受け取り、関連するモデルを検索する際にどの集約関数(min
またはmax
)を適用するかを指定します。
/**
* ユーザーの一番高い注文の取得
*/
public function largestOrder(): HasOne
{
return $this->hasOne(Order::class)->ofMany('price', 'max');
}
Warning!! PostgreSQLはUUID列に対する
MAX
関数の実行をサポートしていないため、今のところPostgreSQLのUUIDカラムと組み合わせて1対多の関係を使用できません。
"Many"リレーションをHas Oneリレーションへ変換する
latestOfMany
、oldestOfMany
、ofMany
メソッドを使用して単一のモデルを取得するとき、多くの場合、同じモデルに対し予め"has
many"リレーションが定義済みです。便利なように、Laravelはリレーションでone
メソッドを呼び出すことにより、こうしたリレーションを"has
one"リレーションへ簡単に変換できます。
/**
* ユーザーの注文の取得
*/
public function orders(): HasMany
{
return $this->hasMany(Order::class);
}
/**
* ユーザーの一番価格の高い注文の取得
*/
public function largestOrder(): HasOne
{
return $this->orders()->one()->ofMany('price', 'max');
}
上級Has One Of Manyリレーション
より高度な"has one of
many"リレーションを構築することも可能です。例えば、Product
モデルは、新しい価格が公開された後でもシステム内で保持している、多くの関連Price
モデルを持つことができます。さらに、製品の新しい価格データは、published_at
カラムにより、将来の日付で有効にするように事前に予約できることにしましょう。
要約すると、公開日が未来ではない最新の価格を取得する必要があるということです。さらに、2つの価格の公開日が同じであれば、より大きいIDを持つ価格を優先します。これを実現するには、最新の価格を決定するソート可能なカラムを含む配列を
ofMany
メソッドに渡す必要があります。さらに、ofMany
メソッドの第2引数には、クロージャが渡されます。このクロージャは、リレーションクエリに追加の発行日制約を追加する役割を果たします。
/**
* 製品の現在価格を取得
*/
public function currentPricing(): HasOne
{
return $this->hasOne(Price::class)->ofMany([
'published_at' => 'max',
'id' => 'max',
], function (Builder $query) {
$query->where('published_at', '<', now());
});
}
Has One Through
"has-one-through"リレーションは、別のモデルとの1対1の関係を定義します。ただし、この関係は、3番目のモデルを仲介(through)に使うことで、宣言するモデルと別のモデルの1インスタンスとマッチさせることを意味します。
たとえば、自動車修理工場のアプリケーションでは、各「整備士(Mechanic
)」モデルを1つの「自動車(Car
)」モデルに関連付け、各「自動車」モデルを1つの「所有者(Owner
)」モデルに関連付けることができます。整備士と所有者はデータベース内で直接の関係はありませんが、整備士は「車」モデルを介して所有者にアクセスできます。この関係を定義するために必要なテーブルを見てみましょう。
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
{
/**
* 車の所有者を取得
*/
public function carOwner(): HasOneThrough
{
return $this->hasOneThrough(Owner::class, Car::class);
}
}
hasOneThrough
メソッドに渡す、最初の引数はアクセスする最終モデルの名前であり、2番目の引数は中間モデルの名前です。
もしくは、関連するリレーションがすべてのモデルで既に定義済みの場合、それらのリレーション名を指定し、through
メソッドを呼び出すことで、"has-one-through"リレーションをスムーズに定義できます。例えば、Mechanic
モデルがcars
リレーションを持ち、Car
モデルがowner
リレーションを持つ場合、メカニックとオーナーを結ぶ"has-one-through"リレーションを次のように定義可能です。
// 文字列ベース記法
return $this->through('cars')->has('owner');
// 動的記法
return $this->throughCars()->hasOwner();
キーの規約
リレーションのクエリ実行時は、一般的にEloquent外部キー規約を使用します。リレーションのキーをカスタマイズする場合は、それらを3番目と4番目の引数としてhasOneThrough
メソッドに渡してください。3番目の引数は、中間モデルの外部キーの名前です。4番目の引数は、最終モデルの外部キーの名前です。5番目の引数はローカルキーであり、6番目の引数は中間モデルのローカルキーです。
class Mechanic extends Model
{
/**
* 車の所有者を取得
*/
public function carOwner(): HasOneThrough
{
return $this->hasOneThrough(
Owner::class,
Car::class,
'mechanic_id', // carsテーブルの外部キー
'car_id', // ownersテーブルの外部キー
'id', // mechanicsテーブルのローカルキー
'id' // carsテーブルのローカルキー
);
}
}
あるいは、先に説明したように、関わるすべてのモデルで関連するリレーションが既に定義済みの場合、それらのリレーション名を指定し、through
メソッドを呼び出すことで、"has-one-through"リレーションを流れるように定義することもできます。この方法は、既存のリレーションで定義したキー関係定義を再利用できるという利点があります。
// 文字列ベース記法
return $this->through('cars')->has('owner');
// 動的記法
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
{
/**
* プロジェクトのすべてのデプロイメントを取得
*/
public function deployments(): HasManyThrough
{
return $this->hasManyThrough(Deployment::class, Environment::class);
}
}
hasManyThrough
メソッドへ渡たす、最初の引数はアクセスする最終モデルの名前であり、2番目の引数は中間モデルの名前です。
また、既にすべてのモデルで関連リレーションが定義済みの場合は、それらのリレーション名を指定し、through
メソッドを呼び出して、"has-many-through"リレーションシップをスムーズに定義できます。例えば、Project
モデルがenvironments
リレーションを持ち、Environment
モデルがdeployments
リレーションを持つ場合、プロジェクトとデプロイメントを結ぶ"has-many-through"リーションを以下のように定義できます。
// 文字列ベース記法
return $this->through('environments')->has('deployments');
// 動的記法
return $this->throughEnvironments()->hasDeployments();
Deployment
モデルのテーブルはproject_id
カラムを含んでいませんが、hasManyThrough
リレーションは、$project->deployments
を介してプロジェクトのデプロイメントへのアクセスを提供します。これらのモデルを取得するために、Eloquentは中間のEnvironment
モデルのテーブルのproject_id
カラムを検査します。関連した環境IDを見つけ、それらを使用してDeployment
モデルのテーブルをクエリします。
キーの規約
リレーションのクエリを実行するときは、Eloquent外部キー規約を一般的に使用します。リレーションのキーをカスタマイズする場合は、それらを3番目と4番目の引数としてhasManyThrough
メソッドに渡たしてください。3番目の引数は、中間モデルの外部キーの名前です。4番目の引数は、最終モデルの外部キーの名前です。5番目の引数はローカルキーであり、6番目の引数は中間モデルのローカルキーです。
class Project extends Model
{
public function deployments(): HasManyThrough
{
return $this->hasManyThrough(
Deployment::class,
Environment::class,
'project_id', // environmentsテーブルの外部キー
'environment_id', // deploymentsテーブルの外部キー
'id', // projectsテーブルのローカルキー
'id' // environmentsテーブルのローカルキー
);
}
}
あるいは、先に説明したように、関連リレーションがリレーションに関わるすべてのモデルで、定義済みの場合、それらのリレーション名を指定し、through
メソッドを呼び出すことで、"has-many-through"リレーションをスムーズに定義することも可能です。この方法は、既存のリレーションに定義されているキー関係定義を再利用できるという利点があります。
// 文字列ベース記法
return $this->through('environments')->has('deployments');
// 動的記法
return $this->throughEnvironments()->hasDeployments();
多対多リレーション
多対多の関係は、hasOne
およびhasMany
の関係よりも少し複雑です。多対多の関係の一例は、多くの役割を持つユーザーであり、役割はアプリケーション内の他のユーザーと共有している場合です。たとえば、あるユーザーに「作成者(Author)」と「編集者(Editor)」の役割を割り当てることができます。ただし、これらの役割は他のユーザーにも割り当てる場合があります。したがって、あるユーザーには多くの役割があり、ある役割には多くのユーザーがいます。
テーブル構造
この関係を定義するには、users
、roles
、およびrole_user
の3つのデータベーステーブルが必要です。role_user
テーブルは、関連モデル名のアルファベット順を由来としており、user_id
カラムとrole_id
カラムを含みます。このテーブルは、ユーザーと役割をリンクする中間テーブルとして使用します。
役割は多くのユーザーに属することができるため、単にuser_id
カラムをroles
テーブルに配置することはできません。そうすることは、役割が1人のユーザーにのみ属することができることを意味します。複数のユーザーに割り当てられている役割(role
)をサポートするには、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
{
/**
* このユーザーに属する役割
*/
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つの関連するモデル名をアルファベット順に結合します。ただし、この規約は自由に上書きできます。その場合、2番目の引数をbelongsToMany
メソッドに渡します。
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
{
/**
* この役割に属するユーザー
*/
public function users(): BelongsToMany
{
return $this->belongsToMany(User::class);
}
}
ご覧のとおり。関係は、App\Models\User
モデルを参照することを除いて、対応するUser
モデルとまったく同じように定義されています。belongsToMany
メソッドを再利用しているため、多対多の関係の「逆」を定義するときにも、通常のテーブルとキーのカスタマイズオプションをすべて使用できます。
中間テーブルカラムの取得
すでに学んだように、多対多の関係を扱うには、中間テーブルの存在が必要です。Eloquentは、このテーブルを操作するのに役立つ手段をいくつか提供しています。たとえば、User
モデルに関連するRole
モデルがたくさんあるとしましょう。この関係にアクセスした後、モデルのpivot
属性を使用して中間テーブルにアクセスできます。
use App\Models\User;
$user = User::find(1);
foreach ($user->roles as $role) {
echo $role->pivot->created_at;
}
取得する各Role
モデルには自動的にpivot
属性が割り当てられることに注意してください。この属性には、中間テーブルを表すモデルが含まれています。
デフォルトでは、モデルキーのみがpivot
モデルに存在します。中間テーブルに追加の属性を含めている場合は、関係を定義するときにそうした属性を指定する必要があります。
return $this->belongsToMany(Role::class)->withPivot('active', 'created_by');
中間テーブルへEloquentが自動的に維持するcreated_at
およびupdated_at
タイムスタンプを持たせたい場合は、関係を定義するときにwithTimestamps
メソッドを呼び出します。
return $this->belongsToMany(Role::class)->withTimestamps();
Warning!! 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;
}
中間テーブルのカラムを使った関係のフィルタリング
リレーションを定義するときに、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\Relationships\Pivot
クラス、カスタムのポリモーフィック多対多ピボットモデルはIlluminate\Database\Eloquent\Relationships\MorphPivot
クラスを拡張する必要があります。たとえば、カスタムのRoleUser
ピボットモデルを使用するRole
モデルを定義してみましょう。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class Role extends Model
{
/**
* この役割に属するユーザー
*/
public function users(): BelongsToMany
{
return $this->belongsToMany(User::class)->using(RoleUser::class);
}
}
RoleUser
モデルを定義するときは、Illuminate\Database\Eloquent\Relationships\Pivot
クラスを拡張する必要があります。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\Pivot;
class RoleUser extends Pivot
{
// ...
}
Warning!! ピボットモデルは
SoftDeletes
トレイトを使用できません。ピボットレコードをソフトデリートする必要がある場合は、ピボットモデルを実際のEloquentモデルに変換することを検討してください。
カスタムピボットモデルと増分ID
カスタムピボットモデルを使用する多対多の関係を定義し、そのピボットモデルに自動増分の主キーがある場合は、カスタムピボットモデルクラスでincrementing
プロパティを確実にtrue
に設定してください。
/**
* IDの自動増分を指定する
*
* @var bool
*/
public $incrementing = true;
ポリモーフィックリレーション
ポリモーフィックリレーションにより、子モデルは単一の関連を使用して複数タイプのモデルに属せます。たとえば、ユーザーがブログの投稿やビデオを共有できるようにするアプリケーションを構築しているとします。このようなアプリケーションで、Comment
モデルはPost
モデルとVideo
モデルの両方に属する可能性があります。
1対1(ポリモーフィック)
テーブル構造
1対1のポリモーフィックリレーションは、一般的な1対1の関係に似ています。ただし、子モデルは単一の関連付けを使用して複数タイプのモデルへ所属できます。たとえば、ブログのPost
とUser
は、Image
モデルとポリモーフィックな関係を共有することがあります。1対1のポリモーフィックな関係を使用すると、投稿やユーザーに関連するひとつの画像の単一のテーブルを作成できます。まず、テーブルの構造を調べてみましょう。
posts
id - integer
name - string
users
id - integer
name - string
images
id - integer
url - string
imageable_id - integer
imageable_type - string
images
テーブルのimageable_id
カラムとimageable_type
カラムに注意してください。imageable_id
カラムには投稿またはユーザーのID値が含まれ、imageable_type
カラムには親モデルのクラス名が含まれます。imageable_type
カラムは、imageable
リレーションへのアクセス時に返す親モデルの「タイプ」を決めるため、Eloquentが使用します。この場合、カラムには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
{
/**
* 親のimageableなモデル(ユーザー/投稿)の取得
*/
public function imageable(): MorphTo
{
return $this->morphTo();
}
}
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphOne;
class Post extends Model
{
/**
* 投稿の画像を取得
*/
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
{
/**
* ユーザーの画像を取得
*/
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__
定数を使用できます。
/**
* 画像が属するモデルを取得
*/
public function imageable(): MorphTo
{
return $this->morphTo(__FUNCTION__, 'imageable_type', 'imageable_id');
}
1対多(ポリモーフィック)
テーブル構造
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
{
/**
* commentableな親モデルの取得(投稿かビデオ)
*/
public function commentable(): MorphTo
{
return $this->morphTo();
}
}
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphMany;
class Post extends Model
{
/**
* このポストの全コメント取得
*/
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
{
/**
* このビデオの全コメント取得
*/
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
インスタンスのいずれかを返します。
One Of Many(ポリモーフィック)
あるモデルが多くの関連モデルを持つことがありますが、そのリレーションの「最新」または「最も古い」関連モデルを簡単に取得したい場合があります。例えば、User
モデルは多くのImage
モデルと関連しており、ユーザーがアップロードした最新の画像を操作する便利な方法を定義したいとします。このような場合には、morphOne
というリレーションタイプとofMany
メソッドを組み合わせることで実現できます。
/**
* 最新のイメージを取得
*/
public function latestImage(): MorphOne
{
return $this->morphOne(Image::class, 'imageable')->latestOfMany();
}
同様に、あるリレーションの「最も古い」、つまり最初の関連モデルを取得するメソッドを定義することもできます。
/**
* ユーザーの最も古い画像を取得
*/
public function oldestImage(): MorphOne
{
return $this->morphOne(Image::class, 'imageable')->oldestOfMany();
}
latestOfMany
とoldestOfMany
メソッドはデフォルトで、ソート可能なモデルの主キーに基づいて、最新または最古の関連モデルを取得します。しかし、時には、別のソート基準を使って、より大きなリレーションから単一モデルを取得したい場合も起きるでしょう。
例えば、ofMany
メソッドを使って、ユーザーが最も"Like"した画像を取得できます。ofMany
メソッドは、ソート可能なカラムを第一引数に取り、関連するモデルを検索する際にどの集約関数(min
またはmax
)を適用するかを指定します。
/**
* ユーザーの最も人気のある画像を取得
*/
public function bestImage(): MorphOne
{
return $this->morphOne(Image::class, 'imageable')->ofMany('likes', 'max');
}
Note: より高度な「一対多」リレーションを構築することも可能です。詳しくは、has one of manyのドキュメントを参照してください。
多対多(ポリモーフィック)
テーブル構造
多対多のポリモーフィックリレーションは、"morph one"と"morph
manyリレーションよりも少し複雑です。たとえば、Post
モデルとVideo
モデルは、Tag
モデルとポリモーフィックな関係を共有できます。この状況で多対多のポリモーフィックリレーションを使用すると、アプリケーションで一意のタグのテーブルを一つ用意するだけで、投稿やビデオにそうしたタグを関係づけられます。まず、この関係を構築するために必要なテーブル構造を見てみましょう。
posts
id - integer
name - string
videos
id - integer
name - string
tags
id - integer
name - string
taggables
tag_id - integer
taggable_id - integer
taggable_type - string
Note: ポリモーフィックな多対多のリレーションへに飛び込む前に、典型的な多対多の関係に関するドキュメントを読むとよいでしょう。
モデル構造
これで、モデルの関係を定義する準備ができました。Post
モデルとVideo
モデルの両方に、基本のEloquentモデルクラスによって提供されるmorphToMany
メソッドを呼び出すtags
メソッドを定義します。
morphToMany
メソッドは、関連モデルの名前と「リレーション名」を引数に取ります。中間テーブル名へ割り当てた名前とそれが持つキーに基づき、"taggable"と言う名前のリレーションで参照します。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
class Post extends Model
{
/**
* 投稿のすべてのタグを取得
*/
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
{
/**
* このタグを割り当てているすべての投稿を取得
*/
public function posts(): MorphToMany
{
return $this->morphedByMany(Post::class, 'taggable');
}
/**
* このタグを割り当てているすべての動画を取得
*/
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
モデルに属する可能性がある前記の1対多関係の例では、デフォルトのcommentable_type
はApp\Models\Post
かApp\Models\Video
のいずれかになります。ただし、これらの値をアプリケーションの内部構造から切り離したい場合も起きるでしょう。
たとえば、モデル名を"type"として使用する代わりに、post
やvideo
などの単純な文字列を使用したい場合もあります。これにより、モデル名が変更されても、データベース内のポリモーフィックな「タイプ」カラムの値は有効なままになります。
use Illuminate\Database\Eloquent\Relations\Relation;
Relation::enforceMorphMap([
'post' => 'App\Models\Post',
'video' => 'App\Models\Video',
]);
enforceMorphMap
メソッドは、App\Providers\AppServiceProvider
クラスのboot
メソッドで呼び出すか、お好みであれば別のサービスプロバイダを作成してください。
モデルのgetMorphClass
メソッドを使用して、実行時に指定したモデルのポリモーフィックのエイリアスを取得できます。逆に、Relation::getMorphedModel
メソッドを使用して、ポリモーフィックのエイリアスへ関連付けた完全修飾クラス名を取得もできます。
use Illuminate\Database\Eloquent\Relations\Relation;
$alias = $post->getMorphClass();
$class = Relation::getMorphedModel($alias);
Warning!! 既存のアプリケーションに「ポリモーフィックのマップ」を適用する場合、ポリモーフィックリレーションで使用していたそれまでの、完全修飾クラスを含むデータベース内の
*_type
カラム値はすべて、「マップ」名に変換する必要が起きます。
動的リレーション
resolveRelationUsing
メソッドを使用して、実行時にEloquentモデル間のリレーションを定義できます。通常のアプリケーション開発には推奨しませんが、Laravelパッケージの開発時には役立つでしょう。
resolveRelationUsing
メソッドは、最初の引数に付けたいリレーション名を引数に取ります。メソッドの2番目の引数は、モデルインスタンスを引数に取り、有効なEloquentリレーションの定義を返すクロージャです。通常、サービスプロバイダのbootメソッド内で動的リレーションを設定する必要があります。
use App\Models\Order;
use App\Models\Customer;
Order::resolveRelationUsing('customer', function (Order $orderModel) {
return $orderModel->belongsTo(Customer::class, 'customer_id');
});
Warning!! 動的リレーションを定義するときは、常に明示的にキー名引数を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
{
/**
* ユーザーのすべての投稿を取得
*/
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) {
// ...
}
動的リレーションプロパティは「遅延読み込み」を実行します。つまり、実際にアクセスしたときにのみリレーションデータが読み込まれます。このため、開発者はEagerロードを使用して、モデルのロード後にアクセスすることがわかっているリレーションを事前ロードすることがよくあります。Eagerロードにより、モデルのリレーションを読み込むために実行する必要のあるSQLクエリが大幅に削減されます。
リレーションの存在のクエリ
モデルレコードを取得するときは、リレーションのありなしに基づいて結果を制約したい場合もあるでしょう。たとえば、コメントが少なくとも1つあるすべてのブログ投稿を取得するとします。これを行うには、関係の名前をhas
メソッドとorHas
メソッドに渡すことができます。
use App\Models\Post;
// コメントが少なくとも1つあるすべての投稿を取得
$posts = Post::has('comments')->get();
演算子とカウント数を指定して、クエリをさらにカスタマイズすることもできます。
// コメントが3つ以上あるすべての投稿を取得
$posts = Post::has('comments', '>=', 3)->get();
ネストしたhas
ステートメントは、「ドット」表記を使用して作成できます。たとえば、少なくとも1つの画像を持つコメントが、少なくとも1つあるすべての投稿を取得できます。
// 画像付きのコメントが少なくとも1つある投稿を取得
$posts = Post::has('comments.images')->get();
さらに強力な機能が必要な場合は、whereHas
メソッドとorWhereHas
メソッドを使用して、コメントの内容の検査など、has
クエリに追加のクエリ制約を定義できます。
use Illuminate\Database\Eloquent\Builder;
// code%と似ている単語を含むコメントが少なくとも1つある投稿を取得
$posts = Post::whereHas('comments', function (Builder $query) {
$query->where('content', 'like', 'code%');
})->get();
// code%と似ている単語を含むコメントが10件以上ある投稿を取得
$posts = Post::whereHas('comments', function (Builder $query) {
$query->where('content', 'like', 'code%');
}, '>=', 10)->get();
Warning!! Eloquentは現在、データベース間をまたぐリレーションの存在のクエリをサポートしていません。リレーションは同じデータベース内に存在する必要があります。
インライン関係存在クエリ
リレーションのクエリに付加する単純な1つの条件で、リレーションの存在をクエリしたい場合は、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;
// code%と似たタイトルの投稿や動画へ関連付けられたコメントを取得
$comments = Comment::whereHasMorph(
'commentable',
[Post::class, Video::class],
function (Builder $query) {
$query->where('title', 'like', 'code%');
}
)->get();
// 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
メソッドは結果のモデル上へ{リレーション}_count
属性を作ります。
use App\Models\Post;
$posts = Post::withCount('comments')->get();
foreach ($posts as $post) {
echo $post->comments_count;
}
配列をwithCount
メソッドに渡すことで、複数のリレーションの「カウント」を追加したり、クエリに制約を追加したりできます。
use Illuminate\Database\Eloquent\Builder;
$posts = Post::withCount(['votes', 'comments' => function (Builder $query) {
$query->where('content', 'like', 'code%');
}])->get();
echo $posts[0]->votes_count;
echo $posts[0]->comments_count;
リレーションカウントの結果に別名を付け、同じリレーションの複数の集計もできます。
use Illuminate\Database\Eloquent\Builder;
$posts = Post::withCount([
'comments',
'comments as pending_comments_count' => function (Builder $query) {
$query->where('approved', false);
},
])->get();
echo $posts[0]->comments_count;
echo $posts[0]->pending_comments_count;
遅延カウントロード
loadCount
メソッドを使用すると、親モデルがすでに取得された後にリレーションのカウントをロードできます。
$book = Book::first();
$book->loadCount('genres');
カウントクエリへクエリ制約を追加設定する必要がある場合は、カウントしたいリレーションをキーにした配列を渡すことができます。配列の値は、クエリビルダインスタンスを受け取るクロージャである必要があります。
$book->loadCount(['reviews' => function (Builder $query) {
$query->where('rating', 5);
}])
リレーションのカウントとカスタムSELECT文
withCount
をselect
ステートメントと組み合わせる場合は、select
メソッドの後にwithCount
を呼び出してください。
$posts = Post::select(['title', 'body'])
->withCount('comments')
->get();
その他の集計関数
Eloquentは、withCount
メソッドに加えて、withMin
、withMax
、withAvg
、withSum
、withExists
メソッドも提供しています。これらのメソッドは、結果のモデルに{リレーション}_{集計機能}_{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"リレーションとそのリレーションが返す可能性のあるさまざまなエンティティの関連モデル数をEagerロードしたい場合は、with
メソッドをmorphTo
リレーションのmorphWithCount
メソッドと組み合わせて使用します。
今回の例では、Photo
モデルとPost
モデルがActivityFeed
モデルを作成していると想定します。ActivityFeed
モデルは、特定のActivityFeed
インスタンスの親Photo
またはPost
モデルを取得できるようにするparentable
という名前の"morph
to"リレーションを定義すると想定します。さらに、Photo
モデルには「多くの(have
many)」Tag
モデルがあり、Post
モデルも「多くの(have
many)」Comment
モデルがあると仮定しましょう。
では、ActivityFeed
インスタンスを取得し、各ActivityFeed
インスタンスのparentable
親モデルをEagerロードしましょう。さらに、各親の写真に関連付いているタグの数と、各親の投稿に関連付いているコメントの数を取得しましょう。
use Illuminate\Database\Eloquent\Relations\MorphTo;
$activities = ActivityFeed::with([
'parentable' => function (MorphTo $morphTo) {
$morphTo->morphWithCount([
Photo::class => ['tags'],
Post::class => ['comments'],
]);
}])->get();
遅延カウントロード
すでにActivityFeed
モデルを取得していて、アクティビティフィードに関連付いているさまざまなparentable
モデルのネストしたリレーションのカウントをロードしたいとします。これを実現するには、loadMorphCount
メソッドを使用します。
$activities = ActivityFeed::with('parentable')->get();
$activities->loadMorphCount('parentable', [
Photo::class => ['tags'],
Post::class => ['comments'],
]);
Eagerロード
Eloquentのリレーションにプロパティとしてアクセスする場合、関連するモデルは「遅延ロード」されます。これは、プロパティに最初にアクセスするまで、リレーションデータが実際にロードされないことを意味します。しかし、Eloquentでは、親モデルへのクエリ時に、リレーションを「Eagerロード」できます。Eagerロードにより、「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
{
/**
* その本を書いた著者を取得
*/
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回の追加クエリです。
ありがたいことに、Eagerロードを使用し、この操作を2つのクエリに減らすことができます。クエリを作成するときに、with
メソッドを使用してどの関係をEagerロードするかを指定します。
$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, ...)
複数リレーションのEagerロード
状況により、いくつか異なるリレーションをEagerロードする必要がおきます。これには、関係の配列をwith
メソッドに渡すだけです。
$books = Book::with(['author', 'publisher'])->get();
ネストしたEagerロード
リレーションのリレーションをEagerロードするために、「ドット」構文が使えます。たとえば、本のすべての著者とすべての著者の個人的な連絡先をEagerロードしましょう。
$books = Book::with('author.contacts')->get();
あるいは、with
メソッドにネストした配列を指定し、ネストしたEagerロード関係を指定することもできます。これは、複数のネストした関係をEagerロードする場合に便利です。
$books = Book::with([
'author' => [
'contacts',
'publisher',
],
])->get();
morphTo
リレーションのネストしたEagerロード
morphTo
リレーション、およびそのリレーションが返す可能性のあるさまざまなエンティティのネストしたリレーションをEagerロードしたい場合は、with
メソッドをmorphTo
リレーションのmorphWith
メソッドと組み合わせて使用します。この方法を説明するために、次のモデルについて考えてみましょう。
<?php
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;
class ActivityFeed extends Model
{
/**
* アクティビティフィードレコードの親を取得
*/
public function parentable(): MorphTo
{
return $this->morphTo();
}
}
この例では、Event
、Photo
、およびPost
モデルがActivityFeed
モデルを作成すると想定します。さらに、Event
モデルが「Calendar
モデルに属し、Photo
モデルがTag
モデルへ関連付けられ、Post
モデルがAuthor
モデルに属していると想定します。
これらのモデル定義とリレーションを使用して、ActivityFeed
モデルインスタンスを取得し、すべてのparentable
モデルとそれぞれのネストしたリレーションをEagerロードできます。
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();
特定のカラムのEagerロード
取得するリレーションのすべてのカラムが常に必要だとは限りません。このため、Eloquentはリレーションでどのカラムを取得するかを指定できます。
$books = Book::with('author:id,name,book_id')->get();
Warning!! この機能を使用するときは、取得するカラムのリストで常に
id
カラムと関連する外部キーカラムを含める必要があります。
デフォルトのEagerロード
モデルを取得するときに、常にいくつかのリレーションをロードしたい場合があります。実現するには、モデルに$with
プロパティを定義します。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Book extends Model
{
/**
* 常にロードする必要があるリレーション
*
* @var array
*/
protected $with = ['author'];
/**
* その本を書いた著者を取得
*/
public function author(): BelongsTo
{
return $this->belongsTo(Author::class);
}
/**
* 本のジャンルを取得
*/
public function genre(): BelongsTo
{
return $this->belongsTo(Genre::class);
}
}
一度のクエリで$with
プロパティからのアイテムを削除する場合は、without
メソッドを使用します。
$books = Book::without('author')->get();
一度のクエリに対し、$with
プロパティ内のすべてのアイテムをオーバーライドしたい場合は、withOnly
メソッドが使えます。
$books = Book::withOnly('genre')->get();
Eagerロードの制約
リレーションをEagerロードするだけでなく、Eagerロードクエリへクエリ条件を追加指定したい場合もあります。そのためには、リレーションの配列をwith
メソッドへ渡します。ここでの配列キーはリレーション名であり、配列値はEagerロードクエリへ制約を追加するクロージャです。
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
という単語を含んでいる投稿のみをEagerロードします。他のクエリビルダメソッドを呼び出して、Eagerロード操作をさらにカスタマイズすることもできます。
$users = User::with(['posts' => function (Builder $query) {
$query->orderBy('created_at', 'desc');
}])->get();
Warning!! Eagerロードを制限する場合は、
limit
およびtake
クエリビルダメソッドは使用できません。
morphTo
リレーションのEagerロードの制約
morphTo
リレーションをEagerロードする場合、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"な動画のみをEagerロードします。
リレーションの存在時のEagerロードの制約
リレーションが存在するかどうかをチェックすると同時に、同じ条件に基づいてそのリレーションをロードする必要がある場合があります。例えば、与えられたクエリ条件と一致するPost
モデルの子を持つUser
モデルのみを取得し、同時に一致する投稿をEagerロードしたいことがあります。このような場合は、withWhereHas
メソッドを使用します。
use App\Models\User;
$users = User::withWhereHas('posts', function ($query) {
$query->where('featured', true);
})->get();
遅延Eagerロード
親モデルを取得した後に、リレーションをEagerロードしたい場合があります。たとえば、これは関連モデルをロードするかを動的に決定する必要がある場合で役立ちます。
use App\Models\Book;
$books = Book::all();
if ($someCondition) {
$books->load('author', 'publisher');
}
Eagerロードクエリにクエリ制約を追加設定する必要がある場合は、ロードしたいリレーションをキーにした配列を渡します。配列の値は、クエリインスタンスを引数に受けるクロージャインスタンスの必要があります。
$author->load(['books' => function (Builder $query) {
$query->orderBy('published_date', 'asc');
}]);
未ロードの場合にのみリレーションを読み込むには、loadMissing
メソッドを使用します。
$book->loadMissing('author');
ネストした遅延EagerロードとmorphTo
morphTo
リレーション、およびそのリレーションが返す可能性のあるさまざまなエンティティのネストした関係をEagerロードしたい場合は、loadMorph
メソッドを使用できます。
このメソッドは、最初の引数としてmorphTo
リレーション名を取り、2番目の引数としてモデル/リレーションペアの配列を受けます。このメソッドを説明するために、次のモデルについて考えてみましょう。
<?php
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;
class ActivityFeed extends Model
{
/**
* アクティビティフィードレコードの親を取得
*/
public function parentable(): MorphTo
{
return $this->morphTo();
}
}
この例では、Event
、Photo
、およびPost
モデルがActivityFeed
モデルを作成すると想定します。さらに、Event
モデルが「Calendar
モデルに属し、Photo
モデルがTag
モデルへ関連付けられ、Post
モデルがAuthor
モデルに属していると想定します。
これらのモデル定義とリレーションを使用して、ActivityFeed
モデルインスタンスを取得し、すべてのparentable
モデルとそれぞれのネストしたリレーションをEagerロードできます。
$activities = ActivityFeed::with('parentable')
->get()
->loadMorph('parentable', [
Event::class => ['calendar'],
Photo::class => ['tags'],
Post::class => ['author'],
]);
遅延ロードの防止
前述したように、リレーションのEagerロードは、しばしばアプリケーションのパフォーマンスに大きなメリットをもたらします。そのため、ご希望であれば、Laravelにリレーションの遅延ロードを常に防ぐように指示できます。そのためには、Eloquentの基本モデルクラスが提供しているpreventLazyLoading
メソッドを呼び出します。一般的には、アプリケーションの
AppServiceProvider
クラスの boot
メソッド内でこのメソッドを呼び出します。
preventLazyLoading`メソッドは、遅延ロードを防止するかを示すオプションの論理値の引数を取ります。例として、運用環境以外では遅延ロードを無効にして、運用コードに遅延ロードするリレーションが誤って存在していても、運用環境では正常に機能し続けるようにしてみましょう。
use Illuminate\Database\Eloquent\Model;
/**
* アプリケーションの全サービスの初期起動処理
*/
public function boot(): void
{
Model::preventLazyLoading(! $this->app->isProduction());
}
遅延ロードを停止したあと、アプリケーションが任意のEloquentリレーションで遅延ロードしようとすると、EloquentはIlluminate\Database\LazyLoadingViolationException
例外を投げます。
遅延ロード違反の動作は,handleLazyLoadingViolationsUsing
メソッドを使ってカスタマイズできます。例えば、このメソッドを使って、アプリケーションの実行を例外で中断する代わりに、遅延ロード違反をログに記録するだけにするよう指示できます。
Model::handleLazyLoadingViolationUsing(function (Model $model, string $relation) {
$class = get_class($model);
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
メソッドは、適切なpost_id
値を新しいComment
モデルへ自動的に追加します。
複数の関連モデルを保存する必要がある場合は、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();
// 新しく保存されたコメントを含むすべてのコメント
$post->comments;
モデルと関係の再帰的保存
モデルとそれに関連するすべてのリレーションをsave
したい場合は、push
メソッドを使用します。下記例では、Post
モデルが、そのコメントとコメントの作成者とともに保存されます。
$post = Post::find(1);
$post->comments[0]->message = 'Message';
$post->comments[0]->author->name = 'Author Name';
$post->push();
pushQuietly
メソッドは、イベントを発生させずに、モデルとそれに関連するリレーションを保存するために使用します。
$post->pushQuietly();
create
メソッド
save
メソッドとsaveMany
メソッドに加え、属性の配列を受け取り、モデルを作成してデータベースに挿入するcreate
メソッドも使用できます。save
とcreate
の違いは、save
は完全なEloquentモデルインスタンスを受け入れるのに対し、create
はプレーンな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.'],
]);
createQuietly
メソッドとcreateManyQuietly
メソッドを使用すると、イベントをディスパッチせずにモデルを作成できます。
$user = User::find(1);
$user->posts()->createQuietly([
'title' => 'Post title.',
]);
$user->posts()->createManyQuietly([
['title' => 'First post.'],
['title' => 'Second post.'],
]);
findOrNew
、firstOrNew
、firstOrCreate
、updateOrCreate
メソッドを使用して関係のモデルを作成および更新することもできます。
Note:
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
メソッドは、中間テーブルから適切なレコードを削除します。ただし、両方のモデルはデータベースに残ります。
// ユーザーから一つの役割を関連解除
$user->roles()->detach($roleId);
// ユーザーからすべての役割を関連解除
$user->roles()->detach();
使いやすいように、attach
とdetach
はIDの配列も引数に取れます。
$user = User::find(1);
$user->roles()->detach([1, 2, 3]);
$user->roles()->attach([
1 => ['expires' => $expires],
2 => ['expires' => $expires],
]);
関連の同期
sync
メソッドを使用して、多対多の関連付けを構築することもできます。sync
メソッドは、中間テーブルに配置するIDの配列を引数に取ります。指定した配列にないIDは、中間テーブルから削除されます。したがってこの操作が完了すると、指定した配列のIDのみが中間テーブルに残ります。
$user->roles()->sync([1, 2, 3]);
IDを使用して追加の中間テーブル値を渡すこともできます。
$user->roles()->sync([1 => ['expires' => true], 2, 3]);
同期したモデルIDごとに同じ中間テーブルの値を挿入したい場合は、syncWithPivotValues
メソッドを使用できます。
$user->roles()->syncWithPivotValues([1, 2, 3], ['active' => true]);
指定した配列から欠落している既存のIDを切り離したくない場合は、syncWithoutDetaching
メソッドを使用します。
$user->roles()->syncWithoutDetaching([1, 2, 3]);
関連の切り替え
多対多リレーションは、指定した関連モデルIDの接続状態を「切り替える」、toggle
メソッドも提供します。指定されたIDが現在関連づいている場合、その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,
]);
親のタイムスタンプの更新
Post
に属するComment
など、モデルが別のモデルとのbelongsTo
またはbelongsToMany
の関係を定義している場合、子モデルのが更新時に親のタイムスタンプも更新できると役立つ場合があります。
たとえば、Comment
モデルが更新されたときに、所有しているPost
のupdated_at
タイムスタンプを自動的に「更新」して、現在の日時を設定したい場合があるでしょう。これを行うには、子モデルの更新時にupdated_at
タイムスタンプを更新する必要があるリレーションの名前を含むtouches
プロパティを子モデルに追加します。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Comment extends Model
{
/**
* 更新日時を更新すべき全リレーション
*
* @var array
*/
protected $touches = ['post'];
/**
* コメントが属する投稿の取得
*/
public function post(): BelongsTo
{
return $this->belongsTo(Post::class);
}
}
Warning!! 親モデルのタイムスタンプは、Eloquentの
save
メソッドを使用して子モデルを更新した場合にのみ更新されます。