イントロダクション
データベーステーブルは大抵の場合他のものと関連しています。たとえばブログポストは多くのコメントを持つか、それを投稿したユーザーと関連しています。Eloquentはそうしたリレーションを簡単に管理し操作できるようにするとともに、様々なタイプのリレーションをサポートしています。
リレーションの定義
EloquentのリレーションとはEloquentモデルクラスの関数として定義します。Eloquentモデル自身と同様にリレーションはパワフルなクエリービルダーとして動作しますので、関数として定義しているリレーションはパワフルなメソッドのチェーンとクエリー能力を提供できるのです。例を見てください。
$user->posts()->where('active', 1)->get();
リレーションの使い方へ進む前に、リレーションの各タイプをどのように定義するかを学びましょう。
1対1
1対1関係が基本です。たとえばUser
モデルはPhone
モデル一つと関係しているとしましょう。このリレーションを定義するには、phone
メソッドをUser
モデルに世知します。phone
メソッドはベースのEloquentモデルクラスのhasOne
メソッドを結果として返す必要があります。
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* ユーザーに関連する電話レコードを取得
*/
public function phone()
{
return $this->hasOne('App\Phone');
}
}
hasOne
メソッドの最初の引数は関係するモデルの名前です。リレーションが定義できたらEloquentの動的プロパティを使って、関係したモデルを取得できます。動的プロパティによりモデル上のプロパティのようにリレーション関数にアクセスできます。
$phone = User::find(1)->phone;
Eloquentはリレーションの外部キーがモデル名に基づいていると仮定します。この場合自動的にPhone
モデルはuser_id
外部キーを持っていると仮定します。この規約をオーバーライドしたければ、hasOne
メソッドの第2引数を指定してください。
return $this->hasOne('App\Phone', 'foreign_key');
Eloquentは親のid
カラムと一致する外部キーの値を持っていると仮定します。言い換えればEloquentはユーザーのid
カラムの値をPhone
レコードのuser_id
カラムに存在しないか探します。リレーションで他のid
を使いたければ、hadOne
メソッドの第3引数でカスタムキーを指定してください。
return $this->hasOne('App\Phone', 'foreign_key', 'local_key');
逆の関係の定義
これでUser
からPhone
モデルへアクセスできるようになりました。今度はPhone
モデルからそれを所有しているUser
へアクセスするリレーションを定義しましょう。hasOne
の逆のリレーションを定義するには、belongsTo
メソッドを使います。
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Phone extends Model
{
/**
* この電話を所有するUserを取得
*/
public function user()
{
return $this->belongsTo('App\User');
}
}
上の例でEloquentはPhone
モデルのuser_id
に一致するid
を持つUser
モデルを見つけようとします。Eloquentはリレーションメソッド名に_id
のサフィックスを付けた名前をデフォルトの外部キー名とします。しかしPhone
モデルの外部キーがuser_id
でなければ、belongsTo
メソッドの第2引数にカスタムキー名を渡してください。
/**
* この電話を所有するUserを取得
*/
public function user()
{
return $this->belongsTo('App\User', 'foreign_key');
}
親のモデルの主キーがid
でないか子のモデルと違ったカラムで紐付けたい場合は、親テーブルのカスタムキー名をbelongsTo
メソッドの第3引数に渡してください。
/**
* この電話を所有するUserを取得
*/
public function user()
{
return $this->belongsTo('App\User', 'foreign_key', 'other_key');
}
1対多
「1対多」リレーションは一つのモデルが他の多くのモデルを所有する関係を定義するために使います。ブログポストが多くのコメントを持つのが一例です。他のEloquentリレーションと同様に1対多リレーションはEloquentモデルの関数として定義します。
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
/**
* ブログポストのコメントを取得
*/
public function comments()
{
return $this->hasMany('App\Comment');
}
}
EloquentはComment
モデルに対する外部キーを自動的に決めることを心に留めてください。規約によりEloquentは自分自身のモデル名の「スネークケース」に_id
のサフィックスをつけた名前と想定します。ですから今回の例でEloquentはComment
モデルの外部キーをpost_id
であると想定します。
リレーションを定義したらcomments
プロパティによりコメントのコレクションへアクセスできます。Eloquentは「動的プロパティ」を提供しているのでモデルのプロパティとして定義したリレーション関数へアクセスできることを覚えておきましょう。
$comments = App\Post::find(1)->comments;
foreach ($comments as $comment) {
//
}
もちろん全リレーションはクエリービルダーとしても働きますから、comments
メソッドを呼び出すときにどのコメントを取得するのかという制約を追加でき、クエリーに条件を続けてチェーンでつなげます。
$comments = App\Post::find(1)->comments()->where('title', 'foo')->first();
hasOne
メソッドと同様に、外部キーとローカルキーをhasMany
メソッドに追加の引数として渡すことでオーバーライドできます。
return $this->hasMany('App\Comment', 'foreign_key');
return $this->hasMany('App\Comment', 'foreign_key', 'local_key');
逆のリレーションを定義
これでポストの全コメントにアクセスできます。今度はコメントから親のポストへアクセスできるようにしましょう。hasMany
リレーションの逆を定義するには子のモデルでbelongsTo
メソッドによりリレーション関数を定義します。
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model
{
/**
* このコメントを所有するポストを取得
*/
public function post()
{
return $this->belongsTo('App\Post');
}
}
リレーションが定義できたらComment
のPost
モデルをpost
動的プロパティにより取得しましょう。
$comment = App\Comment::find(1);
echo $comment->post->title;
前例でEloquentはComment
モデルのpost_id
と一致するid
のPost
モデルを見つけようとします。Eloquentはリレーションメソッドの名前に_id
のサフィックスをつけた名前をデフォルトの外部キーとします。しかしComment
モデルの外部キーがpost_id
でなければ、belongsTo
メソッドの第2引数にカスタムキー名を指定してください。
/**
* このコメントを所有するポストを取得
*/
public function post()
{
return $this->belongsTo('App\Post', 'foreign_key');
}
親モデルが主キーにid
を使っていない場合、もしくは子供のモデルに異なったカラムを紐つけたい場合は、belongsTo
メソッドの第3引数にテーブルのカラム名を指定してください。
/**
* このコメントを所有するポストを取得
*/
public function post()
{
return $this->belongsTo('App\Post', 'foreign_key', 'other_key');
}
多対多
多対多の関係はhasOne
とhasMany
リレーションよりも多少複雑な関係です。このような関係として、ユーザ(user)が多くの役目(roles)を持ち、役目(role)も大勢のユーザー(users)に共有されるという例が挙げられます。たとえば多くのユーザーは"管理者"の役目を持っています。users
、roles
、role_user
の3テーブルがこの関係には必要です。role_user
テーブルは関係するモデル名をアルファベット順に並べたもので、user_id
とrole_id
を持つ必要があります。
多対多リレーションはベースのEloqunetクラスのbelongsToMany
メソッド呼び出しを記述することで定義します。例としてUser
モデルにroles
メソッドを定義してみましょう。
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* userに所属する役割を取得
*/
public function roles()
{
return $this->belongsToMany('App\Role');
}
}
リレーションが定義できたら、roles
動的プロパティを使いユーザーの役割にアクセスできます。
$user = App\User::find(1);
foreach ($user->roles as $role) {
//
}
もちろん他のリレーションタイプと同様にリレーションを制約するクエリーをroles
に続けてチェーンすることができます。
$roles = App\User::find(1)->roles()->orderBy('name')->get();
前に述べたようにリレーションの結合テーブルの名前を決めるため、Eloquentは2つのモデル名をアルファベット順に結合します。しかしこの規約は自由にオーバーライドできます。belongsToMany
メソッドの第2引数に渡してください。
return $this->belongsToMany('App\Role', 'user_roles');
結合テーブル名のカスタマイズに加えテーブルのキーカラム名をカスタマイズするには、belongsToMany
メソッドに追加の引数を渡してください。第3引数はリレーションを定義しているモデルの外部キー名で、一方の第4引数には結合するモデルの外部キー名を渡します。
return $this->belongsToMany('App\Role', 'user_roles', 'user_id', 'role_id');
逆のリレーションを定義
多対多のリレーションの逆リレーションを定義するには、関連するモデルでもbelongsToMany
を呼び出してください。引き続きユーザーと役割の例を続けますがRole
モデルでusers
メソッドを定義してみましょう。
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Role extends Model
{
/**
* 役割を所有するユーザー
*/
public function users()
{
return $this->belongsToMany('App\User');
}
}
ご覧の通り一方のUser
と全く同じ定義のリレーションです。違いはApp\User
モデルを参照していことです。同じbelongsToMany
メソッドを使っているのですから、通常のテーブル名、キーカスタマイズのオプションは逆の多対多リレーションを定義するときでも全て使用できます。
中間テーブルのカラム取得
既に学んだように、多対多リレーションの操作には中間テーブルが必要です。Eloquentこのテーブルを操作する便利な手段を用意しています。例としてUser
オブジェクトが関連するRole
オブジェクトを持っているとしましょう。このリレーションへアクセスした後、モデルのpivot
属性を使い中間テーブルにアクセスできます。
$user = App\User::find(1);
foreach ($user->roles as $role) {
echo $role->pivot->created_at;
}
取得したそれぞれのRole
モデルは自動的にpivot
属性と自動的に結合されます。この属性は中間テーブルを表すモデルを含んでおり、他のElouquentモデルと同様に使用できます。
デフォルトでモデルキーはpivot
オブジェクト上のものを表しています。中間テーブルがその他の属性を持っている場合、リレーションを定義するときに指定できます。
return $this->belongsToMany('App\Role')->withPivot('column1', 'column2');
もし中間テーブルのcreated_at
、updated_at
タイムスタンプを自動的に保守したい場合は、withTimestamps
メソッドをリレーション定義に付けてください。
return $this->belongsToMany('App\Role')->withTimestamps();
Has Many Through
has many
through(〜経由の多対多)リレーションは、仲介するテーブルを通して直接関連付けしていないテーブルへアクセスするための、便利な近道を提供します。たとえばCountry
モデルはUsers
モデルを経由して、多くのPosts
を所有することでしょう。テーブルは以下のような構成になります。
countries
id - integer
name - string
users
id - integer
country_id - integer
name - string
posts
id - integer
user_id - integer
title - string
たとえposts
テーブルにcountry_id
が存在しなくても、hasManyThrough
リレーションではCountryのPostへ$country->posts
によりアクセスできます。このクエリーを行うためにEloquentは仲介するusers
テーブルのcountry_id
を調べます。一致するユーザーIDが存在していたらposts
テーブルのクエリーに利用します。
ではリレーションのテーブル構造を理解したところで、Country
モデルを定義しましょう。
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Country extends Model
{
/**
* この国の全ポストを取得
*/
public function posts()
{
return $this->hasManyThrough('App\Post', 'App\User');
}
}
hasManyThrough
メソッドの第一引数は最終的にアクセスしたいモデル名で、第2引数は仲介するモデル名です。
リレーションのクエリー実行時では典型的なEloquentの外部キー規約が使用されます。リレーションのキーをカスタマイズしたい場合はhasManyThrough
メソッドの第3引数と、第4引数を指定してください。第3引数は仲介モデルの外部キー名、第4引数は最終的なモデルの外部キー名です。
class Country extends Model
{
public function posts()
{
return $this->hasManyThrough('App\Post', 'App\User', 'country_id', 'user_id');
}
}
ポリモーフィック関係
テーブル構造
ポリモーフィックリレーションはあるモデルを一つの関係だけで、複数のモデルに所属させるものです。たとえばスタッフメンバーと商品の写真を保存するとしましょう。ポリモーフィックリレーションによりphotos
テーブル一つを両方のシナリオで使用できます。最初にこのリレーションを構築するために必要な構造を確認してください。
staff
id - integer
name - string
products
id - integer
price - integer
photos
id - integer
path - string
imageable_id - integer
imageable_type - string
photos
テーブル上の2つの重要なカラム、imageable_id
とimageable_type
に注目してください。imageable_id
カラムは所有しているスタッフか製品のIDを持っています。一方のimageable_type
カラムは所収されているモデルのクラス名を持っています。imageable_type
はimageable
リレーションにアクセスされた時に返す、所有しているモデルの「タイプ」をORMが決定するためのカラムです。
モデル構造
次にこのリレーションを構築するために必要なモデル定義を見てみましょう。
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Photo extends Model
{
/**
* 所有している全imageableモデルの取得
*/
public function imageable()
{
return $this->morphTo();
}
}
class Staff extends Model
{
/**
* 全スタッフメンバーの写真の取得
*/
public function photos()
{
return $this->morphMany('App\Photo', 'imageable');
}
}
class Product extends Model
{
/**
* 全商品の写真の取得
*/
public function photos()
{
return $this->morphMany('App\Photo', 'imageable');
}
}
ポリモーフィックリレーションの取得
データベーステーブルとモデルが定義できたらモデルを使いリレーションにアクセスできます。たとえばスタッフメンバーの全写真にアクセスするにはphotos
動的プロパティーを使用するだけです。
$staff = App\Staff::find(1);
foreach ($staff->photos as $photo) {
//
}
ポリモーフィックモデルからポリモーフィックリレーションの所有者をmorphTo
を呼び出すメソッド名にアクセスすることで取得できます。今回の例の場合Photo
モデルのimageable
メソッドです。動的プロパティーとしてメソッドにアクセスしてみましょう。
$photo = App\Photo::find(1);
$imageable = $photo->imageable;
Photo
モデルのimageable
リレーションは写真を所有しているモデルのタイプにより、Staff
かProduct
インスタンスを返します。
カスタムポリモーフィックリレーション
関連付けられたモデルのタイプを保存するため、デフォルトでLaravelははっきりと区別できるクラス名を使います。たとえば上記の例で、Like
がPost
かComment
に所属しているとすると、likable_type
はデフォルトでApp\Post
かApp\Comment
のどちらかになるでしょう。しかし、データーベースはアプリケーションの内部構造と分離したいかと思います。その場合、リレーションの"morph
map"を定義し、クラス名の代わりに使用する、各モデルに関連づいたテーブル名をEloquentへ指示することができます。
Relation::morphMap([
App\Post::class,
App\Comment::class,
]);
もしくは、各モデルに関連づいた、カスタム文字列を指定することもできます。
Relation::morphMap([
'posts' => App\Post::class,
'likes' => App\Like::class,
]);
morphMap
はAppServiceProvider
の中で登録できます。要望に合わせて、独立したサービスプロバイダを作成することもできるでしょう。
ポリモーフィック関係の多対多
テーブル構造
伝統的なポリモーフィックリレーションに加え、「多対多」ポリモーフィックリレーションも指定することができます。たとえばブログのPost
とVideo
モデルはTag
モデルに対するポリモーフィックリレーションを共有できます。多対多ポリモーフィックリレーションを使うことで、ブログポストとビデオの両者に所有されている一意のタグのリストを取得できます。最初にテーブル構造を確認しましょう。
posts
id - integer
name - string
videos
id - integer
name - string
tags
id - integer
name - string
taggables
tag_id - integer
taggable_id - integer
taggable_type - string
モデル構造
次にモデルにその関係を用意しましょう。Post
とVideo
モデルは両方ともベースのEloquentクラスのmorphToMany
メソッドを呼び出すtags
メソッドを持っています。
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
/**
* ポストに対する全タグを取得
*/
public function tags()
{
return $this->morphToMany('App\Tag', 'taggable');
}
}
逆のリレーションの定義
次にTag
モデルで関係する各モデルに対するメソッドを定義する必要があります。ですからこの例の場合、posts
メソッドとvideos
メソッドを用意します。
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Tag extends Model
{
/**
* このタグをつけた全ポストの取得
*/
public function posts()
{
return $this->morphedByMany('App\Post', 'taggable');
}
/**
* このタグをつけた全ビデオの取得
*/
public function videos()
{
return $this->morphedByMany('App\Video', 'taggable');
}
}
リレーションの取得
データベーステーブルとモデルが定義できたら、モデルを使いリレーションにアクセスできます。たとえばポストに対する全タグへアクセスするには、単にtags
動的プロパティーを使用するだけです。
$post = App\Post::find(1);
foreach ($post->tags as $tag) {
//
}
さらにmorphedByMany
を呼び出すメソッドの名前にアクセスし、ポリモーフィックモデルからポリモーフィックリレーションの所有者を取得することも可能です。この例の場合Tag
モデルのposts
とvideos
メソッドです。では動的プロパティーとしてメソッドを呼び出しましょう。
$tag = App\Tag::find(1);
foreach ($tag->videos as $video) {
//
}
リレーションのクエリー
Eloquentリレーションは全てメソッドとして定義され、リレーションのクエリーを実際に記述しなくてもメソッドを呼び出すことでそのリレーションのインスタンスを取得できます。付け加えるなら、全タイプのEloquentリレーションもクエリービルダーとしても動作し、データベースに対してSQLが最終的に実行される前にそのリレーションのクエリーをチェーンで続けて記述できます。
たとえばブログシステムで関連した多くのPost
モデルを持つUser
モデルを想像してください。
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* ユーザーの全ポストの取得
*/
public function posts()
{
return $this->hasMany('App\Post');
}
}
次のようにposts
リレーションのクエリーに追加の制約を付け加えられます。
$user = App\User::find(1);
$user->posts()->where('active', 1)->get();
リレーションにもクエリービルダーメソッドが使用できる点に注目です!
リレーションメソッド v.s. 動的プロパティー
Eloquentリレーションクエリーに追加の制約を加える必要がなければ、シンプルにそのリレーションへプロパティーとしてアクセスできます。User
とPost
の例を続けるとして、ユーザーの全ポストには次のようにアクセスできます。
$user = App\User::find(1);
foreach ($user->posts as $post) {
//
}
動的プロパティーは「遅延ロード」されます。つまり実際にアクセスされた時にだけそのリレーションのデータはロードされます。そのため開発者は多くの場合にEagerローディングを使い、モデルをロードした後にアクセスするリレーションを前もってロードしておきます。Eagerロードはモデルのリレーションをロードするため実行されるSQLクエリーを大幅に減らしてくれます。
存在するリレーションのクエリー
関連付けたモデルのレコードに基づいて、モデルのレコードに対するマッチングを絞り込みたい場合もあるでしょう。たとえば最低でも一つのコメントを持つ、全ブログポストを取得したい場合を考えてください。これを行うためにはhas
メソッドを使用します。
// 最低1つのコメントを持つ全ポストの取得…
$posts = App\Post::has('comments')->get();
演算子と数を指定しクエリーをカスタマイズすることもできます。
// 3つ以上のコメントを持つ全ポストの取得…
$posts = Post::has('comments', '>=', 3)->get();
ネストしたhas
文は「ドット」記法で組立てられます。たとえば最低一つのコメントと評価を持つ全ポストを取得する場合です。
// 最低1つのコメントと、それに対する評価を持つ全ポストの取得…
$posts = Post::has('comments.votes')->get();
もっと強力な機能がお望みならばhas
の問い合わせに"where"で条件をつけるために、whereHas
やorWhereHas
を利用して下さい。これらのメソッドによりリレーションの制約にカスタマイズした制約を追加できます。たとえばコメントの内容を調べることです。
// like foo%の制約に一致する最低1つのコメントを持つ全ポストの取得
$posts = Post::whereHas('comments', function ($query) {
$query->where('content', 'like', 'foo%');
})->get();
Eagerローディング
Eloquentリレーションをプロパティーとしてアクセする場合、リレーションのデータは「遅延ロード」されます。つまりプロパティーにアクセスされるまで実際にリレーションのデータはロードされることはありません。しかし、Eloquentでは親モデルにクエリーする時点で「Eagerロード」できます。EagerローディングはN+1クエリー問題を軽減するために用意しています。たとえばBook
モデルがAuthor
モデルと関連していると考えてください。関係はこのように定義されます。
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Book extends Model
{
/**
* この本を書いた著者を取得
*/
public function author()
{
return $this->belongsTo('App\Author');
}
}
では全書籍とその著者を取得しましょう。
$books = App\Book::all();
foreach ($books as $book) {
echo $book->author->name;
}
このループではまず全ての本をテーブルから取得するために1クエリー実行され、それから著者をそれぞれの本について取得します。ですから25冊あるならば、このループで26クエリーが発生します。
ありがたいことにクエリーの数を徹底的に減らすためにEagerローディングを使うことができます。with
メソッドを使い指定してください。
$books = App\Book::with('author')->get();
foreach ($books as $book) {
echo $book->author->name;
}
この操作では2つだけしかクエリーが実行されません。
select * from books
select * from authors where id in (1, 2, 3, 4, 5, ...)
複数のリレーションに対するEagerローディング
一回の操作で異なった複数のリレーションをEagerローディングする必要がある場合もあります。その場合でも、ただwith
メソッドに引数を追加で渡すだけです。
$books = App\Book::with('author', 'publisher')->get();
ネストしたEagerローディング
ネストしたリレーションをEagerロードする場合は「ドット」記法が使用できます。例としてEloquent文で全著者と著者個人のコンタクトも全部Eagerロードしてみましょう。
$books = App\Book::with('author.contacts')->get();
Eagerロードの制約
場合によりリレーションをEagerロードしたいが、Eagerロードクエリーに制約を追加したい場合があります。例を見てください。
$users = App\User::with(['posts' => function ($query) {
$query->where('title', 'like', '%first%');
}])->get();
この例でEloquentはtitle
カラムの内容にfirst
という言葉を含むポストのみをEagerロードしています。もちろんEagerローディング操作をもっとカスタマイズするために他のクエリービルダーを呼び出すこともできます。
$users = App\User::with(['posts' => function ($query) {
$query->orderBy('created_at', 'desc');
}])->get();
遅延Eagerローディング
既に親のモデルを取得した後にリレーションをEagerロードする必要がある場合もあるでしょう。たとえばこれはどの関連しているモデルをロードするかを動的に決める場合に便利です。
$books = App\Book::all();
if ($someCondition) {
$books->load('author', 'publisher');
}
Eagerロードに追加の制約を付ける必要があるなら、load
メソッドへ「クロージャー」を渡してください。
$books->load(['author' => function ($query) {
$query->orderBy('published_date', 'asc');
}]);
関係したモデルの挿入
saveメソッド
Eloquentは新しいモデルをリレーションに追加するために便利なメソッドを用意しています。たとえばPost
モデルに新しいComment
を挿入する必要がある場合です。Comment
のpost_id
属性を自分で設定する代わりに、リレーションのsave
メソッドで直接Comment
を挿入できます。
$comment = new App\Comment(['message' => 'A new comment.']);
$post = App\Post::find(1);
$post->comments()->save($comment);
動的プロパティーとしてcomments
リレーションにアクセスできない点には注意してください。代わりにリレーションの取得でcomments
メソッドを呼び出しています。save
メソッドは自動的に新しいComment
モデルのpost_id
へ適した値を代入します。
複数の関連したモデルを保存する必要があるなら、saveMany
メソッドを使用できます。
$post = App\Post::find(1);
$post->comments()->saveMany([
new App\Comment(['message' => 'A new comment.']),
new App\Comment(['message' => 'Another comment.']),
]);
saveと多対多関係の挿入
多対多リレーションを操作する場合、save
メソッドの第2引数へ追加の中間テーブルの属性を指定できます。
App\User::find(1)->roles()->save($role, ['expires' => $expires]);
createメソッド
save
とsaveMany
メソッドに加え、create
メソッドも使用できます。属性の配列を引数に受け付け、モデルを作成しデータベースへ挿入します。save
とcreate
の違いはsave
が完全なEloquentモデルを受け付けるのに対し、create
は普通のPHPの「配列」を受け付ける点です。
$post = App\Post::find(1);
$comment = $post->comments()->create([
'message' => '新しいコメント。',
]);
create
メソッドを使用する前に属性の複数代入に関するドキュメントを読んでおいてください。
"Belongs To"リレーションの更新
belongsTo
リレーションを更新する場合はassociate
メソッドを使います。このメソッドは子モデルへ外部キーをセットします。
$account = App\Account::find(10);
$user->account()->associate($account);
$user->save();
belongsTo
リレーションを削除する場合はdissociate
メソッドを使用します。このメソッドはリレーションの子モデルの外部キーをリセットします。
$user->account()->dissociate();
$user->save();
多対多関係の挿入
attach/detach
多対多リレーションを操作時により便利なように、Eloquentはヘルパメソッドをいくつか用意しています。例としてユーザーが多くの役割りを持ち、役割も多くのユーザーを持てる場合を考えてみましょう。モデルを結びつけている中間テーブルにレコードを挿入することにより、ユーザーに役割を持たせるにはattach
メソッドを使います。
$user = App\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 = App\User::find(1);
$user->roles()->detach([1, 2, 3]);
$user->roles()->attach([1 => ['expires' => $expires], 2, 3]);
便利なsync
多対多の関連を構築するためにsync
メソッドも使用できます。sync
メソッドへは中間テーブルに設置しておくIDの配列を渡します。その配列に指定されなかったIDは中間テーブルから削除されます。ですからこの操作が完了すると、中間テーブルには配列中のIDだけが存在することになります。
$user->roles()->sync([1, 2, 3]);
IDと一緒に中間テーブルの追加の値を渡すことができます。
$user->roles()->sync([1 => ['expires' => true], 2, 3]);
親のタイムスタンプの更新
Comment
がPost
に所属しているように、あるモデルが他のモデルに所属(belongsTo
もしくはbelongsToMany
)しており、子のモデルが更新される時に親のタイムスタンプを更新できると便利なことがあります。たとえばComment
モデルが更新されたら、所有者のPost
のupdated_at
タイムスタンプを自動的に"touch"したい場合です。Eloquentなら簡単です。子のモデルにtouches
プロパティを追加しリレーション名を指定してください。
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model
{
/**
* 更新する全リレーション
*
* @var array
*/
protected $touches = ['post'];
/**
* ポストに所属しているコメント取得
*/
public function post()
{
return $this->belongsTo('App\Post');
}
}
これでComment
が更新されると、所有しているPost
のupdated_at
カラムも同時に更新されます。
$comment = App\Comment::find(1);
$comment->text = 'このコメントを修正!';
$comment->save();