Сообщения (Replies)

Следующие маршруты из файла web.php относятся к сообщениям:
Route::post('/threads/{channel}/{thread}/replies', 'RepliesController@store');
Route::delete('/replies/{reply}', 'RepliesController@destroy');
Контроллер RepliesController
class RepliesController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth');
    }

    public function store($channelId, Thread $thread)
    {
        $this->validate(request(), ['body' => 'required']);
        $thread->addReply([
            'body' => request('body'),
            'user_id' => auth()->id()
        ]);
        return back();
    }

    public function destroy(Reply $reply)
    {
        $this->authorize('update', $reply);
        $reply->delete();
        return back();
    }    
}
Здесь ничего интересного. Два метода: store() и destroy() используются для создания и удаления записей. В методе destroy() мы пользуемся методом authorize(), который вызывает метод update() из класса политики ReplyPolicy. Содержимое файла ReplyPolicy.php для этого класса аналогичное файлу ThreadPolicy.php, который мы создавали ранее.

Для создания политики ReplyPolicy воспользуемся командой:
php artisan make:policy ReplyPolicy
И в созданном классе добавим метод update(). Полный код класса будет следующим:
class ReplyPolicy
{
    public function update(User $user, Reply $reply)
    {
        return $reply->user_id == $user->id;
    }
}
После создания политики зарегистрируем ее в классе AuthServiceProvider (app/Providers/ AuthServiceProvider.php). Таким образом, массив $policies в этом классе будет выглядеть следующим образом:
protected $policies = [
    Thread::class => ThreadPolicy::class,
    Reply::class => ReplyPolicy::class,
];
Вид из файла reply.blade.php
Единственный вид для сообщений. В нем выводится список сообщений из БД, с возможностью удаления и голосования за сообщение. Код, отвечающий за создание нового сообщения, располагается в файле show.blade.php. Вид из этого файла отвечает за вывод подробного описания отдельной темы. Данный вид мы рассматривали в предыдущем разделе.
<div class="panel panel-default">
    <div class="panel-heading">
        <div class="level">
            <h5 class="flex">
                <a href="{{ route('profile', $reply->owner) }}">
                    {{ $reply->owner->name }}
                </a> написал {{ $reply->created_at->format('d-m-Y') }} в {{ $reply->created_at->format('H:i:s') }}
            </h5>
            <div>
                <form method="POST" action="/replies/{{ $reply->id }}/favorites">
                    {{ csrf_field() }}

                    <button type="submit" class="btn btn-default" {{ $reply->isFavorited() ? 'disabled' : '' }}>
                        {{ $reply->favorites_count }} 
                        <span class="glyphicon glyphicon glyphicon-star" style="color: #c9302c"></span>
                    </button>
                </form>
            </div>
        </div>
    </div>
    <div class="panel-body">
        {{ $reply->body }}
    </div>
    @can ('update', $reply)
        <div class="panel-footer">
            <form method="POST" action="/replies/{{ $reply->id }}">
                {{ csrf_field() }}
                {{ method_field('DELETE') }}
                <button type="submit" class="btn btn-danger btn-xs">Удалить</button>
            </form>
        </div>
    @endcan
</div>
Модель Reply

class Reply extends Model
{
    use Favoritable;

    protected $guarded = [];
    /* Активная загрузка (eager loading)  */
    protected $with = ['owner', 'favorites'];

    public function owner()
    {
        return $this->belongsTo(User::class, 'user_id');
    }
} 
Часть функционала вынесем в отдельный файл – Favoritable.php. Содержимое трейта из этого файла будет следующим:
trait Favoritable
{
    /* Полиморфное отношение – получаем все голоса за сообщения */
    public function favorites()
    {
        return $this->morphMany(Favorite::class, 'favorited');
    }

    /* Сохраняем новый голос за сообщение */
    public function favorite()
    {
        $attributes = ['user_id' => auth()->id()];
        if (!$this->favorites()->where($attributes)->exists()) {
            return $this->favorites()->create($attributes);
        }
    }

    /* Проверяем голосовал ли уже этот пользователь за это сообщение
     * (используется в reply.blade.php)
     */
    public function isFavorited()
    {
        return !! $this->favorites->where('user_id', auth()->id())->count();
    }

    /* Получаем количество голосований за сообщение
     * (используется в reply.blade.php)
     */
    public function getFavoritesCountAttribute()
    {
        return $this->favorites->count();
    }
}
Для работы функционала голосования мы используем следующий маршрут из файла web.php:
Route::post('/replies/{reply}/favorites', 'FavoritesController@store');
Для работы маршрута нам нужен FavoritesController, поэтому создадим нужный для него файл. Содержимое класса FavoritesController должно быть следующим:
class FavoritesController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth');
    }

    public function store(Reply $reply)
    {
        $reply->favorite();
        return back();
    }
} 
Здесь, в методе store() мы используем метод favorite() из модели Reply (трейт Favoritable), который был приведен выше.

В разделе «БД и маршруты» мы создали модель Favorite. Содержимое класса Favorite должно быть следующим:
class Favorite extends Model
{
    protected $guarded = [];
}
Массив $guarded оставляем пустым, чтобы новые голоса могли беспрепятственно сохраняться в БД.