Мини-блог на Laravel, часть 4. Создание нового поста, загрузка и обрезка изображения

14.09.2020

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

Теперь нам нужно реализовать остальные методы контроллера PostController — create(), store(), show(), edit(), update() и destroy(). Давайте создадим шаблон create.blade.php, добавим два маршрута в файле routes/web.php и внесем изменения в метод create(), который будет просто показывать форму.

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

Новый шаблон

@extends('layouts.site')

@section('content')
    <h1 class="mt-2 mb-3">Создать пост</h1>
    <form method="post" action="{{ route('post.store') }}">
        @csrf
        <div class="form-group">
            <input type="text" class="form-control" name="title" placeholder="Заголовок" required>
        </div>
        <div class="form-group">
            <textarea class="form-control" name="excerpt" placeholder="Анонс поста" required></textarea>
        </div>
        <div class="form-group">
            <textarea class="form-control" name="body" placeholder="Текст поста" rows="7" required></textarea>
        </div>
        <div class="form-group">
            <input type="file" class="form-control-file" name="image">
        </div>
        <div class="form-group">
            <button type="submit" class="btn btn-primary">Сохранить</button>
        </div>
    </form>
@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');

Метод create()

class PostController extends Controller {
    /* ... */
    public function create() {
        return view('posts.create');
    }
    /* ... */
}

Ссылка на страницу с формой создания поста в layout-шаблоне:

<div class="collapse navbar-collapse" id="navbarSupportedContent">
    <ul class="navbar-nav mr-auto">
        <li class="nav-item">
            <a class="nav-link" href="#">Авторы</a>
        </li>
        <li class="nav-item">
            <a class="nav-link" href="{{ route('post.create') }}">Создать</a>
        </li>
        <li class="nav-item">
            <a class="nav-link" href="#">Контакты</a>
        </li>
    </ul>
    <form class="form-inline my-2 my-lg-0" action="{{ route('post.search') }}">
        <input class="form-control mr-sm-2" type="search" name="search"
               placeholder="Найти пост..." aria-label="Поиск">
        <button class="btn btn-outline-success my-2 my-sm-0" type="submit">Поиск</button>
    </form>
</div>

Подготовка файлового хранилища

Сразу после установки Laravel доступны диски local и public, использующие драйвер local. Для диска local место хранения — директория storage/app, для диска public место хранения — директория storage/app/public. Диск local является диском по умолчанию.

Чтобы сделать файлы диска public доступными через веб, надо создать символьную ссылку из public/storage на storage/app/public. Директория public проекта Laravel — является корневой директорией сервера, поэтому файл storage/app/public/image.jpg будет доступен через веб как http://server.com/storage/image.jpg.

> cd D:/work/localhost25/www
> php artisan storage:link
The [D:/work/localhost25/www/public/storage] link has been connected to [D:/work/localhost25/www/storage/app/public].
The links have been created.

Метод store() контроллера

Теперь можно приступать к реализации метода store() контроллера PostController:

<?php
namespace App\Http\Controllers;

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

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');
        $image = $request->file('image');
        if ($image) {
            $path = Storage::putFile('public', $image);
            $post->image = Storage::url($path);
        }
        $post->save();
        return redirect()->route('post.index');
    }
    /* ... */
}

Сохраним в сессию сообщение, чем завершилось добавление поста:

class PostController extends Controller {
    /* ... */
    public function store(Request $request) {
        /* ... */
        return redirect()->route('post.index')->with('success', 'Новый пост успешно создан');
    }
    /* ... */
}

И будем показывать это сообщение в layout-шаблоне:

<!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>
    <link rel="stylesheet" href="{{ asset('css/app.css') }}">
    <script src="{{ asset('js/app.js') }}"></script>
</head>
<body>
<div class="container">
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <!-- ... -->
    </nav>

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

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

Заодно подключим js-файл app.js — без него сообщения работать не будут.

Создание превьюшки

Не слишком удачно, что в списке постов блога мы показываем большое изображение. Поэтому при добавлении нового поста будем создавать два уменьшенных изображения и сохранять путь к ним в базе данных. Для этого установим пакет intervention/image с помощью composer.

> cd D:/work/localhost25/www
> composer require intervention/image

Открываем файл config/app.php и добавляем следующие строки:

return [
    /* ... */
    'providers' => [
        /* ... */
        Intervention\Image\ImageServiceProvider::class,
    ],
    
    'aliases' => [
        /* ... */
        'Image' => Intervention\Image\Facades\Image::class,
    ]
    /* ... */
]

Исходный файл изображения будем сохранять без изменений в директорию storage/public/image/source, а уменьшенные копии — в директории storage/public/image/image и storage/public/image/thumb. Так что эти три директории нужно сразу создать. Кроме того, добавить в таблицу БД posts еще одно поле thumb.

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');
        $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');
        }
        $post->save();
        return redirect()->route('post.index')->with('success', 'Новый пост успешно создан');
    }
    /* ... */
}

Все готово, осталось только изменить шаблоны index.blade.php и search.blade.php:

<img src="{{ $post->thumb ?? asset('img/default.jpg') }}" alt="" class="img-fluid">

Дополнительно

Поиск: Laravel • MySQL • PHP • Web-разработка • База данных • Блог • Класс • Форма • Фреймворк • Шаблон сайта • Практика • resize • image • Изображение • 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.