今回は記事にタグを付与する機能を追加します。この機能の実装で、多対多のリレーションを学びます。
ブログをやっている方にはお馴染みかと思いますが、タグの仕様は以下のようになります。
- 記事には複数のタグが付与できる(記事1件に対して、タグn件)
- タグは複数の記事を持てる(タグ1件に対して、記事n件)
- 記事は複数のタグに属する
- タグは複数の記事に属する
モデル
まずはタグのモデルを作成します。
php artisan make:model Tag
以下のファイルが作成されます。
app/Tag.php
モデルに対して、多対多のリレーションを実装します。
User モデルと Article モデルを1対多で関連付けた時は、Article モデルで belongsTo() メソッドを使いましたが、多対多の場合は belongsToMany() を使用します。
Article
<?php // app/Article.php namespace App; ... class Article extends Model { ... public function user() { return $this->belongsTo('App\User'); } // 追加 public function tags() { return $this->belongsToMany('App\Tag')->withTimestamps(); } }
Tag
<?php // app/Tag.php namespace App; use Illuminate\Database\Eloquent\Model; class Tag extends Model { protected $fillable = ['name']; public function articles() { return $this->belongsToMany('App\Article')->withTimestamps();; } }
belongsToMany() メソッドの第1引数には関連するモデル名を渡します。
第2引数は多対多の中間テーブル名を渡します。上記の例では第2引数は省略されています。省略された場合は、モデル名をアルファベット順で並べた物が中間テーブル名となります。中間テーブル名に規約から外れた物を指定したい時に、第2引数を指定します。
return $this->belongsToMany('App\Article'); return $this->belongsToMany('App\Article', 'article_tag'); // 上記は同じです。
第3、第4キーは中間テーブルの外部キーを指定します。省略された場合は、モデル名_idが外部キーとなります。外部キーに規約から外れた物を指定したい時に、第3、第4キーを指定します。
return $this->belongsToMany('App\Article', 'article_tag'); return $this->belongsToMany('App\Article', 'article_tag', 'article_id', 'tag_id'); // 上記は同じです。
また、中間テーブルのタイムスタンプを更新する為に、withTimestamps() を使用する必要があります。
詳細は公式サイトをご覧ください。
http://laravel.com/docs/5.6/eloquent-relationships#many-to-many
Migration
tags と article_tag テーブルを作成するマイグレーションを作成します。
php artisan make:migration create_tags_table php artisan make:migration create_article_tag_table
<?php // database/migrations/YYYY_MM_DD_XXXXXX_create_tags_table.php use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateTagsTable extends Migration { public function up() { Schema::create('tags', function (Blueprint $table) { $table->increments('id'); $table->string('name')->unique(); $table->timestamps(); }); } public function down() { Schema::dropIfExists('tags'); } }
<?php // database/migrations/YYYY_MM_DD_XXXXXX_create_article_tag_table.php use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateArticleTagTable extends Migration { public function up() { Schema::create('article_tag', function (Blueprint $table) { $table->increments('id'); $table->unsignedInteger('article_id'); $table->unsignedInteger('tag_id'); $table->timestamps(); $table->unique(['article_id', 'tag_id']); $table->foreign('article_id') ->references('id') ->on('articles') ->onDelete('cascade'); $table->foreign('tag_id') ->references('id') ->on('tags') ->onDelete('cascade'); }); } public function down() { Schema::dropIfExists('article_tag'); } }
タグテーブルと共に、記事とタグの中間テーブルも作成します。中間テーブルはテーブル名を関連するモデル名をアルファベット順で並べた名前(article_tag)にし、記事テーブルとタグテーブルへの外部キーを設定します。onDelete 制約を加えて、関連する記事もしくはタグが削除された時には中間テーブルのレコードも削除するように設定します。
マイグレートを実行します。
php artisan migrate
動作確認
tinker を使って、動作確認をしてみます。
タグの作成
$ php artisan tinker >>> $tag_diary = App\Tag::create(['name' => 'diary']) >>> $tag_work = App\Tag::create(['name' => 'work']) >>> $tag_hobby = App\Tag::create(['name' => 'hobby']) >>> $tag_family = App\Tag::create(['name' => 'family']) >>> App\Tag::all()->toArray() => [ [ "id" => "1", "name" => "diary", "created_at" => "2015-03-28 18:11:40", "updated_at" => "2015-03-28 18:11:40" ], [ "id" => "2", "name" => "work", "created_at" => "2015-03-28 18:11:57", "updated_at" => "2015-03-28 18:11:57" ], [ "id" => "3", "name" => "hobby", "created_at" => "2015-03-28 18:13:18", "updated_at" => "2015-03-28 18:13:18" ], [ "id" => "4", "name" => "family", "created_at" => "2015-03-28 18:13:45", "updated_at" => "2015-03-28 18:13:45" ] ] >>> App\Tag::pluck('name')->toArray(); => [ "diary", "work", "hobby", "family" ] >>>
記事にタグを関連付け
>>> $article = App\Article::first() >>> $article->tags()->attach($tag_diary->id) // ① >>> >>> DB::select('select * from article_tag') => [ { article_id: "1", tag_id: "1", created_at: "2015-03-28 21:07:56", updated_at: "2015-03-28 21:07:56" } ] >>> >>> $article = App\Article::first() >>> $article->tags->toArray() => [ [ "id" => "1", "name" => "diary", "created_at" => "2015-03-28 18:11:40", "updated_at" => "2015-03-28 18:11:40", "pivot" => [ "article_id" => "1", "tag_id" => "1", "created_at" => "2015-03-28 21:07:56", "updated_at" => "2015-03-28 21:07:56" ] ] ] >>>
多対多のモデルを挿入するには ① attach()メソッドを使用します。
タグ側から記事を参照
上記で関連付けたタグ側から記事を参照してみます。
>>> $tag = App\Tag::first(); >>> $tag->articles->toArray(); => [ [ "id" => "1", "user_id" => "3", "title" => "OMNIS ARCHITECTO ODIO REPELLAT ET VOLUPTATEM BEATAE.", "body" => "Minus magni est dignissimos est excepturi incidunt. Eligendi et consequatur sunt adipisci laborum corrupti repudiandae vero. Dolor eum perspiciatis enim non reiciendis.", "created_at" => "2015-03-22 17:24:56", "updated_at" => "2015-03-22 19:28:45", "published_at" => "2015-03-22 00:00:00", "pivot" => [ "tag_id" => "1", "article_id" => "1", "created_at" => "2015-03-28 21:07:56", "updated_at" => "2015-03-28 21:07:56" ] ] ] >>>
多対多のリレーション作成が上手くいきました。次回は画面を通して多対多を操作を行っていきます。