LoginSignup
0
1

More than 3 years have passed since last update.

Laravelで簡単なCMSを作成(記事へのタグ付け)

Last updated at Posted at 2021-05-03

Laravelで簡単なCMSを作成してみます。

環境構築から記事の登録・更新は下記の記事を参考にどうぞ。

今回は、作成した記事にタグを付けてみます。

リソースコントローラ作成

Laravelのartisanコマンドを使ってリソースの作成、読み取り、更新、または削除を処理するコントローラを作成できます。

Tagモデルをリソースとして、作成・読み取りができるように下記コマンドを実行してリソースコントローラーとモデルを作成します。

php artisan make:controller TagsController --resource --model=Tag
php artisan make:model Tag

TagsControllerを作成したらindexcreatestoreメソッドを追加します。

app/Http/Controllers/TagsController.php
<?php

namespace App\Http\Controllers;

use App\Models\Tag;
use Illuminate\Http\Request;

class TagsController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        $tags = Tag::all();
        return view('tags.index', ['tags' => $tags]);
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        return view('tags.add');
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        $article = new Tag;
        $article->title = $request->title;
        $article->save();

        return redirect('/tags');
    }

    // その他のメソッド
}

リソースルートを登録します。

routes/web.php
use App\Http\Controllers\TagsController;

Route::resource('tags', TagsController::class);

以下のルートが自動的に作成されます。

動詞 URI アクション ルート名
GET /tags index tags.index
GET /tags/create create tags.create
POST /tags store tags.store
GET /tags/{tag} show tags.show
GET /tags/{tag}/edit edit tags.edit
PUT/PATCH /tags/{tag} update tags.update
DELETE /tags/{tag} destroy tags.destroy

このうち今のところ、indexcreatestore以外は使用しないのでルートから除外します。

routes/web.php
// Route::resource('tags', TagsController::class);
Route::resource('tags', TagsController::class)->only([
    'index', 'create', 'store',
]);

Viewを作成

add.blade.phpindex.blade.phpを作成してtagの登録から一覧で確認できるようにします。

resources/views/tags/index.blade.php
<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            {{ __('Tags') }}
        </h2>
    </x-slot>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
                <div class="p-6 bg-white border-b border-gray-200">

                <table class="table-auto">
                    <thead>
                        <tr>
                            <th>Title</th>
                            <th>Updated</th>
                        </tr>
                    </thead>
                    <tbody>
                        @foreach($tags as $tag)
                            <tr>
                                <td>{{$tag->title}}</td>
                                <td>{{$tag->updated_at}}</td>
                            </tr>
                        @endforeach

                    </tbody>
                </table>

                </div>
            </div>
        </div>
    </div>
</x-app-layout>
resources/views/tags/add.blade.php
<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            {{ __('Tags') }}
        </h2>
    </x-slot>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
                <div class="p-6 bg-white border-b border-gray-200">

                    <form action="/tags" method="post">
                        @csrf
                        <div class="grid grid-cols-1 gap-6">
                            <label class="block">
                                <span class="text-gray-700">Title</span>
                                <input type="text" name="title" class="mt-1 block w-full">
                            </label>
                            <input type="submit" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline" value="send">
                        </div>
                    </form>

                </div>
            </div>
        </div>
    </div>
</x-app-layout>

/tags/createにアクセスしてtagをいくつか登録します。

スクリーンショット 2021-05-02 230958.png
スクリーンショット 2021-05-03 093226.png

記事とタグを関連付ける

記事の登録と更新画面にタグを選択できるようにします。articles/add.blade.phparticles/edit.blade.phpに以下のようにtag用のチェックボックスを追加します。

<label class="block">
    <span class="text-gray-700">Tag</span>
    <select class="mt-1 block w-full" rows="3" name="tags">
        @foreach($tags as $tag)
        <option value="{{$tag->id}}">{{$tag->title}}</option>
        @endforeach
    </select>
</label>

モデルを関係付ける

articlesテーブルとtagsテーブルを関連付けます。1記事に複数タグ付けすることができ、また1つのタグに対して複数の記事を関連付けることができるので、articlesテーブルとtagsテーブルは多対多の関係になります。多対多の関係はbelongsToManyメソッドで定義します。

ArticleModelのほうにbelongsToManyメソッドでtagsテーブルと関連付けます。

app/Models/ArticleModel.php
use App\Models\Tags;

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

中間テーブルに登録する

多対多の関係であるarticlesテーブルとtagsテーブルを関連付けるために中間テーブルのarticle_tagテーブルを使用します。記事の作成・更新時に中間テーブルにデータを更新していきます。attachメソッドを使用することでリレーションの中間テーブルにデータを挿入できます。syncメソッドを使用すると、引数に渡されたIDが中間テーブルに登録され、引数に渡されなかったIDは中間テーブルから物理削除されます。

リレーションの更新処理をArticlesControllerに追加します。

app/Http/Controllers/ArticlesController.php
    public function create(Request $request)
    {
        $article = Article::create([
            'title' => $request->title,
            'body' => $request->body,
            'user_id' => Auth::id(),
            'slug' => $request->title,
        ]);

        // 中間テーブルにtagを登録
        $article->tags()->attach($request->tags);

        return redirect('/articles');
    }

    public function update(Request $request, Article $article)
    {
        $article->fill($request->all())->save();
        $article->tags()->sync($request->tags);
        return redirect('/articles');
    }

中間テーブルへの登録時にtimestampを追加する場合は、withTimestampsメソッドを使用します。

app/Models/ArticleModel.php
<?php

namespace App\Models;

use App\Models\Tag;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Article extends Model
{
    use HasFactory;

    protected $fillable = [
        'title',
        'body',
        'slug',
        'user_id',
    ];

    protected $casts = [
        'user_id' => 'integer'
    ];

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

一覧ページ修正

記事一覧ページに、記事に関連付けられているタグも表示させます。

resources/views/articles/index.blade.php
<table class="table-fixed">
    <thead>
        <tr>
            <th class="w-1/4">Title</th>
            <th class="w-1/4">Tag</th>
            <th class="w-1/4">Author</th>
            <th class="w-1/4">Updated</th>
        </tr>
    </thead>
    <tbody>
        @foreach($articles as $article)
            <tr>
                <td><a href="/article/edit/{{$article->id}}">{{$article->title}}</a></td>
                <td>
                    @foreach($article->tags as $tag)
                    <div>
                        {{$tag->title}}
                    </div>
                    @endforeach
                </td>
                <td>{{$article->user_id}}</td>
                <td>{{$article->updated_at}}</td>
            </tr>
        @endforeach

    </tbody>
</table>

N + 1問題

laravelの動的プロパティは「遅延読み込み」なので、N+1問題が発生します。今回のように動的プロパティにアクセスすることが分かっている場合は、Eagerロードを使用してあらかじめモデルをロードすることでN+1問題を回避することができます。

記事一覧取得時にEagerロードを使用する場合と使用しない場合で発行されるSQLを比較してみます。まずEagerロードを使用しない場合、

app/Http/Controllers/ArticlesController.php
    public function index()
    {
        $articles = Article::all();
        return view('articles.index', ['articles' => $articles]);
    }

発行されるSQLは、まず

select * from "articles"

で記事を全件取得しその後記事ごとに、

select "tags".*, "article_tag"."article_id" as "pivot_article_id", "article_tag"."tag_id" as "pivot_tag_id", "article_tag"."created_at" as "pivot_created_at", "article_tag"."updated_at" as "pivot_updated_at" from "tags" inner join "article_tag" on "tags"."id" = "article_tag"."tag_id" where "article_tag"."article_id" = article_id

が発行されます。合計で記事の件数 + 1本のSQLが発行されます。

スクリーンショット 2021-05-03 085415.png
一方、Eagerロードを使用した場合、

app/Http/Controllers/ArticlesController.php
    public function index()
    {
        // $articles = Article::all();
        $articles = Article::with('tags')->get();
        return view('articles.index', ['articles' => $articles]);
    }

発行されるSQLは2本となり激減します。
スクリーンショット 2021-05-03 090043.png

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1