Laravel で電子掲示板を作ろう ⑥(コメントの編集機能と削除機能の追加)

皆さん、こんにちは。管理人です。今回はシリーズ「Laravel で電子掲示板を作ろう」の第6回目となります。

コメント編集機能の追加

それでは、ここからはコメントの編集機能の実装に入っていきたいと思います。まず、routes/web.php に二行を追加して次のようにします。

<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\PostController;
use App\Http\Controllers\CommentController;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', [PostController::class, 'index'])->name('index');
Route::get('/post/{post_id}', [PostController::class, 'post'])->name('post');

Route::middleware([
    'auth:sanctum',
    config('jetstream.auth_session'),
    'verified'
])->group(function () {
    Route::get('/dashboard', [PostController::class, 'show'])->name('dashboard');
    Route::get('/create', function () {
        return view('create');
    })->name('create');
    Route::post('/store', [PostController::class, 'store'])->name('store');
    Route::get('/edit/{post_id}', [PostController::class, 'edit'])->name('edit');
    Route::post('/update/{post_id}', [PostController::class, 'update'])->name('update');
    Route::post('/delete/{post_id}', [PostController::class, 'delete'])->name('delete');
    Route::post('/store_comment', [CommentController::class, 'store'])->name('store_comment');
    Route::get('/edit_comment/{comment_id}', [CommentController::class, 'edit'])->name('edit_comment'); // 追記
    Route::post('/update_comment/{comment_id}', [CommentController::class, 'update'])->name('update_comment'); // 追記
});

続いて、ファイル resources/views/edit_comment.blade.php を作成し、内容は次のようにします。

<x-app-layout>
    <x-slot name="title">コメントの編集</x-slot>

    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            コメントの編集
        </h2>
    </x-slot>

    <div class="py-6">
        <div class="max-w-2xl px-6 lg:px-8">
            <form class="grid grid-cols-1 gap-4" method='POST' action="{{ route('update_comment', $comment['id']) }}" enctype="multipart/form-data">
                @csrf
                <label class="block">
                    <span class="text-gray-700">内容</span>
                    <textarea id="markdown-editor" class="block w-full mt-1 rounded-md" name="content" rows="5">{{ $comment['content'] }}</textarea>
                </label>
                <button type='submit' class="w-20 bg-blue-600 hover:bg-blue-500 text-white rounded px-4 py-2">更新</button>
            </form>
        </div>
    </div>
</x-app-layout>

続いて、app/Http/Controllers/CommentController.php に次のように edit アクションを追加します。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Auth;
use App\Models\Comment;

class CommentController extends Controller
{
    public function store(Request $request)
    {
        $data = $request->all();
        $post_id = $data['post_id'];

        Comment::insertGetID([
            'content' => $data['content'], 
            'post_id' => $post_id, 
            'user_id' => Auth::id(), 
        ]);

        return redirect()->route('post', compact('post_id'));
    }

    /**
     * このアクションを追加
     */
    public function edit($comment_id)
    {
        $comment = Comment::where('id', $comment_id)
                          ->where('user_id', Auth::id())
                          ->first();

        return view('edit_comment', compact('comment'));
    }
}
ここでビューを確認しても良いです。phpMyAdmin でコメントの id を確認し、ブラウザで
http://localhost:8000/edit_comment/*
にアクセスしてみましょう。* にはコメントの id が入ります。
ただし、ログイン中のユーザーのコメントでないとエラーとなりますので注意しましょう。

さて、この段階でコメントを編集して更新ボタンをクリックしてもエラーとなります。まだ Comment コントローラーに update アクションを追加していないからです。なので、次のようにして Comment コントローラーに update アクションを追加しましょう。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Auth;
use App\Models\Comment;

class CommentController extends Controller
{
    public function store(Request $request)
    {
        $data = $request->all();
        $post_id = $data['post_id'];

        Comment::insertGetID([
            'content' => $data['content'], 
            'post_id' => $post_id, 
            'user_id' => Auth::id(), 
        ]);

        return redirect()->route('post', compact('post_id'));
    }

    public function edit($comment_id)
    {
        $comment = Comment::where('id', $comment_id)
                          ->where('user_id', Auth::id())
                          ->first();

        return view('edit_comment', compact('comment'));
    }

    /**
     * このアクションを追加
     */
    public function update(Request $request, $comment_id)
    {
        $data = $request->all();
        $query = Comment::where('id', $comment_id)->where('user_id', Auth::id());

        // ユーザーが不正な操作で他人のコメントを編集しようとしていないかチェック
        if ($query->exists()) {
            $query->update(['content' => $data['content']]); // コメントの更新
            $post_id = Comment::where('id', $comment_id)->first()->post_id; // 該当のコメントがつく記事の id を取得
            return redirect()->route('post', compact('post_id')); // 該当の記事へリダイレクト
        } else {
            abort(500); // サーバーエラー
        }
    }
}

この段階でコメントが編集できるか確認しても良いです。ログイン中のユーザーが作成したコメントをひとつ選び、そのコメントを編集して「更新」ボタンをクリックすると、そのコメントがつく記事にリダイレクトされて、コメントの内容が変わっていることが確認できるかと思います。

さて、作成された各コメントに編集ボタンを設置したいと思います。ただし、各コメントについて、そのコメントを作成したユーザーのみにそのコメントの編集ボタンが見えるようにしたいと思います。

まず、app/Models/Comment.php と app/Models/User.php を次のように編集します。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use App\Models\Post;
use App\Models\User; // 追記

class Comment extends Model
{
    use HasFactory;

    public function post()
    {
        return $this->belongsTo(Post::class);
    }

    /**
     * 追記
     */
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}
<?php

namespace App\Models;

use App\Models\Comment; // 追記
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Fortify\TwoFactorAuthenticatable;
use Laravel\Jetstream\HasProfilePhoto;
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens;
    use HasFactory;
    use HasProfilePhoto;
    use Notifiable;
    use TwoFactorAuthenticatable;

    /**
     * The attributes that are mass assignable.
     *
     * @var string[]
     */
    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var array
     */
    protected $hidden = [
        'password',
        'remember_token',
        'two_factor_recovery_codes',
        'two_factor_secret',
    ];

    /**
     * The attributes that should be cast.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

    /**
     * The accessors to append to the model's array form.
     *
     * @var array
     */
    protected $appends = [
        'profile_photo_url',
    ];

    /**
     * 追記
     */
    public function comments()
    {
        return $this->hasMany(Comment::class);
    }
}

そして、resources/views/post.blade.php を次のように編集します。

<x-guest-layout>
    <x-slot name="title">{{ $post['title'] }}</x-slot>

    <h1>{{ $post['title'] }}</h1>
    <p>{{ $post['content'] }}</p>

    <h2>みんなのコメント</h2>
    @forelse ($comments as $comment)
        <div class="bg-gray-300 p-2 mb-4">
            <p class="my-2">{{ $comment['content'] }}</p>
            @auth
                @if ($comment->user->id === Auth::id())
                    <button class="bg-green-600 hover:bg-green-500 text-white rounded px-4 py-2" 
                            type="button" onclick="location.href='{{ route('edit_comment', $comment['id']) }}'">
                        編集
                    </button>
                @endif
            @endauth
        </div>
        
    @empty
        <p>コメントはまだありません。</p>
    @endforelse

    @auth
        <h2>コメントを投稿する</h2>
        <form class="grid grid-cols-1 gap-6 text-black" method='POST' action="{{ route('store_comment') }}" enctype="multipart/form-data">
            @csrf
            <input type='hidden' name='post_id' value="{{ $post['id'] }}">
            <label class="block">
                <textarea name='content'
                    class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
                    rows="3"></textarea>
            </label>
            <button type='submit' class="w-20 bg-blue-600 hover:bg-blue-500 text-white rounded px-4 py-2">投稿</button>
        </form>
    @else
        <p>ログインするとコメントを投稿することができます。</p>
    @endauth
</x-guest-layout>

このコードについて少し解説しますと、12行目の

@if ($comment->user->id === Auth::id())

でそのコメントを作成したユーザーの ID とログイン中のユーザーの ID が一致するか否かを判定し、一致すれば編集ボタンを表示する、という処理になっています。

ここでブラウザから記事にアクセスしてみると、ログイン中のユーザーが作成したコメントにだけ編集ボタンがついていることが確認できます。
試しに編集ボタンをクリックしてみると、コメントの編集ができる画面に移ります。コメントを適当に編集して「更新」ボタンをクリックすると
コメントの内容が変化したことが分かります。

コメントを削除するボタンの追加

続いて、ログイン中のユーザーが、自身で作成したコメントを削除できるようにしたいと思います。routes/web.php に一行を追加して次のようにします。

<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\PostController;
use App\Http\Controllers\CommentController;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', [PostController::class, 'index'])->name('index');
Route::get('/post/{post_id}', [PostController::class, 'post'])->name('post');

Route::middleware([
    'auth:sanctum',
    config('jetstream.auth_session'),
    'verified'
])->group(function () {
    Route::get('/dashboard', [PostController::class, 'show'])->name('dashboard');
    Route::get('/create', function () {
        return view('create');
    })->name('create');
    Route::post('/store', [PostController::class, 'store'])->name('store');
    Route::get('/edit/{post_id}', [PostController::class, 'edit'])->name('edit');
    Route::post('/update/{post_id}', [PostController::class, 'update'])->name('update');
    Route::post('/delete/{post_id}', [PostController::class, 'delete'])->name('delete');
    Route::post('/store_comment', [CommentController::class, 'store'])->name('store_comment');
    Route::get('/edit_comment/{comment_id}', [CommentController::class, 'edit'])->name('edit_comment');
    Route::post('/update_comment/{comment_id}', [CommentController::class, 'update'])->name('update_comment');
    Route::post('/delete_comment/{comment_id}', [CommentController::class, 'delete'])->name('delete_comment'); // 追記
});

続いて、CommentController に次のように delete アクションを追加します。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Auth;
use App\Models\Comment;

class CommentController extends Controller
{
    public function store(Request $request)
    {
        $data = $request->all();
        $post_id = $data['post_id'];

        Comment::insertGetID([
            'content' => $data['content'], 
            'post_id' => $post_id, 
            'user_id' => Auth::id(), 
        ]);

        return redirect()->route('post', compact('post_id'));
    }

    public function edit($comment_id)
    {
        $comment = Comment::where('id', $comment_id)
                          ->where('user_id', Auth::id())
                          ->first();

        return view('edit_comment', compact('comment'));
    }

    public function update(Request $request, $comment_id)
    {
        $data = $request->all();
        $query = Comment::where('id', $comment_id)->where('user_id', Auth::id());

        // ユーザーが不正な操作で他人のコメントを編集しようとしていないかチェック
        if ($query->exists()) {
            $query->update(['content' => $data['content']]); // コメントの更新
            $post_id = Comment::where('id', $comment_id)->first()->post_id; // 該当のコメントがつく記事の id を取得
            return redirect()->route('post', compact('post_id')); // 該当の記事へリダイレクト
        } else {
            abort(500); // サーバーエラー
        }
    }

    /**
     * このアクションを追加
     */
    public function delete($comment_id)
    {
        $post_id = Comment::where('id', $comment_id)->first()->post_id;
        $query = Comment::where('id', $comment_id)->where('user_id', Auth::id());

        // ユーザーが不正な操作で、他人のコメントを削除しないかチェック
        if ($query->exists()) {
            Comment::destroy($comment_id);
            return redirect()->route('post', compact('post_id')); // 該当の記事へリダイレクト
        } else {
            abort(500); // サーバーエラー
        }
    }
}

そして、resources/views/post.blade.php に form タグを追加して次のようにします。

<x-guest-layout>
    <x-slot name="title">{{ $post['title'] }}</x-slot>

    <h1>{{ $post['title'] }}</h1>
    <p>{{ $post['content'] }}</p>

    <h2>みんなのコメント</h2>
    @forelse ($comments as $comment)
        <div class="bg-gray-300 p-2 mb-4">
            <p class="my-2">{{ $comment['content'] }}</p>
            @auth
                @if ($comment->user->id === Auth::id())
                    <button class="bg-green-600 hover:bg-green-500 text-white rounded px-4 py-2" 
                            type="button" onclick="location.href='{{ route('edit_comment', $comment['id']) }}'">
                        編集
                    </button>

                    <!-- この form タグを追加 -->
                    <form method='POST' action="{{ route('delete_comment', $comment['id']) }}">
                        @csrf
                        <button type='submit' class="bg-red-600 hover:bg-red-500 text-white rounded px-4 py-2" 
                                onclick='return confirm("コメント「{{ $comment->content }}」を削除しますか?");'>
                            削除
                        </button>
                    </form>
                @endif
            @endauth
        </div>
        
    @empty
        <p>コメントはまだありません。</p>
    @endforelse

    @auth
        <h2>コメントを投稿する</h2>
        <form class="grid grid-cols-1 gap-6 text-black" method='POST' action="{{ route('store_comment') }}" enctype="multipart/form-data">
            @csrf
            <input type='hidden' name='post_id' value="{{ $post['id'] }}">
            <label class="block">
                <textarea name='content'
                    class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
                    rows="3"></textarea>
            </label>
            <button type='submit' class="w-20 bg-blue-600 hover:bg-blue-500 text-white rounded px-4 py-2">投稿</button>
        </form>
    @else
        <p>ログインするとコメントを投稿することができます。</p>
    @endauth
</x-guest-layout>

さて、ここまで来たら、ブラウザで記事にアクセスしてみましょう。

するとこのように、ログイン中のユーザーが作成したコメントに削除ボタンがついていることが確認できます。削除ボタンをクリックして OK をクリックすると、該当するコメントが削除され、もとの記事にリダイレクトされます。

コメント

タイトルとURLをコピーしました