Мини-блог на Laravel, часть 3. Постраничная навигация, layout-шаблон и поиск по блогу

12.09.2020

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

Методы контроллера

Наш контроллер умеет только выводить список всех постов блога. Но нам нужно, чтобы контроллер умел еще показывать отдельный пост, добавлять новый, редактировать или удалять существующий. Поэтому удаляем файл контроллера и создаем заново с помощью artisan-команды

> php artisan make:controller PostController -r
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;

class PostController extends Controller {
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index() {
        // .....
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create() {
        // .....
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request) {
        // .....
    }

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id) {
        // .....
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function edit($id) {
        // .....
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, $id) {
        // .....
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id) {
        // .....
    }
}

Изменим метод index(), чтобы получать все посты блога с сортировкой по дате и с пагиницией:

class PostController extends Controller {
    /* ... */
    public function index() {
        $posts = Post::select('posts.*', 'users.name as author')
            ->join('users', 'posts.author_id', '=', 'users.id')
            ->orderBy('posts.created_at', 'desc')
            ->paginate(4);
        return view('posts.index', compact('posts'));
    }
    /* ... */
}

И внесем изменения в шаблон, чтобы показывать картинку, автора поста и дату публикации:

<!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') }}">
</head>
<body>
<div class="container">
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <!-- ... -->
    </nav>

    <h1 class="mt-2 mb-3">Все посты блога</h1>
    <div class="row">
        @foreach ($posts as $post)
            <div class="col-6 mb-4">
                <div class="card">
                    <div class="card-header"><h3>{{ $post->title }}</h3></div>
                    <div class="card-body">
                        <img src="{{ $posts->image ?? asset('img/default.jpg') }}" alt="" class="img-fluid">
                        <p class="mt-3 mb-0">{{ $post->excerpt }}</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>
        @endforeach
    </div>
    {{ $posts->links() }}
</div>
</body>
</html>

Если для поста не загружена картинка, тогда показываем картинку по умолчанию default.jpg, которую разместим в директории img в корне веб-сервера.

Layout-шаблон

Сейчас у нас всего один шаблон, но скоро их будет много. У всех шаблонов будет много общего, так что создадим общий шаблон и будем его наследовать. Создаем файл site.blade.php в директории resources/views/layouts:

<!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') }}">
</head>
<body>
<div class="container">
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <a class="navbar-brand" href="{{ route('blog.index') }}">Веб-разработка</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
                aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>

        <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="#">Создать</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="#">Контакты</a>
                </li>
            </ul>
            <form class="form-inline my-2 my-lg-0" action="">
                <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>
    </nav>

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

А шаблон index.blade.php в директории resources/views/posts будет наследовать site.blade.php:

@extends('layouts.site')

@section('content')
    <h1 class="mt-2 mb-3">Все посты блога</h1>
    <div class="row">
        @foreach ($posts as $post)
            <div class="col-6 mb-4">
                <div class="card">
                    <div class="card-header"><h3>{{ $post->title }}</h3></div>
                    <div class="card-body">
                        <img src="{{ $posts->image ?? asset('img/default.jpg') }}" alt="" class="img-fluid">
                        <p class="mt-3 mb-0">{{ $post->excerpt }}</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>
        @endforeach
    </div>
    {{ $posts->links() }}
@endsection

Поиск по блогу

Добавим еще один метод search() в класс контроллера, который будет искать посты блога по введенной фразе:

class PostController extends Controller {
    /* ... */
    public function search(Request $request) {
        $search = $request->input('search', '');
        // образаем слишком длинный запрос
        $search = iconv_substr($search, 0, 64);
        // удаляем все, кроме букв и цифр
        $search = preg_replace('#[^0-9a-zA-ZА-Яа-яёЁ]#u', ' ', $search);
        // сжимаем двойные пробелы
        $search = preg_replace('#\s+#u', ' ', $search);
        if (empty($search)) {
            return view('posts.search');
        }
        $posts = Post::select('posts.*', 'users.name as author')
            ->join('users', 'posts.author_id', '=', 'users.id')
            ->where('posts.title', 'like', '%'.$search.'%') // поиск по заголовку поста
            ->orWhere('posts.body', 'like', '%'.$search.'%') // поиск по тексту поста
            ->orWhere('users.name', 'like', '%'.$search.'%') // поиск по автору поста
            ->orderBy('posts.created_at', 'desc')
            ->paginate(4)
            ->appends(['search' => $request->input('search')]);;
        return view('posts.search', compact('posts'));
    }
    /* ... */
}

Добавим новый маршрут в файл routes/web.php, а заодно — зададим имена для маршрутов, чтобы с ними было удобно работать:

<?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');

В layout-шаблоне site.blade.php установим значение атрибута action тега form:

<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>

И осталось только создать шаблон search.blade.php в директории resources/views/posts:

@extends('layouts.site')

@section('content')
    <h1 class="mt-2 mb-3">Результаты поиска</h1>
    @if (isset($posts) && count($posts))
        <div class="row">
            @foreach ($posts as $post)
                <div class="col-6 mb-4">
                    <div class="card">
                        <div class="card-header"><h3>{{ $post->title }}</h3></div>
                        <div class="card-body">
                            <img src="{{ $posts->image ?? asset('img/default.jpg') }}" alt="" class="img-fluid">
                            <p class="mt-3 mb-0">{{ $post->excerpt }}</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>
            @endforeach
        </div>
        {{ $posts->links() }}
    @else
        <p>По вашему запросу ничего не найдено</p>
    @endif
@endsection

Поиск: CLI • 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.