Eloquentのリレーションメソッドを活用して、紐付けの設定や解除を行う方法について解説します。
複数の手続きをまとめて行うメソッドが用意されているので、コードの可読性や保守性を高めることができます。
実際に実行されるSQLを載せています。どういった動作が行われるのか理解するのに役立つと思います。
なお、Eloquentのリレーションについて以下3回に渡って解説しています。他の記事も参考にしてください。
- Eloquentのリレーション活用方法【関連の定義】
- Eloquentのリレーション活用方法【関連の取得】
- Eloquentのリレーション活用方法【紐付けの設定と解除】 ← 今回
動作確認に利用したリポジトリです。
https://github.com/raku-raku/laravel_eloquent_practice
紐づく子モデルを設定
save, saveMany
( Eloquentモデルで設定 )
save
$comment = new Comment(['body' => 'A new comment.']);
$post = Post::find(1);
$post->comments()->save($comment);
insert into `comments`
(`body`, `commentable_id`, `commentable_type`, `updated_at`, `created_at`)
values
('A new comment.', '1', 'App\Models\Post', '2018-11-17 13:05:41', '2018-11-17 13:05:41')
saveMany
$post = Post::find(1);
$post->comments()->saveMany([
new Comment(['body' => 'A new comment.']),
new Comment(['body' => 'Another comment.']),
]);
insert into `comments`
(`body`, `commentable_id`, `commentable_type`, `updated_at`, `created_at`)
values
('A new comment.', '1', 'App\Models\Post', '2018-11-17 13:07:28', '2018-11-17 13:07:28')
insert into `comments`
(`body`, `commentable_id`, `commentable_type`, `updated_at`, `created_at`)
values
('Another comment.', '1', 'App\Models\Post', '2018-11-17 13:07:28', '2018-11-17 13:07:28')
create, createMany
( 配列で設定 )
create
$comment = ['body' => 'A new comment.'];
$post = Post::find(1);
$post->comments()->create($comment);
insert into `comments`
(`body`, `commentable_id`, `commentable_type`, `updated_at`, `created_at`)
values
('A new comment.', '1', 'App\Models\Post', '2018-11-17 13:12:32', '2018-11-17 13:12:32')
createMany
$post = Post::find(1);
$post->comments()->createMany([
['body' => 'A new comment.'],
['body' => 'Another comment.'],
]);
insert into `comments`
(`body`, `commentable_id`, `commentable_type`, `updated_at`, `created_at`)
values
('A new comment.', '1', 'App\Models\Post', '2018-11-17 13:10:52', '2018-11-17 13:10:52')
insert into `comments`
(`body`, `commentable_id`, `commentable_type`, `updated_at`, `created_at`)
values
('Another comment.', '1', 'App\Models\Post', '2018-11-17 13:10:52', '2018-11-17 13:10:52')
push
( 再帰的に設定 )
$country = Country::find(2);
$country->users[0]->name = 'user name';
$country->users[0]->posts[0]->title = 'post title';
$country->push();
select * from `countries` where `countries`.`id` = '2' limit 1
select * from `users` where `users`.`country_id` = '2' and `users`.`country_id` is not null
select * from `posts` where `posts`.`user_id` = '1' and `posts`.`user_id` is not null
update `users` set `name` = 'user name', `updated_at` = '2018-11-17 13:18:17' where `id` = '1'
update `posts` set `title` = 'post title', `updated_at` = '2018-11-17 13:18:17' where `id` = '5'
紐づく親モデルを設定
associate
( 紐付け設定 )
$user = User::find(1);
$post = Post::find(1);
$post->user()->associate($user);
$post->save();
update `posts`
set `user_id` = '1', `updated_at` = '2018-11-17 13:26:16'
where `id` = '1'
dissociate
( 紐付け解除 )
外部キーが nullable
の場合、以下のように紐付けを解除することができます。
$phone = Phone::find(1);
$phone->user()->dissociate();
$phone->save();
update `phones`
set `user_id` = '', `updated_at` = '2018-11-17 13:35:09'
where `id` = '1'
多対多関係の紐付け
目的に応じた利用可能メソッド
多対多関係では、目的に応じて以下メソッドが用意されています。
メソッド | insert | update | delete |
---|---|---|---|
attach | ○ | × | × |
detach | × | × | ○ |
updateExistingPivot | × | ○ | × |
sync | ○ | ○ | ○ |
syncWithoutDetaching | ○ | ○ | × |
toggle | ○ | × | ○ |
insert, update, deleteは中間テーブルへの操作になります。
attach
( 紐付け追加 )
$roleId1 = 1;
$roleId2 = 2;
$user1 = User::find(1);
$user1->roles()->attach($roleId1);
$user1->roles()->attach($roleId2, ['column1' => 'xxxxx']);
insert into `role_user`
(`created_at`, `role_id`, `updated_at`, `user_id`)
values
('2018-11-17 13:42:41', '1', '2018-11-17 13:42:41', '1')
insert into `role_user`
(`column1`, `created_at`, `role_id`, `updated_at`, `user_id`)
values
('xxxxx', '2018-11-17 13:42:41', '2', '2018-11-17 13:42:41', '1')
以下のように、まとめて追加することもできます。
$roleId1 = 1;
$roleId2 = 2;
$user2 = User::find(2);
$user2->roles()->attach([
$roleId1 => ['column1' => 'xxxxx'],
$roleId2 => ['column1' => 'yyyyy'],
]);
insert into `role_user`
(`column1`, `created_at`, `role_id`, `updated_at`, `user_id`)
values
('xxxxx', '2018-11-17 13:50:30', '1', '2018-11-17 13:50:30', '2'),
('yyyyy', '2018-11-17 13:50:30', '2', '2018-11-17 13:50:30', '2')
detach
( 紐付け解除 )
引数を指定しない場合、全ての紐付けが解除されます。
$user1 = User::find(1);
$user1->roles()->detach(1);
$user2 = User::find(2);
$user2->roles()->detach();
delete from `role_user` where `user_id` = '1' and `role_id` in ('1')
delete from `role_user` where `user_id` = '2'
updateExistingPivot
( 中間テーブルの値更新 )
updateExistingPivotメソッド
を利用すると中間テーブルに存在するカラムの値を更新できます。
$roleId = 1;
$user = User::find(1);
$user->roles()->updateExistingPivot($roleId, ['column1' => 'xxxxx']);
update `role_user`
set `column1` = 'xxxxx',
`updated_at` = '2018-11-17 16:14:11'
where `user_id` = '1'
and `role_id` in ('1')
sync
( 同期 )
syncメソッド
を利用すると紐付け状態を 同期
できます。
つまり、現在の紐付け状態に応じて、
- 紐付けの追加
- 中間テーブルの値更新
- 紐付けの解除
をまとめて行います。
動作確認していきます。
現在の紐付け状態
mysql> SELECT * FROM `role_user` WHERE `user_id` = 1;
+---------+---------+------------------------------------------------+---------------------------------------------------+---------------------+---------------------+
| user_id | role_id | column1 | column2 | created_at | updated_at |
+---------+---------+------------------------------------------------+---------------------------------------------------+---------------------+---------------------+
| 1 | 1 | Queen will hear you! You see, she came upon a. | Cat said, waving its right ear and left off when. | 2018-11-17 14:22:12 | 2018-11-17 14:22:12 |
| 1 | 2 | Queen will hear you! You see, she came upon a. | Cat said, waving its right ear and left off when. | 2018-11-17 14:22:12 | 2018-11-17 14:22:12 |
+---------+---------+------------------------------------------------+---------------------------------------------------+---------------------+---------------------+
2 rows in set (0.00 sec)
Userが 2つのRole
と紐づいている状態です。
syncメソッド実行
$user = User::find(1);
$user->roles()->sync([2 => ['column1' => 'xxxxx'], 3]);
delete from `role_user`
where `user_id` = '1'
and `role_id` in ('1')
update `role_user`
set `column1` = 'xxxxx',
`updated_at` = '2018-11-17 14:29:29'
where `user_id` = '1'
and `role_id` in ('2')
insert into `role_user`
(`created_at`, `role_id`, `updated_at`, `user_id`)
values
('2018-11-17 14:29:29', '3', '2018-11-17 14:29:29', '1')
role_id | 処理 |
---|---|
1 | 紐付け解除されました |
2 | 中間テーブルの値が更新されました |
3 | 紐付け追加されました |
syncメソッド実行後の紐付け状態
mysql> SELECT * FROM `role_user` WHERE `user_id` = 1;
+---------+---------+---------+---------------------------------------------------+---------------------+---------------------+
| user_id | role_id | column1 | column2 | created_at | updated_at |
+---------+---------+---------+---------------------------------------------------+---------------------+---------------------+
| 1 | 2 | xxxxx | Cat said, waving its right ear and left off when. | 2018-11-17 14:22:12 | 2018-11-17 14:29:29 |
| 1 | 3 | NULL | NULL | 2018-11-17 14:29:29 | 2018-11-17 14:29:29 |
+---------+---------+---------+---------------------------------------------------+---------------------+---------------------+
2 rows in set (0.00 sec)
syncWithoutDetaching
( 同期|削除はしない )
syncメソッド
では 紐付け解除
も行われてしまいます。
紐付け解除したくないのであれば、 syncWithoutDetachingメソッド
を利用します。
現在の紐付け状態
mysql> SELECT * FROM `role_user` WHERE `user_id` = 1;
+---------+---------+---------------------------------------------------+------------------------------------------------+---------------------+---------------------+
| user_id | role_id | column1 | column2 | created_at | updated_at |
+---------+---------+---------------------------------------------------+------------------------------------------------+---------------------+---------------------+
| 1 | 1 | I believe.' 'Boots and shoes under the hedge. In. | Alice did not venture to ask his neighbour to. | 2018-11-17 14:43:26 | 2018-11-17 14:43:26 |
| 1 | 2 | I believe.' 'Boots and shoes under the hedge. In. | Alice did not venture to ask his neighbour to. | 2018-11-17 14:43:26 | 2018-11-17 14:43:26 |
+---------+---------+---------------------------------------------------+------------------------------------------------+---------------------+---------------------+
2 rows in set (0.07 sec)
syncWithoutDetachingメソッド実行
$user = User::find(1);
$user->roles()->syncWithoutDetaching([2 => ['column1' => 'xxxxx'], 3]);
update `role_user`
set `column1` = 'xxxxx',
`updated_at` = '2018-11-17 15:29:45'
where `user_id` = '1' and `role_id` in ('2')
insert into `role_user`
(`created_at`, `role_id`, `updated_at`, `user_id`)
alues
('2018-11-17 15:29:45', '3', '2018-11-17 15:29:45', '1')
syncWithoutDetachingメソッド実行後の紐付け状態
mysql> SELECT * FROM `role_user` WHERE `user_id` = 1;
+---------+---------+--------------------------------------------+-----------------------------------------------+---------------------+---------------------+
| user_id | role_id | column1 | column2 | created_at | updated_at |
+---------+---------+--------------------------------------------+-----------------------------------------------+---------------------+---------------------+
| 1 | 3 | Alice looked round, eager to see the Mock. | I am very tired of sitting by her sister was. | 2018-11-17 16:00:27 | 2018-11-17 16:00:27 |
+---------+---------+--------------------------------------------+-----------------------------------------------+---------------------+---------------------+
1 row in set (0.01 sec)
toggle
( 紐付け状態切り替え )
現在の紐付け状態に応じて、動作が変わります。
指定された紐付け対象が、 紐づいている状態
であれば 紐付け解除
します。
指定された紐付け対象が、 紐づいていない状態
であれば 紐付け
します。
現在の紐付け状態
mysql> SELECT * FROM `role_user` WHERE `user_id` = 1;
+---------+---------+----------------------------------------------+---------------------------------------------------+---------------------+---------------------+
| user_id | role_id | column1 | column2 | created_at | updated_at |
+---------+---------+----------------------------------------------+---------------------------------------------------+---------------------+---------------------+
| 1 | 3 | Dodo. Then they both cried. 'Wake up, Alice. | There was not going to begin lessons: you d only. | 2018-11-17 15:56:39 | 2018-11-17 15:56:39 |
+---------+---------+----------------------------------------------+---------------------------------------------------+---------------------+---------------------+
1 row in set (0.00 sec)
toggleメソッド実行
$user = User::find(1);
$user->roles()->toggle([1, 2, 3]);
delete from `role_user`
where `user_id` = '1'
and `role_id` in ('3')
insert into `role_user`
(`created_at`, `role_id`, `updated_at`, `user_id`)
values
('2018-11-17 16:01:00', '1', '2018-11-17 16:01:00', '1'),
('2018-11-17 16:01:00', '2', '2018-11-17 16:01:00', '1')
toggleメソッド実行後の紐付け状態
mysql> SELECT * FROM `role_user` WHERE `user_id` = 1;
+---------+---------+---------+---------+---------------------+---------------------+
| user_id | role_id | column1 | column2 | created_at | updated_at |
+---------+---------+---------+---------+---------------------+---------------------+
| 1 | 1 | NULL | NULL | 2018-11-17 16:01:00 | 2018-11-17 16:01:00 |
| 1 | 2 | NULL | NULL | 2018-11-17 16:01:00 | 2018-11-17 16:01:00 |
+---------+---------+---------+---------+---------------------+---------------------+
2 rows in set (0.01 sec)