PHP
laravel

Laravel 5.5 で多対多(Many To Many)リレーションを使ってタグ付き掲示板を作る

はじめに

Laravelで多対多(n:n, Many To Many)リレーションはとても簡単に扱うことが出来ます。
今回は、ひとつの投稿に対して複数のタグを付与できる掲示板をサンプルに解説してみます。

screen.gif

https://github.com/naga3/laravel-sample-manytomany

プロジェクト作成

tagbbsプロジェクトを作成します。
今回はLaravel Installerを使いましたが、各自の環境に合わせてください。

Laravel Installerの導入

composer global require laravel/installer

プロジェクト作成

laravel new tagbbs

データベース設定

データベース作成

データベースを作成し、.envファイルを編集します。
作成したDB名がtagbbs、ユーザー名rootパスワードなしの場合以下のようになります。

DB_DATABASE=tagbbs
DB_USERNAME=root
DB_PASSWORD=

モデル・マイグレーション作成

まず投稿用のモデルPostとタグ用のモデルTagを作成します。

php artisan make:model Post -m
php artisan make:model Tag -m

-mオプションでマイグレーションファイルが同時に作成されます。

また、中間テーブルはモデルが不要なのでマイグレーションファイルのみを作成します。

php artisan make:migration create_post_tag_table --create=post_tag

post_tagという名前にすることで、自動的にpostsテーブルとtagsテーブルの中間テーブルであることを示すことができます(明示的に示すこともできます)。

マイグレーション編集

database/migrationsディレクトリにある、各マイグレーションファイルのカラムを編集します。

xxxx_create_posts_table.php

投稿テーブルです。

        Schema::create('posts', function (Blueprint $table) {
            $table->increments('id');
            $table->string('body');
            $table->timestamps();
        });

bodyカラムが本文です。

xxxx_create_tags_table.php

タグテーブルです。

        Schema::create('tags', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->timestamps();
        });

nameカラムにタグ名が入ります。

xxxx_create_post_tag_table.php

投稿テーブルとタグテーブルを関連付ける中間テーブルです。

        Schema::create('post_tag', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('post_id');
            $table->integer('tag_id');
        });

post_idpostsテーブルのidtag_idtagsテーブルのidです。

マイグレーション

マイグレーションを実行し、テーブルを作成します。

php artisan migrate

posts, tags, post_tagテーブルが定義通り作成されていることを確認してください。

タグデータの生成

今回はタグ登録機能を省略し、Laravelのシーディングで代用します。

database/seeds/DatabaseSeeder.phprunメソッドを書きます。

    public function run()
    {
        $tags = ['うどん', 'そば', 'ラーメン', 'フォー'];
        foreach ($tags as $tag) App\Tag::create(['name' => $tag]);
    }

シーディングを実行します。

php artisan db:seed

tagsテーブルに4レコード登録されていることを確認してください。

多対多(Many To Many)リレーションの設定

モデルクラスに多対多のリレーションを設定します。

app/Post.phpに以下のメソッドを追加します。

    public function tags()
    {
        return $this->belongsToMany(Tag::class);
    }

これで、tagsメソッドにより投稿に紐付けられたタグ一覧を取得できるようになります。

ルートの設定

次にルートを設定します。

routes/web.phpRoute::get呼び出しに、投稿一覧とタグ一覧を付与します。

Route::get('/', function () {
    return view('welcome', ['posts' => App\Post::all(), 'tags' => App\Tag::all()]);
});

さらに、routes/web.phpRoute::postを追加します。

Route::post('/', function () {
    $post = new App\Post();
    $post->body = request()->body;
    $post->save();
    $post->tags()->attach(request()->tags);
    return redirect('/');
});

投稿フォームから本文取得してpostsテーブルに保存し、さらに投稿に付与されたタグも保存しています。

$post->tags()で投稿に紐付いたタグ一覧を取得出来ます。attachメソッドにタグIDの配列を渡すことによって一気にタグを登録しています。

attachメソッドの代わりにsyncメソッドを使うと、指定したIDのみを保存し、それ以外を削除してくれます。

ビューの設定

resources/views/welcome.blade.phpを以下のようにします。

<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <title>Tag BBS</title>
</head>
<body>
    <form method="post">
        {{ csrf_field() }}
        @foreach ($tags as $tag)
            <input type="checkbox" name="tags[]" value="{{ $tag->id }}">{{ $tag->name }}
        @endforeach
        <input name="body">
        <button>投稿</button>
    </form>
    @foreach ($posts as $post)
        <hr>
        <p>Tags: @foreach ($post->tags as $tag) {{ $tag->name }} @endforeach </p>
        <p>{{ $post->body }}</p>
    @endforeach
</body>
</html>

チェックを入れたチェックボックスがタグIDの配列としてPOSTされるようにして、attachメソッドにそのまま渡せるようにしています。

実行

php artisan serve

などで実行し、ブラウザでタグ付きの登録ができることを確認します。