Мини-блог на Laravel, часть 5. Просмотр и редактирование отдельного поста блога

16.09.2020

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

Хорошо, с добавлением нового поста вроде разобрались, теперь нужно его показать. Для этого создаем новый шаблон show.blade.php, добавляем новый маршрут, реализуем метод show() контроллера. В шаблонах index.blade.php и search.blade.php делаем ссылку для просмотра отдельного поста.

Шаблон, маршрут, метод контроллера

Новый шаблон

@extends('layouts.site')

@section('content')
    <div class="row">
        <div class="col-12">
            <div class="card mt-4 mb-4">
                <div class="card-header">
                    <h1>{{ $post->title }}</h1>
                </div>
                <div class="card-body">
                    <img src="{{ $post->image ?? asset('img/default.jpg') }}" alt="" class="img-fluid">
                    <p class="mt-3 mb-0">{{ $post->body }}</p>
                </div>
                <div class="card-footer">
                    <div class="clearfix">
                        <span class="float-left">
                            Автор: {{ $post->author }}
                            <br>
                            Дата: {{ date_format($post->created_at, 'd.m.Y H:i') }}
                        </span>
                        <a href="#" class="btn btn-dark float-right">Редактировать</a>
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

Новый маршрут

<?php
use Illuminate\Support\Facades\Route;

Route::get('/', 'PostController@index')->name('blog.index');

Route::get('post/index', 'PostController@index')->name('post.index');
Route::get('post/search', 'PostController@search')->name('post.search');
Route::get('post/create', 'PostController@create')->name('post.create');
Route::post('post/store', 'PostController@store')->name('post.store');
Route::get('post/show/{id}', 'PostController@show')->name('post.show');

Метод show()

<?php
namespace App\Http\Controllers;

use App\Post;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Intervention\Image\Facades\Image;

class PostController extends Controller {
    /* ... */
    public function show($id) {
        $post = Post::select('posts.*', 'users.name as author')
            ->join('users', 'posts.author_id', '=', 'users.id')
            ->find($id);
        return view('posts.show', compact('post'));
    }
    /* ... */
}

Добавляем ссылки для просмотра постов в шаблонах index.blade.php и search.blade.php

<a href="{{ route('post.show', ['id' => $post->post_id]) }}" class="btn btn-dark float-right">Читать дальше</a>

Вроде все готово, можно проверять. Но тут у меня появилась ошибка:

Illuminate\Database\QueryException
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'posts.id' in 'where clause'
(SQL: select * from `posts` where `posts`.`id` = 20 limit 1)

Это потому, что по умолчанию Laravel считает, что первичный ключ таблицы должен быть id, а у нас это post_id. Давайте сообщим об этом Laravel, для этого отредактируем файл класса модели app/Post.php.

<?php
namespace App;

use Illuminate\Database\Eloquent\Model;

class Post extends Model {
    protected $primaryKey = 'post_id';
}

Еще раз проверяем — теперь все в порядке:

Редактирование поста блога

Добавляем шаблон edit.blade.php

@extends('layouts.site')

@section('content')
    <h1 class="mt-2 mb-3">Редактировать пост</h1>
    <form method="post" action="{{ route('post.update', ['id' => $post->post_id]) }}"
          enctype="multipart/form-data">
        @csrf
        @method('PATCH')
        <div class="form-group">
            <input type="text" class="form-control" name="title"
                   placeholder="Заголовок" required value="{{ $post->title }}">
        </div>
        <div class="form-group">
            <textarea class="form-control" name="excerpt"
                      placeholder="Анонс поста" required>{{ $post->excerpt }}</textarea>
        </div>
        <div class="form-group">
            <textarea class="form-control" name="body"
                      placeholder="Текст поста" rows="7" required>{{ $post->body }}</textarea>
        </div>
        <div class="form-group">
            <input type="file" class="form-control-file" name="image">
        </div>
        @isset($post->image)
            <div class="form-group form-check">
                <input type="checkbox" class="form-check-input" name="remove" id="remove">
                <label class="form-check-label" for="remove">
                    Удалить загруженное <a href="{{ $post->image }}" target="_blank">изображение</a>
                </label>
            </div>
        @endisset
        <div class="form-group">
            <button type="submit" class="btn btn-primary">Сохранить</button>
        </div>
    </form>
@endsection
Поскольку HTML-формы не могут создавать запросы PUT, PATCH или DELETE, нам нужно добавить скрытое поле _method для имитации этих HTTP-запросов — это можно сделать с помощью директивы @method('PATCH').

Добавляем новые маршруты

<?php
use Illuminate\Support\Facades\Route;

Route::get('/', 'PostController@index')->name('blog.index');

Route::get('post/index', 'PostController@index')->name('post.index');
Route::get('post/search', 'PostController@search')->name('post.search');
Route::get('post/create', 'PostController@create')->name('post.create');
Route::post('post/store', 'PostController@store')->name('post.store');
Route::get('post/show/{id}', 'PostController@show')->name('post.show');
Route::get('post/edit/{id}', 'PostController@edit')->name('post.edit');
Route::patch('post/update/{id}', 'PostController@update')->name('post.update');

Реализуем метод edit()

<?php
namespace App\Http\Controllers;

use App\Post;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Intervention\Image\Facades\Image;

class PostController extends Controller {
    /* ... */
    public function edit($id) {
        $post = Post::find($id);
        return view('posts.edit', compact('post'));
    }
    /* ... */
}

Реализуем метод update()

<?php
namespace App\Http\Controllers;

use App\Post;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Intervention\Image\Facades\Image;

class PostController extends Controller {
    /* ... */
    public function store(Request $request) {
        $post = new Post();
        $post->author_id = rand(1, 4);
        $post->title = $request->input('title');
        $post->excerpt = $request->input('excerpt');
        $post->body = $request->input('body');
        $this->uploadImage($request, $post);
        $post->save();
        return redirect()
            ->route('post.index')
            ->with('success', 'Новый пост успешно создан');
    }
    /* ... */
    public function update(Request $request, $id) {
        $post = Post::find($id);
        $post->title = $request->input('title');
        $post->excerpt = $request->input('excerpt');
        $post->body = $request->input('body');
        $this->uploadImage($request, $post);
        $post->update();
        return redirect()
            ->route('post.show', compact('id'))
            ->with('success', 'Пост успешно отредактирован');
    }
    /* ... */
    private function uploadImage(Request $request, Post $post) {
        // если надо удалить старое изображение
        if ($request->input('remove')) {
            if (!empty($post->image)) {
                $name = basename($post->image);
                if (Storage::exists('public/image/image/' . $name)) {
                    Storage::delete('public/image/image/' . $name);
                }
                $post->image = null;
            }
            if (!empty($post->thumb)) {
                $name = basename($post->thumb);
                if (Storage::exists('public/image/thumb/' . $name)) {
                    Storage::delete('public/image/thumb/' . $name);
                }
                $post->thumb = null;
            }
            // здесь сложнее, мы не знаем, какое у файла расширение
            if (!empty($name)) {
                $images = Storage::files('public/image/source');
                $base = pathinfo($name, PATHINFO_FILENAME);
                foreach ($images as $img) {
                    $temp = pathinfo($img, PATHINFO_FILENAME);
                    if ($temp == $base) {
                        Storage::delete($img);
                        break;
                    }
                }
            }
        }
        // если было загружено новое изображение
        $source = $request->file('image');
        if ($source) {
            $ext = str_replace('jpeg', 'jpg', $source->extension());
            // уникальное имя файла, под которым сохраним его в storage/image/source
            $name = md5(uniqid());
            Storage::putFileAs('public/image/source', $source, $name. '.' . $ext);
            // создаем jpg изображение для с страницы поста размером 1200x400, качество 100%
            $image = Image::make($source)
                ->resizeCanvas(1200, 400, 'center', false, 'dddddd')
                ->encode('jpg', 100);
            // сохраняем это изображение под именем $name.jpg в директории public/image/image
            Storage::put('public/image/image/' . $name . '.jpg', $image);
            $image->destroy();
            $post->image = Storage::url('public/image/image/' . $name . '.jpg');
            // создаем jpg изображение для списка постов блога размером 600x200, качество 100%
            $thumb = Image::make($source)
                ->resizeCanvas(600, 200, 'center', false, 'dddddd')
                ->encode('jpg', 100);
            // сохраняем это изображение под именем $name.jpg в директории public/image/thumb
            Storage::put('public/image/thumb/' . $name . '.jpg', $thumb);
            $thumb->destroy();
            $post->thumb = Storage::url('public/image/thumb/' . $name . '.jpg');
        }
    }
    /* ... */
}

Заодно исправим метод store(), потому что код загрузки изображения теперь в отдельном методе (чтобы не дублировать код). После обновления поста делаем редирект на страницу поста и показываем сообщение, что пост успешно отредактирован.

Получилось не слишком удачно с маленькими изображениями. Мало того, что в таблице posts два почти одинаковых поля image и thumb, так и работать с ними неудобно. Надо сохранять в БД только уникальное имя файла изображения, тогда при обновлении поста будет удобнее удалять старые изображения.

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

Каталог оборудования
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.