Создаем блог на Laravel
суббота, 12 января 2019 03:42:18, написал Admin
В этой статье я расскажу, как быстро развернуть простой блог на Laravel. Мы воспользуемся админкой Voyager c её генератором BREAD, настроим показ категорий и постов на фронтенде, добавим карту сайта и форму обратной связи, рассмотрим тонкости настройки самого Laravel.
Ставим последнюю версию (на момент написания статьи это 5.7) Laravel - https://laravel.com/docs/5.7/installation Я ставил через composer в текущую папку
composer create-project --prefer-dist laravel/laravel ./
После установки генерируем ключ
php artisan key:generate
Генерируем вью для входа и регистрации
php artisan make:auth
Теперь зайдите на сайт и проверьте, работает ли он.
Дальше ставим админку Voyager: https://docs.laravelvoyager.com/getting-started/installation
composer require tcg/voyager
настраиваем доступ к базе данных в файле .env
DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=database DB_USERNAME=user DB_PASSWORD=password
и выполняем
php artisan voyager:install --with-dummy
- поставит с демо-данными. Если хотите без них, то вызывайте без ключа --with-dummy
Команда у меня не выполнилась из-за ошибки. Подробнее тут: Laravel и ошибка MySql Specified key was too long error
Исправляем ошибку по статье и идем дальше: заходим в адимнку site/admin и смотрим, что панель встала.
Далее включаем русский. Сначала для всего сайта. В конфиге (config/app.php) находим строку
'locale' => 'en'
И меняем на
'locale' => 'ru'
Качаем https://github.com/caouecs/Laravel-lang/tree/master/src/ru все файлы с переводом, бросаем их в папку resources/lang/ru
В конфиге config/voyager.php приводим к такому виду
/* * Set whether or not the multilingual is supported by the BREAD input. */ 'enabled' => true, /* * Set whether or not the admin layout default is RTL. */ 'rtl' => false, /* * Select default language */ 'default' => 'ru', /* * Select languages that are supported. */ 'locales' => [ 'en', 'ru', ],
Проверяем, идем в site/admin (важно! Надо непременно обновить Dashboard) и проверяем, что там контент на русском.
После установки в админке нам доступны статьи и посты с категориями. Давайте сделаем вывод страниц из таблицы pages на самом сайте.
Первым делом настраиваем роутер. Файл app\routers\web.php, дописываем
Route::get('/{page}.html', 'PageController@index')->name('page');
Здесь первый параметр – правило роутера (слаг страницы и расширение .html), далее контроллер и экшен. И наконец ->name('page') – название роутера (используется для вызовов в коде).
Далее создаем контроллер PageController через команду
php artisan make:controller PageController
В app\Http\Controllers у нас появился PageController. Приводим его к такому виду:
<?php namespace App\Http\Controllers; use Illuminate\Routing\Controller as BaseController; use TCG\Voyager\Models\Page; class PageController extends BaseController { /** * Show page. * * @param string $slug * @return Response */ public function index($slug) { $page = Page::where('slug', $slug)->firstOrFail(); return view('page', ['page' => $page]); } }
В app\resources\views\page.blade.php пишем такой код:
@extends('layouts.app') @section('title') <?= $page->title ?> @endsection @section('content') <h1>{{ $page->title }}</h1> <img style="width: 100%;" src="{{ Voyager::image( $page->image ) }}" /> <p>{!! $page->body !!}</p> @endsection
Идем в админку, создаем страницу со слагом about и любым заголовком и текстом. Открываем её по адресу http://ваш-сайт/about.html
Теперь приступим к блогу. Создадим BlogController и роутеры к нему. Начнем с роутеров, добавьте в app\routers\web.php следующие строки :
Route::get('/blog', 'BlogController@index')->name('blog.index'); Route::get('/blog/category/{slug}', 'BlogController@category')->name('blog.category'); Route::get('/blog/tag/{slug}', 'BlogController@tag')->name('blog.tag'); Route::get('/blog/archive/{year}/{month}', 'BlogController@archive')->name('blog.archive'); Route::get('/blog/{slug}.html', 'BlogController@show')->name('blog.show');
Вы видите здесь экшен tag. По умолчанию в Voyager нет такой модели и мы позже создадим её, используя интерфейс Voyager
В контроллер блога вписываем:
<?php namespace App\Http\Controllers; use Illuminate\Routing\Controller as BaseController; use App\Category; use App\Post; use App\Tag; class BlogController extends BaseController { // количество записей на странице public $item_count = 20; /** * Show the posts * * @return \Illuminate\Contracts\Support\Renderable */ public function index() { $models = Post::orderBy('created_at', 'DESC')->paginate($this->item_count); return view('blog.index', ['models' => $models]); } /** * Show the posts of category. * * @return \Illuminate\Contracts\Support\Renderable */ public function category($slug) { $category = Category::where('slug', '=', $slug)->firstOrFail(); $models = Post::where('category_id', '=', $category->id) ->orderBy('created_at', 'DESC') ->paginate($this->item_count); return view('blog.category', [ 'category' => $category, 'models' => $models, ]); } /** * Show the posts of tag. * * @return \Illuminate\Contracts\Support\Renderable */ public function tag($slug) { $tag = Tag::where('slug', '=', $slug)->firstOrFail(); $tag_id = $tag->id; $models = Post::whereHas('tags', function($q) use($tag_id) { $q->where('id', $tag_id); })->orderBy('created_at', 'DESC')->paginate($this->item_count); return view('blog.tag', [ 'models' => $models, 'tag' => $tag ]); } /** * Show the posts of archive. * * @return \Illuminate\Contracts\Support\Renderable */ public function archive($year, $month) { $models = Post::whereYear('created_at', '=', $year) ->whereMonth('created_at', '=', $month) ->orderBy('created_at', 'DESC') ->paginate($this->item_count); return view('blog.archive', [ 'models' => $models, 'year' => $year, 'month' => $month, ]); } /** * Show the post of slug. * * @param string $slug * @return Response */ public function show($slug) { $article = Post::where('slug', '=', $slug)->firstOrFail(); return view('blog.show', ['article' => $article]); } }
Обратите внимание на то, как здесь получается пагинация. В Yii например для этих целей используется датапровайдер, который затем подставляется в виджеты. Здесь же мы вызываем у экземпляра класса метод paginate(), где входной параметр – число записей на страницу, и получаем список моделей плюс пагинацию. Пагинация выводиться во вью через
{{ $models->links() }}
Более подробно здесь https://laravel.com/docs/5.7/pagination
Я не буду приводить листинг вью для категории, архива, и тэга. Приведу только для index, у остальных все аналогично, меняется только заголовок и хлебные крошки (о них речь потом).
Итак, создаем файлы в папке app\resources\views\blog\index.blade.php и app\resources\views\blog\_view.blade.php
Код index.blade.php:
@extends('layouts.app') @section('title', 'Блог') @section('content') @foreach ($models as $model) @include('blog._view', ['model' => $model]) @endforeach {{ $models->links() }} @endsection
Разметка blade очень похожа на twig из Симфони. Конструкция @extends указывает, от какого шаблона наследоваться.
@section – это блок, который потом выводиться в лайоуте командой @yield
@foreach – обход массива. Внутри циклам мы рендерим _view.blade.php через команду @include. Файл имеет следующее содержимое:
<h2><a href="/blog/{{ $model->slug }}.html">{{ $model->title }}</a></h2> <?php $data = new Jenssegers\Date\Date($model->created_at);?> <p><i class="fa fa-calendar"></i> {{ $data->format('l, j F Y H:i:s') }}, написал <i class="fa fa-user"></i> <a href="#">{{ $model->authorId()->first()->name }}</a></p> <p>{!! nl2br($model->excerpt) !!} </p> <p><a href="{{ route('blog.show', ['slug' => $model->slug]) }}" class="btn btn-info pull-right">Читать дальше</a></p> @if ($model->tags) @foreach ($model->tags as $tag) <a href="{{ route('blog.tag', ['slug'=> $tag->slug]) }}"><span class="badge badge-info">{{ $tag->title }}</span></a> @endforeach @endif @if (!empty($model->category)) <a href="{{ route('blog.category', ['slug' => $model->category->slug]) }}"> <span class="badge badge-lg badge-success">{{ $model->category->name }}</span> </a> @endif <hr/>
Тут стоит отметить работу с датами. Мне не получилось вывести название месяца на русском ни встроенными средствами Laravel (https://carbon.nesbot.com/docs/), ни чистым php (через setlocale)
В app/providers/AppServiceProvider.php я прописал
public function boot() { Schema::defaultStringLength(191); setlocale(LC_ALL, 'ru_RU.UTF-8'); Carbon::setLocale(config('app.locale')); }
Но ничего не помогло. Месяц по-прежнему выводился по-английски. Наткнулся на совет – поставить пакет jenssegers/date
Помогло. Сам пакет ставим отсюда: https://github.com/jenssegers/date
composer require jenssegers/date
Потом в config/app.php добавляем
'Date' => Jenssegers\Date\Date::class,
И вызываем в _view.blade.php
<?php $data = new Jenssegers\Date\Date($article->created_at);?> {{ $data->format('l, j F Y H:i:s') }}
Выведет
суббота, 12 января 2019 07:42:18
Настраиваем вью для показа поста show.blade.php
@extends('layouts.app') @section('breadcrumbs', Breadcrumbs::render('post', $article)) @section('title') {{ $article->title }} @endsection @section('content') <h2>{{ $article->title }}</h2> <?php $data = new Jenssegers\Date\Date($article->created_at);?> <p><i class="fa fa-calendar"></i> {{ $data->format('l, j F Y H:i:s') }}, написал <i class="fa fa-user"></i> <a href="#">{{ $article->authorId()->first()->name }}</a></p> <p>{!! nl2br($article->excerpt) !!} </p> <p>{!! $article->body !!}</p> @if ($article->tags) @foreach ($article->tags as $tag) <a href="{{ route('blog.tag', ['slug'=> $tag->slug]) }}"><span class="badge badge-info">{{ $tag->title }}</span></a> @endforeach @endif @if ($article->category) <a href="{{ route('blog.category', ['slug'=> $article->category->slug]) }}"><span class="badge badge-success">{{ $article->category->name }}</span></a> @endif <div> <h3>Комментарии к статье</h3> @widget('DisqusWidget', ['page_id' => route('blog.show', ['slug'=> $article->slug], false)]); </div> @endsection
Тут есть несколько важных моментов.
Первое – вызов хлебных крошек
@section('breadcrumbs', Breadcrumbs::render('post', $article))
Подробнее о них написал здесь: Laravel - вывод хлебных крошек
Второе – печать данных без экранирования тегов (аналог raw в twig)
{!! $article->body !!}
Третье – про подключение тэгов написал здесь: Тэги. Laravel - блог на Voyager
@if ($article->tags) @foreach ($article->tags as $tag) <a href="{{ route('blog.tag', ['slug'=> $tag->slug]) }}"><span class="badge badge-info">{{ $tag->title }}</span></a> @endforeach @endif
И наконец, в четвертых – вызов виджета комментариев Disqus. Про него написал в этой статье Laravel - создание виджета Disqus.
<div> <h3>Комментарии к статье</h3> @widget('DisqusWidget', ['page_id' => route('blog.show', ['slug'=> $article->slug], false)]); </div>
Осталось по мелочам.
Давайте создадим карту сайта по этой статье: Laravel - создание карты сайта
А форму обратной связи - по этой: Laravel - форма обратной связи
Laravel Voyager Laravel