【Laravel8】記事の登録と同時に多対多のリレーション（中間テーブル）

記事の作成と同時にタグのようなものとリレーションさせたいと思い、調べながら実装した。
フォロー機能と違い、記事の作成と同時にリレーションさせるので少し時間がかかった。

本記事では、`articlesテーブル`、`tagsテーブル`、`article_tagテーブル`を作成する（細かいバリデーション等は省く）。
タグのデータは事前に作成し、記事作成フォーム内で`checkbox`としてinputさせる。

##テーブル作成
まずは`articlesテーブル`と`tagsテーブル`を作成し、編集していく。

```
$ php artisan make:model Article --migration
$ php artisan make:model Tag --migration
```

```:2021_03_10_161118_create_articles_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateArticlesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('articles', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->timestamps();
        });

    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('articles');
    }
}

```

```:2021_03_10_212128_create_tags_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateTagsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('tags', function (Blueprint $table) {
            $table->id();
            $table->string('name')->unique();  //念の為、uniqueでnameのかぶりをなくす
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('tags');
    }
}

```

`article_tagテーブル`も作成（modelは不要）
規約通りに作成するならば、中間テーブルの名前は関係するモデル名（articleとtag）をアルファベット順に並べた「article_tag」となる。もし別のテーブル名にするならば、上述したbelongsToManyメソッドの第２引数に設定する。

```
$ php artisan make:migration create_article_tag_table
```
```:2021_03_10_160720_create_article_tag_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateArticleTagTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('article_tag', function (Blueprint $table) {
            $table->id();
            //外部キー制約
            $table->unsignedBigInteger('article_id');
            $table->foreign('article_id')
                ->references('id')
                ->on('articles')
                ->onDelete('cascade');
            $table->unsignedBigInteger('tag_id');
            $table->foreign('tag_id')
                ->references('id')
                ->on('tags')
                ->onDelete('cascade');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('article_tag');
    }
}
```
マイグレーションの実行

```
$ php artisan migrate
```

次に、モデルの編集（Laravel8では、Modelディレクトリが自動生成される）

```:app/Models/Article.php
<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
 
class Article extends Model
{
    use HasFactory;
 
    protected $fillable = [
        'title',
    ];
 
    public function tags()
    {
        // belongsToManyメソッドで多対多として関連付け
        return $this->belongsToMany('App\Models\Tag')->withTimestamps();
    }
}
```
```:app/Models/Tag.php
<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
 
class Tag extends Model
{
    use HasFactory;
 
    protected $fillable = [
        'name',
    ];
 
    public function articles()
    {
        // belongsToManyメソッドで多対多として関連付け
        return $this->belongsToMany('App\Models\Article')->withTimestamps();
    }
}
```

####タグのデータ生成
登録方法は何でも良いが、今回はtinkerで作成

```
$ php artisan tinker
Psy Shell v0.10.6 (PHP 7.3.11 — cli) by Justin Hileman
>>> $tag = new App\Models\Tag();
>>> $tag->name = 'foo';
>>> $tag->save(); //nameを変えて何個か生成する
```

##コントローラーの作成
`Articleコントローラー`を作成（create,store,edit,updateのみ）

```
$ php artisan make:controller ArticleController
```

```:app/Http/Controllers/ArticleController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Models\Article;
use App\Models\Tag;
use App\Http\Requests\ArticleRequest;

class PostController extends Controller
{
    public function create()
    {
        $tags = Tag::all();
        return view('article.create', compact('tags'));
    }

    public function store(ArticleRequest $request)
    {
        $article = Article::create($request->all());
        $article->tags()->attach(request()->tags);  //attachメソッドを利用して、中間テーブルにデータを追加
        return back();
    }

    public function edit(Article $article)
    {
        $tags = Tag::all();
        return view('article.edit', compact('article', 'tags'));
    }

    public function update(ArticleRequest $request, Article $article)
    {
        $article->fill($request->all())->save();
        $article->tags()->sync(request()->tags);  //syncメソッドを利用して、中間テーブルの更新
        return back();
    }
}
```

####ArticleRequestの作成
バリデーションのため作成

```
$ php artisan make:request ArticleRequest
```

```:app/Http/Requests/ArticleRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class ArticleRequest extends FormRequest
{
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'title' => 'required|max:20',
        ];
    }

    public function attributes()
    {
        return [
            'title' => 'タイトル',
        ];
    }
}
```

####ルーティングの追加
```:routes/web.php
Route::resource('/articles', 'ArticleController')->only(['create', 'store', 'edit', 'update']);
```

##ビューの作成
Article新規作成画面

```:resources/views/articles/create.blade.php
<h1>記事新規作成</h1>
 
<form action="{{ route('article.store')}}" method="POST">
    @csrf
    <p>タイトル<input type="text" name="title" value="{{ old('title') }}"></p>
    <p>
        @foreach($tags as $tag) //用意したタグのデータ（name）をすべて表示
        <label for="{{ $tag->id }}">
            <input type="checkbox" id="{{ $tag->id }}" name="tags[]" value="{{ $tag->id }}">{{ $tag->name }}
        </label>
        @endforeach
    </p>
    <input type="submit" value="登録する">
</form>
```
Article編集画面

```:resources/views/articles/edit.blade.php
<h1>記事編集画面</h1>
 
<form action="{{ route('article.update',$article->id)}}" method="POST">
    @csrf
    @method('PUT')
    <p>タイトル<input type="text" name="title" value="{{ $article->title }}"></p>
    <p>
        @foreach ($tags as $tag)
        <label for="{{ $tag->id }}">
            <input type="checkbox" id="{{ $tag->id }}" name="tags[]" value="{{$tag->id}}" 
　　　　　　　　{{ $article->tags->contains('id', $tag->id) ? 'checked="checked"' : '' }}  //contains()メソッドを使って、指定したidが含まれているか確認
　　　　　　　>
            {{ $tag->name }}
        </label>
        @endforeach
    </p>
    <input type="submit" value="編集する">
</form>
```

##つまったところ
valueに何も記述しておらず、そのエラーに気づかずに１日奮闘した。valueがないことは、文字通り値がないということであり、textの場合（見た目的に）空白になる。`input type`が`checkbox`なので気付きずらかった。

```
<input type="checkbox" id="{{ $tag->id }}" name="tags[]" value="{{ $tag->id }}"<=これがなかった
```
基本を理解していないことがわかったが、逆に、この程度の基本を理解していれば、それなりにできると思った。

##参考
https://noumenon-th.net/programming/2020/11/05/many-to-many/
https://i-purple-u.com/2020/03/18/%E3%80%90laravel%E3%80%91%E9%85%8D%E5%88%97%E3%81%AE%E3%83%81%E3%82%A7%E3%83%83%E3%82%AF%E3%83%9C%E3%83%83%E3%82%AF%E3%82%B9%E3%81%ABchecked%E3%82%92%E3%81%A4%E3%81%91%E3%82%8B/