Блог на Laravel 7, часть 10. Личный кабинет — CRUD-операции над постами и комментариями

12.01.2021

Теги: LaravelMySQLPHPWeb-разработкаБазаДанныхБлогПанельУправленияПользовательПрактикаФреймворкШаблонСайта

Теперь поработаем над личным кабинетом обычного пользователя. Здесь пользователь сможет увидеть все свои посты и комментарии. Еще не опубликованные посты и комментарии можно редактировать и удалять. Но после проверки администратором и публикации у пользователя больше не будет такой возможности. Чтобы личный кабинет визуально отличался — создадим еще один layout-шаблон.

namespace App\Http\Controllers\User;

use App\Http\Controllers\Controller;

class IndexController extends Controller {
    public function __invoke() {
        return view('user.index');
    }
}

Шаблон resporces/views/user/index.blade.php:

@extends('layout.user', ['title' => 'Личный кабинет'])

@section('content')
    <h1>Личный кабинет</h1>

    <p>Добрый день {{ auth()->user()->name }}!</p>

    @perm('create-post')
        <a href="{{ route('user.post.create') }}" class="btn btn-success">
            Новая публикация
        </a>
    @endperm
        <a href="{{ route('user.post.index') }}" class="btn btn-primary">
            Ваши публикации
        </a>
        <a href="{{ route('user.comment.index') }}" class="btn btn-primary">
            Ваши комментарии
        </a>
@endsection

Мы здесь используем layout-шаблон resources/views/layout/user.blade.php:

<!doctype html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>{{ $title ?? 'Личный кабинет' }}</title>
    <link href="{{ asset('css/app.css') }}" rel="stylesheet">
    <link rel="stylesheet" href="https://pro.fontawesome.com/releases/v5.10.0/css/all.css"/>
    <script src="{{ asset('js/app.js') }}"></script>
</head>
<body>
<div class="container">
    <nav class="navbar navbar-expand-lg navbar-dark bg-success mb-4">
        <!-- Логотип и кнопка «Гамбургер» -->
        <a class="navbar-brand" href="{{ route('user.index') }}">Личный кабинет</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse"
                data-target="#navbar-blog" aria-controls="navbar-blog"
                aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>
        <!-- Основная часть меню (может содержать ссылки, формы и прочее) -->
        <div class="collapse navbar-collapse" id="navbar-blog">
            <!-- Этот блок расположен слева -->
            <ul class="navbar-nav mr-auto">
                <li class="nav-item">
                    <a class="nav-link" href="{{ route('blog.index') }}">Перейти к блогу</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="{{ route('user.post.index') }}">Ваши публикации</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="{{ route('user.comment.index') }}">Ваши комментарии</a>
                </li>
            </ul>
            <!-- Этот блок расположен справа -->
            <ul class="navbar-nav ml-auto">
                @guest
                    <li class="nav-item">
                        <a class="nav-link" href="{{ route('auth.login') }}">Войти</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="{{ route('auth.register') }}">Регистрация</a>
                    </li>
                @else
                    <li class="nav-item">
                        <a class="nav-link" href="{{ route('user.index') }}">Личный кабинет</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="{{ route('auth.logout') }}">Выйти</a>
                    </li>
                @endif
            </ul>
        </div>
    </nav>

    <div class="row">
        <div class="col">
            @if ($message = session('success'))
                <div class="alert alert-success alert-dismissible mt-0" role="alert">
                    <button type="button" class="close" data-dismiss="alert" aria-label="Закрыть">
                        <span aria-hidden="true">&times;</span>
                    </button>
                    {{ $message }}
                </div>
            @endif

            @if ($errors->any())
                <div class="alert alert-danger alert-dismissible mt-4" role="alert">
                    <button type="button" class="close" data-dismiss="alert" aria-label="Закрыть">
                        <span aria-hidden="true">&times;</span>
                    </button>
                    <ul class="mb-0">
                        @foreach ($errors->all() as $error)
                            <li>{{ $error }}</li>
                        @endforeach
                    </ul>
                </div>
            @endif

            @yield('content')
        </div>
    </div>
</div>
</body>
</html>

Нам потребуется еще два ресурсных контроллера PostController и CommentController для CRUD-операций над постами и комментариями.

CRUD-операции над постами блога

Новые маршруты:

/*
 * Личный кабинет пользователя
 */
Route::group([
    'as' => 'user.', // имя маршрута, например user.index
    'prefix' => 'user', // префикс маршрута, например user/index
    'namespace' => 'User', // пространство имен контроллеров
    'middleware' => ['auth'] // один или несколько посредников
], function () {
    /*
     * Главная страница личного кабинета
     */
    Route::get('index', 'IndexController')
        ->name('index');
    /*
     * CRUD-операции над постами пользователя
     */
    Route::resource('post', 'PostController');
});

Контроллер PostController:

> php artisan make:controller User/PostController --resource --model=Post
namespace App\Http\Controllers\User;

use App\Http\Controllers\Controller;
use App\Http\Requests\PostRequest;
use App\Post;
use Illuminate\Http\Request;

class PostController extends Controller {

    public function __construct() {
        $this->middleware('perm:create-post')->only(['create', 'store']);
    }

    /**
     * Список всех постов пользователя
     */
    public function index() {
        $posts = Post::whereUserId(auth()->user()->id)->orderByDesc('created_at')->paginate();
        return view('user.post.index', compact('posts'));
    }

    /**
     * Показывает форму создания поста
     */
    public function create() {
        return view('user.post.create');
    }

    /**
     * Сохраняет новый пост в базу данных
     */
    public function store(PostRequest $request) {
        $request->merge(['user_id' => auth()->user()->id]);
        $post = Post::create($request->all());
        $post->tags()->attach($request->tags);
        return redirect()
            ->route('user.post.show', ['post' => $post->id])
            ->with('success', 'Новый пост успешно создан');
    }

    /**
     * Страница пред.просмотра поста блога
     */
    public function show(Post $post) {
        // можно просматривать только свои посты
        if ( ! $post->isAuthor()) {
            abort(404);
        }
        // сигнализирует о том, что это режим пред.просмотра
        session()->flash('preview', 'yes');
        // все опубликованные комментарии других пользователей
        $others = $post->comments()->published();
        // и не опубликованные комментарии этого пользователя
        $comments = $post->comments()
            ->whereUserId(auth()->user()->id)
            ->whereNull('published_by')
            ->union($others)
            ->orderBy('created_at')
            ->paginate();
        return view('user.post.show', compact('post', 'comments'));
    }

    /**
     * Показывает форму редактирования поста
     */
    public function edit(Post $post) {
        // редактировать можно только свои посты
        if ( ! $post->isAuthor()) {
            abort(404);
        }
        // редактировать можно не опубликованные
        if ($post->isVisible()) {
            abort(404);
        }
        // нужно сохранить flash-переменную, которая сигнализирует о том,
        // что кнопка редактирования была нажата в режиме пред.просмотра
        session()->keep('preview');
        return view('user.post.edit', compact('post' ));
    }

    /**
     * Обновляет пост блога в базе данных
     */
    public function update(PostRequest $request, Post $post) {
        // обновлять можно только свои посты
        if ( ! $post->isAuthor()) {
            abort(404);
        }
        // обновлять можно не опубликованные
        if ($post->isVisible()) {
            abort(404);
        }
        $post->update($request->all());
        $post->tags()->sync($request->tags);
        // кнопка редактирования может быть нажата в режиме пред.просмотра
        // или в личном кабинете пользователя, поэтому редирект разный
        $route = 'user.post.index';
        $param = [];
        if (session('preview')) {
            $route = 'user.post.show';
            $param = ['post' => $post->id];
        }
        return redirect()
            ->route($route, $param)
            ->with('success', 'Пост был успешно обновлен');
    }

    /**
     * Удаляет пост блога из базы данных
     */
    public function destroy(Post $post) {
        // удалять можно только свои посты
        if ( ! $post->isAuthor()) {
            abort(404);
        }
        // удалять можно не опубликованные
        if ($post->isVisible()) {
            abort(404);
        }
        $post->delete();
        return redirect()
            ->route('user.post.index')
            ->with('success', 'Пост блога успешно удален');
    }
}

Мы здесь активно используем метод isAuthor() модели Post, так что сразу его добавим:

class Post extends Model {
    /**
     * Возвращает true, если пользователь является автором
     */
    public function isAuthor() {
        return $this->user->id === auth()->user()->id;
    }
}

Здесь есть небольшая проблема с классом валидации PostRequest — мы используем имя маршрута при обновлении поста, чтобы изменить правило валидации уникальности slug. Но теперь маршрутов будет два, потому что пост может обновить администратор и автор.

class PostRequest extends FormRequest {
    /* ... */
    public function rules() {
        $unique = 'unique:posts,slug';
        if (in_array($this->route()->getName(), ['admin.post.update', 'user.post.update'])) {
            // получаем модель Post через маршрут admin/post/{post}
            $model = $this->route('post');
            $unique = 'unique:posts,slug,'.$model->id.',id';
        }
        /* ... */
    }
    /* ... */
}

Шаблон resporces/views/user/post/index.blade.php для просмотра всех постов пользователя:

@extends('layout.user', ['title' => 'Ваши публикации'])

@section('content')
    <h1>Ваши публикации</h1>
    @perm('create-post')
        <a href="{{ route('user.post.create') }}" class="btn btn-success mb-4">
            Новая публикация
        </a>
    @endperm
    @if ($posts->count())
        <table class="table table-bordered">
            <tr>
                <th width="10%">Дата</th>
                <th width="40%">Наименование</th>
                <th width="20%">Автор публикации</th>
                <th width="20%">Разрешил публикацию</th>
                <th><i class="fas fa-eye"></i></th>
                <th><i class="fas fa-toggle-on"></i></th>
                <th><i class="fas fa-edit"></i></th>
                <th><i class="fas fa-trash-alt"></i></th>
            </tr>
            @foreach ($posts as $post)
                <tr>
                    <td>{{ $post->created_at }}</td>
                    <td>{{ $post->name }}</td>
                    <td>{{ $post->user->name }}</td>
                    <td>
                        @if ($post->editor)
                            {{ $post->editor->name }}
                        @endif
                    </td>
                    <td>
                        <a href="{{ route('user.post.show', ['post' => $post->id]) }}"
                           title="Предварительный просмотр">
                            <i class="far fa-eye"></i>
                        </a>
                    </td>
                    <td>
                        @if ($post->isVisible())
                            <i class="far fa-toggle-on text-success"></i>
                        @else
                            <i class="far fa-toggle-off text-black-50"></i>
                        @endif
                    </td>
                    <td>
                        @if ( ! $post->isVisible())
                            <a href="{{ route('user.post.edit', ['post' => $post->id]) }}">
                                <i class="far fa-edit"></i>
                            </a>
                        @else
                            <i class="far fa-edit text-black-50"></i>
                        @endif
                    </td>
                    <td>
                        @if ( ! $post->isVisible())
                            <form action="{{ route('user.post.destroy', ['post' => $post->id]) }}"
                                  method="post" onsubmit="return confirm('Удалить этот пост?')">
                                @csrf
                                @method('DELETE')
                                <button type="submit" class="m-0 p-0 border-0 bg-transparent">
                                    <i class="far fa-trash-alt text-danger"></i>
                                </button>
                            </form>
                        @else
                            <i class="far fa-trash-alt text-black-50"></i>
                        @endif
                    </td>
                </tr>
            @endforeach
        </table>
        {{ $posts->links() }}
    @endif
@endsection

Шаблон views/user/post/create.blade.php для создания нового поста:

@extends('layout.user', ['title' => 'Создание поста'])

@section('content')
    <h1>Создание поста</h1>
    <form method="post" action="{{ route('user.post.store') }}" enctype="multipart/form-data">
        @include('admin.post.part.form')
    </form>
@endsection

Шаблон views/user/post/edit.blade.php для редактирования поста:

@extends('layout.user', ['title' => 'Редактирование поста'])

@section('content')
    <h1>Редактирование поста</h1>
    <form method="post" enctype="multipart/form-data"
          action="{{ route('user.post.update', ['post' => $post->id]) }}">
        @method('PUT')
        @include('admin.post.part.form')
    </form>
@endsection

Мы здесь используем шаблон формы admin.post.part.form для создания и редактирования поста блога. Этот шаблон мы уже создали, когда работали над панелью управления. Расположение шаблона не слишком удачное, лучше разместить его в директории, не связаной с директорией admin. Так что переместим его в директорию resources/views/common. И отредактируем три шаблона, которые его подключают — admin.post.edit, user.post.create и user.post.edit. Не буду это подробно описывать, здесь все достаточно просто.

Шаблон resporces/views/user/show.blade.php — для предварительного просмотра поста блога.

@extends('layout.site', ['title' => $post->name, 'user' => true])

@section('content')
    <div class="card mb-4">
        <div class="card-header">
            <h1>
                @if ( ! $post->isVisible())
                    <i class="far fa-eye-slash text-danger" title="Предварительный просмотр"></i>
                @else
                    <i class="far fa-eye text-success" title="Этот пост опубликован"></i>
                @endif
                {{ $post->name }}
            </h1>
        </div>
        <div class="card-body">
            <img src="http://via.placeholder.com/1000x300" alt="" class="img-fluid">
            <div class="mt-4">{!! $post->content !!}</div>
        </div>
        <div class="card-footer d-flex justify-content-between">
            <span>
                Автор:
                <a href="{{ route('blog.author', ['user' => $post->user->id]) }}">
                    {{ $post->user->name }}
                </a>
                <br>
                Дата: {{ $post->created_at }}
            </span>
            <span>
                @if ( ! $post->isVisible())
                    <a href="{{ route('user.post.edit', ['post' => $post->id]) }}"
                       class="btn btn-primary" title="Редактировать пост">
                        <i class="far fa-edit"></i>
                    </a>
                    <form action="{{ route('user.post.destroy', ['post' => $post->id]) }}"
                          method="post" class="d-inline" onsubmit="return confirm('Удалить этот пост?')">
                        @csrf
                        @method('DELETE')
                        <button type="submit" class="btn btn-danger" title="Удалить пост">
                            <i class="far fa-trash-alt"></i>
                        </button>
                    </form>
                @endif
            </span>
        </div>
        @if ($post->tags->count())
            <div class="card-footer">
                Теги:
                @foreach($post->tags as $tag)
                    @php $comma = $loop->last ? '' : ' • ' @endphp
                    <a href="{{ route('blog.tag', ['tag' => $tag->slug]) }}">
                        {{ $tag->name }}</a>
                    {{ $comma }}
                @endforeach
            </div>
        @endif
    </div>
    @include('user.post.comments', ['comments' => $comments])
@endsection

Об этом шаблоне надо поговорить подробнее. Во-первых, он расширяет layout-шаблон layout.site публичной части — так что в режиме предварительного просмотра пост будет выглядеть практически так же, как в публичной части. Во-вторых, он подключает шаблон user.post.comments для показа комментариев к посту. Этого делать не обязательно, но мы используем этот шаблон и для предварительного просмотра комментариев чуть ниже. В-третьих, мы передаем в layout-шаблон переменную user — чтобы все-таки было понятно, что это режим пред.просмотра и у него есть некоторые ограничения (а то сам запутался).

Раз мы передаем переменную user в layout-шаблон layout.site — нужно ее использовать. Кроме того, будем передавать аналогичную переменную admin из шаблона admin.post.show — чтобы режим пред.просмотра пользователя и администратора визуально отличались.

<div class="container">
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark mb-4">
        <!-- Логотип и кнопка «Гамбургер» -->
        @isset($admin) <i class="far fa-user text-danger mr-2"></i> @endisset
        @isset($user) <i class="far fa-user text-success mr-2"></i> @endisset
        <a class="navbar-brand" href="/">{{ env('APP_NAME') }}</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse"
                data-target="#navbar-blog" aria-controls="navbar-blog"
                aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>
        <!-- ..... -->
    </nav>
    <!-- ..... -->
</div>

Обратите внимание, что иконка в левом верхнем углу для пользователя зеленая, а для администратора красная.

Шаблон resporces/views/post/comments.blade.php для просмотра комментариев к посту блога.

<h3 id="comment-list">Все комментарии</h3>
@if ($comments->count())
    @foreach ($comments as $comment)
    <div class="card mb-3" id="comment-{{ $comment->id }}">
        <div class="card-header p-2">
            @if ( ! $comment->isVisible())
                <i class="far fa-eye-slash text-danger" title="Предварительный просмотр"></i>
            @else
                <i class="far fa-eye text-success" title="Комментарий опубликован"></i>
            @endif
            {{ $comment->user->name }}
        </div>
        <div class="card-body p-2">
            {{ $comment->content }}
        </div>
        <div class="card-footer p-2 d-flex justify-content-between">
            <span>{{ $comment->created_at }}</span>
            <span>
                @if ( ! $comment->isVisible())
                    <a href="{{ route('user.comment.edit', ['comment' => $comment->id]) }}"
                       class="btn btn-outline-primary btn-sm" title="Редактировать комментарий">
                        <i class="far fa-edit"></i>
                    </a>
                    <form action="{{ route('user.comment.destroy', ['comment' => $comment->id]) }}"
                          method="post" class="d-inline"
                          onsubmit="return confirm('Удалить этот комментарий?')">
                        @csrf
                        @method('DELETE')
                        <button type="submit" class="btn btn-outline-danger btn-sm" title="Удалить комментарий">
                            <i class="far fa-trash-alt"></i>
                        </button>
                    </form>
                @endif
            </span>
        </div>
    </div>
    @endforeach
    {{ $comments->fragment('comment-list')->links() }}
@else
    <p>К этому посту еще нет комментариев</p>
@endif

CRUD-операции над комментариями

Новые маршруты:

/*
 * Личный кабинет пользователя
 */
Route::group([
    'as' => 'user.', // имя маршрута, например user.index
    'prefix' => 'user', // префикс маршрута, например user/index
    'namespace' => 'User', // пространство имен контроллеров
    'middleware' => ['auth'] // один или несколько посредников
], function () {
    /*
     * Главная страница личного кабинета
     */
    Route::get('index', 'IndexController')
        ->name('index');
    /*
     * CRUD-операции над постами пользователя
     */
    Route::resource('post', 'PostController');
    /*
     * CRUD-операции над комментариями пользователя
     */
    Route::resource('comment', 'CommentController', ['except' => [
        'create', 'store'
    ]]);
});

Контроллер CommentController:

> php artisan make:controller User/CommentController --resource --model=Comment
namespace App\Http\Controllers\User;

use App\Comment;
use App\Http\Controllers\Controller;
use App\Http\Requests\CommentRequest;
use Illuminate\Http\Request;

class CommentController extends Controller {
    /**
     * Список всех комментариев пользователя
     */
    public function index() {
        $comments = Comment::whereUserId(auth()->user()->id)
            ->orderByDesc('created_at')
            ->paginate();
        return view('user.comment.index', compact('comments'));
    }

    /**
     * Просмотр комментария к посту блога
     */
    public function show(Comment $comment) {
        // можно просматривать только свои комментарии
        if ( ! $comment->isAuthor()) {
            abort(404);
        }
        // сигнализирует о том, что это режим пред.просмотра
        session()->flash('preview', 'yes');
        // это тот пост блога, к которому оставлен комментарий
        $post = $comment->post;
        // все опубликованные комментарии других пользователей
        $others = $post->comments()->published();
        // и не опубликованные комментарии этого пользователя
        $comments = $post->comments()
            ->whereUserId(auth()->user()->id)
            ->whereNull('published_by')
            ->union($others)
            ->orderBy('created_at')
            ->paginate();
        // используем шаблон предварительного просмотра поста
        return view('user.post.show', compact('post', 'comments'));
    }

    /**
     * Показывает форму редактирования комментария
     */
    public function edit(Comment $comment) {
        // проверяем права пользователя на это действие
        if ( ! $this->can($comment)) {
            abort(404);
        }
        // нужно сохранить flash-переменную, которая сигнализирует о том,
        // что кнопка редактирования была нажата в режиме пред.просмотра
        session()->keep('preview');
        return view('user.comment.edit', compact('comment'));
    }

    /**
     * Обновляет комментарий в базе данных
     */
    public function update(CommentRequest $request, Comment $comment) {
        // проверяем права пользователя на это действие
        if ( ! $this->can($comment)) {
            abort(404);
        }
        $comment->update($request->all());
        return $this->redirectAfterUpdate($comment);
    }

    /**
     * Удаляет комментарий из базы данных
     */
    public function destroy(Comment $comment) {
        // проверяем права пользователя на это действие
        if ( ! $this->can($comment)) {
            abort(404);
        }
        $comment->delete();
        // кнопка удаления может быть нажата в режиме пред.просмотра
        // или в личном кабинете пользователя, поэтому редирект разный
        $redirect = back();
        if (session('preview')) {
            $redirect = $redirect->withFragment('comment-list');
        }
        return $redirect->with('success', 'Комментарий успешно удален');
    }

    /**
     * Выполняет редирект после обновления
     */
    private function redirectAfterUpdate(Comment $comment) {
        // кнопка редактирования может быть нажата в режиме пред.просмотра
        // или в личном кабинете пользователя, поэтому и редирект разный
        $redirect = redirect();
        if (session('preview')) {
            $redirect = $redirect->route(
                'user.comment.show',
                ['comment' => $comment->id, 'page' => $comment->userPageNumber()]
            )->withFragment('comment-list');
        } else {
            $redirect = $redirect->route('user.comment.index');
        }
        return $redirect->with('success', 'Комментарий был успешно исправлен');
    }

    /**
     * Проверяет, что пользователь может редактировать
     * или удалять пост блога
     */
    private function can(Comment $comment) {
        return $comment->isAuthor() && !$comment->isVisible();
    }
}

Мы здесь активно используем метод isAuthor() модели Comment, так что сразу его добавим:

class Comment extends Model {
    /**
     * Возвращает true, если пользователь является автором
     */
    public function isAuthor() {
        return $this->user->id === auth()->user()->id;
    }
}

Шаблон resporces/views/user/comment/index для просмотра всех комментариев пользователя:

@extends('layout.user', ['title' => 'Ваши комментарии'])

@section('content')
    <h1>Ваши комментарии</h1>
    <table class="table table-bordered">
        <tr>
            <th>#</th>
            <th width="12%">Дата и время</th>
            <th width="47%">Текст комментария</th>
            <th width="17%">Автор комментария</th>
            <th width="20%">Разрешил публикацию</th>
            <th><i class="fas fa-eye"></i></th>
            <th><i class="fas fa-toggle-on"></i></th>
            <th><i class="fas fa-edit"></i></th>
            <th><i class="fas fa-trash-alt"></i></th>
        </tr>
        @foreach ($comments as $comment)
            <tr>
                <td>{{ $comment->id }}</td>
                <td>{{ $comment->created_at }}</td>
                <td>{{ iconv_substr($comment->content, 0, 100) }}</td>
                <td>{{ $comment->user->name }}</td>
                <td>
                    @if ($comment->editor)
                        {{ $comment->editor->name }}
                    @endif
                </td>
                <td>
                    @php $params = ['comment' => $comment->id, 'page' => $comment->userPageNumber()] @endphp
                    <a href="{{ route('user.comment.show', $params) }}" title="Предварительный просмотр">
                        <i class="far fa-eye"></i>
                    </a>

                </td>
                <td>
                    @if ($comment->isVisible())
                        <i class="far fa-toggle-on text-success"></i>
                    @else
                        <i class="far fa-toggle-off text-black-50"></i>
                    @endif
                </td>
                <td>
                    @if ( ! $comment->isVisible())
                        <a href="{{ route('user.comment.edit', ['comment' => $comment->id]) }}">
                            <i class="far fa-edit"></i>
                        </a>
                    @else
                        <i class="far fa-edit text-black-50"></i>
                    @endif
                </td>
                <td>
                    @if ( ! $comment->isVisible())
                        <form action="{{ route('user.comment.destroy', ['comment' => $comment->id]) }}"
                              method="post" onsubmit="return confirm('Удалить этот комментарий?')">
                            @csrf
                            @method('DELETE')
                            <button type="submit" class="m-0 p-0 border-0 bg-transparent">
                                <i class="far fa-trash-alt text-danger"></i>
                            </button>
                        </form>
                    @else
                        <i class="far fa-trash-alt text-black-50"></i>
                    @endif
                </td>
            </tr>
        @endforeach
    </table>
    {{ $comments->links() }}
@endsection

Для предварительного просмотра комментария используем шаблон resporces/views/user/post/show.blade.php — этот же шаблон используется для предварительного просмотра поста блога. Но если в режиме предварительного просмотра поста мы отбираем только опубликованные комментарии, то здесь отбираем опубликованные + не опубликованные этого пользователя. Комментариев к посту может быть много, поэтому используется постраничная навигация — а нам нужно попасть на ту страницу, где расположен комментарий. Для этого используем метод userPageNumber() модели Comment.

class Comment extends Model {
    /**
     * Номер страницы пагинации, на которой расположен комментарий;
     * все опубликованные + не опубликованные этого пользователя
     */
    public function userPageNumber() {
        // все опубликованные комментарии других пользователей
        $others = $this->post->comments()->published();
        // и не опубликованные комментарии этого пользователя
        $comments = $this->post->comments()
            ->whereUserId(auth()->user()->id)
            ->whereNull('published_by')
            ->union($others)
            ->orderBy('created_at')
            ->get();
        if ($comments->count() == 0) {
            return 1;
        }
        if ($comments->count() <= $this->getPerPage()) {
            return 1;
        }
        foreach ($comments as $i => $comment) {
            if ($this->id == $comment->id) {
                break;
            }
        }
        return (int) ceil(($i+1) / $this->getPerPage());
    }
}

Мы уже использовали похожий метод adminPageNumber() — отличие между ними в том, сколько всего комментариев к посту будет показано для администратора и пользователя. Обычный пользователь видит все опубликованные + свои не опубликованные, а администратор видит комментарии всех пользователей — в том числе еще не опубликованные. Один и тот же комментарий для администратора и пользователя может быть расположен на разных страницах — поэтому два метода, а не один.

Шаблон resporces/views/user/comment/edit.blade.php для редактирования комментария к посту:

Поиск: Laravel • MySQL • PHP • Web-разработка • База данных • Блог • Панель управления • Пользователь • Практика • Фреймворк • Шаблон сайта

Каталог оборудования
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Производители
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Функциональные группы
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.