Мини-блог на Laravel, часть 6. Исправление ошибок, удаление поста, семь маршрутов
17.09.2020
Теги: Laravel • MySQL • PHP • Web-разработка • БазаДанных • Блог • Практика • Форма • Фреймворк • ШаблонСайта
Работа над ошибками
Прежде, чем двигаться дальше, давайте внесем пару мелких изменений в проект. Во-первых, у нас сейчас два почти одинаковых шаблона create.blade.php
и edit.blade.php
. Во-вторых, если будет запрошен для показа или редактирования не существующий пост — будет выброшено исключение. Лучше, если в этом случае мы покажем страницу 404 Not Found.
Создадим шаблон resources/views/parts/form.blade.php
:
@csrf <div class="form-group"> <input type="text" class="form-control" name="title" maxlength="100" placeholder="Заголовок" required value="{{ $post->title ?? '' }}"> </div> <div class="form-group"> <textarea class="form-control" name="excerpt" maxlength="200" 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>
Отредактируем шаблон resources/views/posts/create.blade.php
:
@extends('layouts.site') @section('content') <h1 class="mt-2 mb-3">Создать пост</h1> <form method="post" action="{{ route('post.store') }}" enctype="multipart/form-data"> @include('parts.form') </form> @endsection
Отредактируем шаблон resources/views/posts/update.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"> @method('PATCH') @include('parts.form') </form> @endsection
С дублированием кода разобрались, теперь исправим проблему с просмотром или редактированием не существующего поста. Заменим в контроллере вызов Post::find()
на Post::findOrFail()
. При этом Laravel выбросит исключение ModelNotFoundException
, которое мы можем централизованно отлавливать и показывать страницу 404 Not Found. Для этого отредактируем файл app/Exceptions/Handler.php
.
<?php namespace App\Exceptions; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; use Throwable; class Handler extends ExceptionHandler { /* ... */ public function render($request, Throwable $exception) { if ($exception instanceof ModelNotFoundException) { abort(404); } return parent::render($request, $exception); } /* ... */ }
Добавляем иконку favicon.png
Тут все просто — кладем favicon.png
(у меня это логотип Laravel) в директорию public
и прописываем путь к ней в layout-шаблоне site.blade.php
.
<!doctype html> <html lang="ru"> <head> <!-- ..... --> <title>{{ $title ?? 'Веб-разработка' }}</title> <link rel="shortcat icon" type="image/png" href="{{ asset('favicon.png') }}"/> <!-- ..... --> </head> <body> <!-- ..... --> </body> </html>
Удаление поста блога
На страницу поста добавим кнопку «Удалить»
@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> <span class="float-right"> <a href="{{ route('post.edit', ['id' => $post->post_id]) }}" class="btn btn-dark mr-2">Редактировать</a> <!-- Форма для удаления поста --> <form action="{{ route('post.delete', ['id' => $post->post_id]) }}" method="post" onsubmit="return confirm('Удалить этот пост?')" class="d-inline"> @csrf @method('DELETE') <input type="submit" class="btn btn-danger" value="Удалить"> </form> </span> </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'); Route::get('post/edit/{id}', 'PostController@edit')->name('post.edit'); Route::patch('post/update/{id}', 'PostController@update')->name('post.update'); Route::delete('post/destroy/{id}', 'PostController@destroy')->name('post.destroy');
Реализуем метод destroy()
class PostController extends Controller { /* ... */ public function destroy($id) { $post = Post::findOrFail($id); $post->delete(); return redirect() ->route('post.index') ->with('success', 'Пост был успешно удален'); } /* ... */ }
Но это еще не окончательная реализация. При удалении поста надо удалять и связанные с ним изображения. Код для удаления изображений у нас уже есть, надо только его приспособить к решению еще одной задачи.
<?php namespace App\Http\Controllers; use App\Post; use Illuminate\Http\Request; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; 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::findOrFail($id); $post->title = $request->input('title'); $post->excerpt = $request->input('excerpt'); $post->body = $request->input('body'); // если надо удалить старое изображение if ($request->input('remove')) { $this->removeImage($post); } // если было загружено новое изображение $this->uploadImage($request, $post); // все готово, можно сохранять пост в БД $post->update(); return redirect() ->route('post.show', compact('id')) ->with('success', 'Пост успешно отредактирован'); } private function uploadImage(Request $request, Post $post) { $source = $request->file('image'); if ($source) { // перед тем, как загружать новое изображение, удаляем загруженное ранее $this->removeImage($post); /* * сохраняем исходное изображение и создаем две копии 1200x400 и 600x200 */ $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'); } } private function removeImage(Post $post) { 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; } } } } /* ... */ public function destroy($id) { $post = Post::findOrFail($id); $this->removeImage($post); $post->delete(); return redirect() ->route('post.index') ->with('success', 'Пост был успешно удален'); } /* ... */ }
Оптимизация маршрутов
Наш контроллер PostController
— ресурсный, то есть позволяет производить над неким ресурсом (пост блога) базовые операции: просмотр списка постов, просмотр отдельного поста, создание или редактирование, удаление существующего поста. И в этом случае мы можем описать семь маршрутов, которые добавляли ранее, всего одной строкой.
// на главной странице сайта показываем список всех постов Route::get('/', 'PostController@index')->name('blog.index'); Route::get('post/search', 'PostController@search')->name('post.search'); // поиск по блогу Route::get('post/index', 'PostController@index')->name('post.index'); // все посты блога 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'); // обновление поста Route::delete('post/destroy/{id}', 'PostController@destroy')->name('post.destroy'); // удаление поста
// на главной странице сайта показываем список всех постов; но если проводить аналогию с WordPress — на // главной странице может быть показана статичная страница или список постов блога или еще что-то третье Route::get('/', 'PostController@index')->name('blog.index'); // этот маршрут оставляем, потому как он не входит в число семи стандартных маршрутов Route::get('post/search', 'PostController@search')->name('post.search'); /* Тип URI Действие Имя маршрута -------------------------------------------------- GET /post index post.index GET /post/create create post.create POST /post store post.store GET /post/{id} show post.show GET /post/{id}/edit edit post.edit PUT/PATCH /post/{id} update post.update DELETE /post/{id} destroy post.destroy */ Route::resource('post', 'PostController');
Здесь Laravel предлагает использовать точно такие же имена маршрутов, которые мы уже использовали. Но вот предлагаемые URI немного другие, хотя отличия несущественные. Так что можно заменить семь маршрутов одним — и все будет работать. Поскольку имена маршрутов у нас совпадают, а в шаблонах мы используем только имена — в шаблонах ничего изменять не надо.
При объявлении маршрута можно указать подмножество всех возможных действий, которые должен обрабатывать контроллер вместо полного набора стандартных действий:
Route::resource('post', 'PostController', ['only' => ['index', 'show']]);
Route::resource('post', 'PostController', ['except' => ['create', 'store', 'update', 'destroy']]);
- Блог на Laravel 7, часть 9. Панель управления — создание, публикация, удаление комментариев
- Блог на Laravel 7, часть 8. Панель управления — CRUD для категорий, тегов и пользователей
- Блог на Laravel 7, часть 7. Панель управления — создание, публикация, удаление постов
- Мини-блог на Laravel, часть 4. Создание нового поста, загрузка и обрезка изображения
- Блог на Laravel 7, часть 13. Загрузка и ресайз изображений для категорий и постов блога
- Блог на Laravel 7, часть 12. Доп.страницы сайта в панели управления и в публичной части
- Блог на Laravel 7, часть 11. Панель управления — назначение ролей и прав для пользователей
Поиск: Laravel • MySQL • PHP • Web-разработка • База данных • Блог • Практика • Форма • Фреймворк • Шаблон сайта