Начало работы с Laravel / Публикация статей + ЧПУ

В этой статье я расскажу как произвести первоначальную установку и настройку Laravel 5+. Научу вас создавать роуты и контролеры, выводть статьи из базы данных, открыть статьи по ЧПУ и добавлять ваши данные в БД.

Приступая к выполнению статьи убедитесь, что в вашей среде разработки установлен PHP5.6+ MySQL, Composer и ваша ОС обладает терминалом.

Во-первых, скачайте установщик Laravel с помощью Composer следующей командой в терминале:

composer global require "laravel/installer"

Установка

Давайте создадим каркас приложения и назовём его например блогом:

laravel new blog

Иногда тут может возникнуть ошибка, если терминал ругается, что понятия не имеет кто такой Laravel, используйте вот такую команду:

composer create-project --prefer-dist laravel/laravel blog

или для установки определённой версии:

composer create-project laravel/laravel="5.5.*" myProject

Теперь перейдите в папку вашего приложения:

cd blog

Если во время установки вы не увидели сообщения о генерации хеш ключа, то необходимо запустить его генерацию следующей командой:

php artisan key:generate

И запустим Laravel на встроенном PHP сервере:

php artisan serve

Ваше приложение станет доступно по адресу: http://localhost:8000/

Механизм авторизации

Теперь давайте подключим механизм авторизации и регистрации на сайте который предоставляет нам Laravel. Для этого в корневой папке найдите скрытый файл .env и настройте в нём параметры подключения к базе данных которую предварительно следует создать. У меня получился вот такой код:

<code>APP_ENV=local  
APP_KEY=base64:IgQmEN0xTduP7kLSjc4ghYU4e4khkWtpURPd6CsCM=  
APP_DEBUG=true // при переносе на прод поставьте falce  
APP_LOG_LEVEL=debug  
APP_URL=http://localhost
DB_CONNECTION=mysql  
DB_HOST=127.0.0.1  
DB_PORT=3306  
DB_DATABASE=lara  
DB_USERNAME=root  
DB_PASSWORD=root  
...
</code>

Теперь установите менеджер авторизации:

php artisan make:auth

И запустите миграцию:

php artisan migrate

Всё готово! Теперь вы можете зарегистрировать вашего первого пользователя в системе. Для этого перейдите по адресу: http://localhost:8000/register

Так же скорей всего в верхнем меню на сайте появятся два пункта "Login" и "Register".

Установка дебагера

Ну и для полного счастья давайте установим дебагер бар. Для начала выполните следующую команду в терминале:

composer require barryvdh/laravel-debugbar

Затем перейдите в папку /config и откройте файл app.php В данном файле найдите строки типа таких:

<code>App\Providers\AppServiceProvider::class,  
App\Providers\AuthServiceProvider::class,
App\Providers\EventServiceProvider::class,  
App\Providers\RouteServiceProvider::class,  
</code>

И под ними вставьте вот такую строку:

Barryvdh\Debugbar\ServiceProvider::class,

Затем найдите что то вроде такого:

<code>...
'Response' => Illuminate\Support\Facades\Response::class,  
'Route' => Illuminate\Support\Facades\Route::class,  
'Schema' => Illuminate\Support\Facades\Schema::class,  
'Session' => Illuminate\Support\Facades\Session::class,  
'Storage' => Illuminate\Support\Facades\Storage::class,  
'URL' => Illuminate\Support\Facades\URL::class,  
'Validator' => Illuminate\Support\Facades\Validator::class,  
'View' => Illuminate\Support\Facades\View::class,  
</code>

И ниже вставьте строку:

'Debugbar' => Barryvdh\Debugbar\Facade::class,

Теперь запустите в терминале следующую команду:

php artisan vendor:publish --provider="Barryvdh\Debugbar\ServiceProvider"

И на вашем сайте, снизу, появится замечательная строка с отладочной информацией.

Структура приложения

Laravel как и большинство современных фреймворков предлагает нам работать используя патерн проектирования приложений MVC. Давайте рассмотри структуру приложения ларавела.

Конрноллеры находятся по адресу app\http\Controllers

Модели в корне папки app

Представления (виды) resources\views

Ну и конечно, прежде чем попасть в контроллер запрос идёт в маршрутизатор routes\web.php

Роутер

Давайте рассмотрим работу маршрутизатора. Для этого откройте фвйл routes\web.php в нём вы увидите вот такой код:

<code>Route::get('/', function () {  
    return view('welcome');
});
</code>

Давайте разбираться что тут происходит. Для начала Router получает из GET запроса текущий URL адрес. Если адрес совпадает с / то-есть с корнем сайта выполняется функция возвращающая хелпер views отвечающий за вызов представления.

В нашем примере мы вызываем файл представления resources\views\welcome.blade.php как видно в примере мы пишем вызов только welcome имя представления, шаблонизатор и расширение .blade.php подставляются фреймворком Laravel.

Давайте создадим свой собственный роутер такого вида:

<code>Route::get('/about', function () {  
    return 'Hey this about page';
});
</code>

Теперь если после адреса вашего сайта ввести /about вы увидите наше приветствие. Что тут произошло? Наш роутер получил GET параметр и когда он совпал с /about сработала функция которая вернула строку "Hey this about page". Таким образом мы создали первый простейший роут.

Контроллер

Теперь давайте заменим наш пример выше на вызов контроллера вместо отображения строки. Выглядит это вот таким образом:

<code>Route::get('/about', 'StaticController@about');  
</code>

Давайте разбираться что я тут такого по-написал. Первая часть как и в предыдущем примере получает URL из GET, и если он совпадает с /about вызываем контроллер StaticController который мы пока не создали. Далее символ @ говорит о том, что мы будем в контроллере искать метод с именем которое следует за символом @ в данном случае внутри класса class StaticController будет выполнена функция public function about(){}. Прелесть такого подхода что вы можете в одном контроллере принимать различные урлы со сходими задачами.

Теперь создадим сам контроллер. Для этого в терминале выполните команду:

<code>php artisan make:controller StaticController  
</code>

Теперь в папке app\http\Controllers у нас появился файл StaticController.php со следующим содержанием:

<code><?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class StaticController extends Controller  
{
    //
}
</code>
Имя контроллера не обязательно должно содержать часть Controller я это делаю для наглядности, но имена контроллеров принято начинать с заглавной буквы.

Давайте же наконец добавим сюда функцию которую вызывает наш роутер:

<code>public function about(){  
  return "Hi again! i'm about page";
}
</code>

Таким образом мы снова увидим строку из примера выше когда зайдём по адресу /about, только на этот раз строку нам отдаёт контроллер.

Давайте разбираться если результат тот-же зачем лишние телодвижения? Всё довольно просто, у контроллера есть своя роль, он является связующим звеном, принимая то роутера параметры запросов контроллер выполняет определённые сценарии.

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

Вот так вот лихо в одно предложение мы рассмотрели весь патерн MVC.

Давайте теперь научим наш роутер вместо возврата текстовой строки, отображать пользователю представление. Для этого приведём нашу функцию about() к следующему виду:

<code>public function about(){  
   return view('about');
}
</code>

Как видите способ аналогичен тому, которым мы вызывали представление в роутере. Различие лишь в том, что теперь мы можем связать представление с моделью и передать в него данные или массив данных.

Давайте рассмотрим как передать переменную в представление из контроллера.

<code>public function about(){  
   $myData = "hola! i'm data in passed variable";
   return view('about')->with('myData', $myData);
}
</code>

Метод ->with служит для передачи данных в представление, где первый аргумент ('myData' это имя переменной которое будет доступно в представлении а второе, это его значение, в нашем примере строка которую мы присвоили переменной $myData.

Модель

Модель обычно содержит в себе всю бизнес логику, подключения к БД, выборки и сортировки, оставляя контроллер максимально тонким.

В данном примере мы будем делать выборку из базы данных. Для этого предварительно создайте таблицу posts с полями ID, Title, Body и заполните их любыми данными.

Создайте модель аналогично примеру контроллера:

<code>php artisan make:model Post  
</code>

Обратите внимание мы называем модель аналогично имени таблицы в БД только в единственном числе. Таким образом Laravel за нас выполняет подключение к данной таблице, в общем-то для нашего простого примера менять модель не понадобится, сгенерированый код уже самодостаточен. Давайте теперь передадим данные из модели в представление. В этом нам будет помогать контроллер.

Для начала в контроллере подключаем созданную модель:

<code>use App\Post;  
</code>

Теперь создаём новый метод для страницы блога:

<code>public function blog(){
    $posts = Post::all();
    return view('blog')->with('posts', $posts);
}
</code>

Обратите внимание метод Post::all(); выбирает все записи из таблицы posts в базе данных посредством модели Post.

Что бы убедиться в том, что всё работает давайте на время закоментируем строку с return и добавим вывод массива на экран:

<code>public function blog(){
    $posts = Post::all();
    dump($posts);
  //return view('blog')->with('posts', $posts);
}
</code>

Вы должны увидеть на экране коллекции, где в каждой из строк массива в массиве [#attributes:] Будут видны поля вашей записи.

У меня это выглядит вот так:

<code>[id] => 1
[title] => Test post 1
[body] => body of test post 1
</code>

А вот тут пример вывода из базы с большим количеством полей:

Как видите в этом массиве данных видно и наши записи в БД у меня это Test post 1 с содержимым body of test post 1.

Теперь можем вернуть метод контроллера к первоначальному виду и перейти к созданию представления в котором мы сделаем вывод статей.

Первоначальный вид метода контроллера:

<code>public function blog(){
    $posts = Post::all();
    return view('blog')->with('posts', $posts);
}
</code>

Хотя погодите, нафига нам в списке статей все поля, если у нас на боевом проекте их может быть около десятка, в краткой записи нам не потребуется метатеги, ключевики, дата последнего обновления и прочие прелести. Для того что бы выбрать из бд только интересующие нас данные давайте научимся составлять запросы, в строке $posts = Post::all(); заменим метод all() на метод select() и выберем из БД только интересующие нас строки и запишем их в одномерный массив:

<code>   public function blog(){
   $posts = Post::select(['id','title','excerpt','slug'])->get();
   return view('blog')->with('posts', $posts);
}
</code>

Представление

Представления в фреймворке Laravel работают с помощью шаблонизатора Blade что позволяет очистить представления от php кода тем самым разгрузив их от ненадлежащего использования и обезопасив сайт от потенциальных уязвимостей.

Давайте создадим наше представление. На этот раз командная строка нам не товарищ. Делаем руками, моя заготовка выглядит вот так:

<code>@extends('layouts.app')
@section('content')
<div class="container">  
    <div class="row">
        <div class="col-md-12">
          <h1>Blog</h1>
...
        </div>
    </div>
</div>  
@endsection
</code>

Как мы ранее уже говорили, контроллер передаёт в представление коллекцию записей, которая по факту является не чем иным как массивом, а что как не цикл foreach умеет отлично перебирать массивы? Но как же быть? Мы же только что обсудили, что у нас ест шаблонизатор который не позволяет нам использовать php в представлении, какой foreach?

Не стоит паниковать, тот самый шаблонизатор предоставляет нам инструменты для удобной работы с данными, например мы можем использовать директиву @foreach как вы уже наверное догадались, эта директива и есть не что иное как foreach из php.

Итак хватит болтовни, давайте наконец выведем наши данные, перебирая их в массиве и выводя в виде записей на странице. Для этого нам понадобится вот такой код:

<code>@foreach($posts as $post)
   <h3>{{ $post->title }}</h3>
   <p>{{ $post->body }}</p>
@endforeach
</code>

Как вы видите здесь всё аналогично обычному циклу, мы проходимся по массиву $posts сохраняя каждую его итерацию (пост) в переменную $post и затем выводим её параметры {{ $post->title }}.

Обратите внимание, что в поле body могут содержатся html теги и вместо их применения наш шаблонизатор польностью их экранирует и выводит на экран. Что бы заставить ваши теги снова превратить в нормальные html сущности необходимо отменить экранирование, делается этовот так: {!! $post->body !!}

Вот и вся магия. Теперь можете украсить всё вашим html и css.

Отдельные страницы

Теперь давайте из нашего массива данных сделаем ссылки на отдельные страницы с отображением этих данных, ну и собственно создадим представления отдельных страниц.

Начинаем как обычно с роутера. Нам необходимо создать шаблон запроса, что бы не писать вручную маршрут для каждой статьи. Делаеться это вот так:

<code>Route::get('/blog/{url}', 'StaticController@blogPost')->name('postShow');  
</code>

Давайте разберёмся что такое шаблон запроса и как он работает. Первая часть запроса /blog/ - у нас статическая, а {url} это динамический парамерт, который мы будем передавать для отображения конкретной статьи. Далее по аналогично примеру выводу всех статьей, вызываем метод blogpost в контроллере StaticController. Тепер псевдоним ->name('postShow'), псевдоним будет использоваться в котролере, что бы позволить нам обращаться к данному роуту и передавать в него параметры, в нашем случае это будут URL адреса статей.

Составляем метод контроллера для отображения отдельных статей:

<code>public function blogPost($url){  
      $post = Post::all()->where('slug',$url)->first();
      return view('postShow')->with('post', $post);
}
</code>

Итак разберёмся в коде. Как это работает? в массив $post мы сохраняем результаты выборки из БД поста URL (slug) которого совпадает с переданным в контролер Post::all()->where('slug',$id), и далее ограничиваем выборку единственным результатом ->first().

Теперь вернёмся в наше представление где мы выводили список статей и заменим такой вид:

<code>@foreach($posts as $post)
   <h3>{{ $post->title }}</h3>
   <p>{{ $post->body }}</p>
@endforeach
</code>

на

<code>@foreach($posts as $post)
   <h3>{{ $post->title }}</h3>
   <p>{{ $post->body }}</p>
   <a href="{{ route('postShow', ['id' => $post->slug]) }}">More</a>
@endforeach
</code>

Теперь у нас появится ссылка с ЧПУ принадлежащим конкретной статье котору мы будем передавать в качестве аргумента в роутер и контролер.

Для полного счастья, нам осталось только создать представление postShow которое мы обьявили в контролере и распечатать данные массива:

<code>  <h1>{{ $post->title }}</h1>
  <p>{!! $post->content !!}</p>
</code>

Ну и остальные поля по желанию, базовые принципы я объяснил.

Ну м напоследок, давайте в этом-же контролере передадим три последние статьи в добавок к текущей. Обновляем контроллер до такого вида:

<code>public function blogPost($url){  
      $posts = Post::limit(3)
                             ->orderBy('id', 'desc')
                             ->get();
      $post = Post::all()->where('slug',$url)->first();
      return view('blogPost')->with('post', $post)
                             ->with('posts', $posts);
}
</code>

По мимо предыдущего массива $post мы так-же передали массив $posts в которой вложили последние три статьи, отсортировали их по полю id что-бы сперва отображались самые свежие статьи. Но вы так-же можете проделать этот трюк и с датами, что бы удобнее манипулировать в последствии позицией в списке с помощью изменения даты публикации. Ещё больше примеров ищите в документации по запросу: Database: Query Builder.

Теперь давайте обновим наше представление вот так:

<code><div class="left__col">  
  <h1>{{ $post->title }}</h1>
  <p>{!! $post->content !!}</p>
</div>  
<div class="right__col">  
  @foreach($posts as $post)
            <h4><a href="{{ route('postShow', ['id' => $post->slug]) }}">{{ $post->title }}</a></h4><br/>
  @endforeach
</div>  
</code>

Теперь у вас в левой колонке будет содержание поста, а в правой список последних записей.

Laravel 5,4 вывод статей и ЧПУ

Создание материалов

Теперь давайте научимся создавать материалы и сохранять их в БД. Для этого нам понадобится создать два роута и два метода контроллера, на одном из которых будет размещена страница с формой добавления, а на другой будет приходить запрос на сохранение данных.

Создаём вот такие роуты:

<code>Route::get('/admin/add/blog', 'StaticController@blogAdd');  
Route::post('/admin/add/blog', 'StaticController@blogStore')->blogStore;
</code>

Надеюсь вы уже можете прочитать их код и его понимание не вызовет у вас затруднений. Теперь создаём метод blogStore в контроллере. В этом методе мы не будем ничего передавать в представление.

<code>public function blogAdd(){  
      return view('addPost');
}
</code>

И создаём представление с формой:

<code><form method="post" action="{{route('blogStore')}}">  
  <input name="title" type="text">
  <textarea name="body"></textarea>
  <input name="slug" type="text">
  {{ csrf_field() }}
  <button type="submit">Save</button>
</form>  
</code>

Обратите внимание на action данные будут отправлены через роутер/псевдоним прямиком в метод контроллера для последующей валидации и сохранения в БД. Так-же здесь используется хелпер {{ csrf_field() }} защищающий нас от кроссдоменных атак, добавляя в форму скрытое поле с определённым хешем, без которого модель откажется принимать данные и сохранять в БД.

Теперь давайте вернёмся в наш контроллер и создадим метод blogStore. В качестве аргумента этот метод будет принимать объект который мы создаём из глобального класса Request который и будет содержать отправляемые данные из формы. При помощи хелпера dump() мы сможем вывести на экран все данные переданные в запросе.

<code>public function blogStore(Request $request){  
      dump($request->all());
}
</code>

Теперь можете вписать любые данные и отправить вашу форму для того, что-бы убедится, что все данные отправились и отображаются корректно.

Валидация в Laravel

Теперь перед тем как отправить все данные непосредственно в БД давайте их провалидируем, что бы избежать попадения в БД всякого мусора или отправки пустой формы.

<code>$this->validate($request, [
    'title' => 'required|max:255',
    'slug' => 'required|unique:posts',
]);
</code>

Тут мы обращаемся к объекту контроллера и вызываем метод validate. В качестве первого аргумента мы передаём тот самый запрос $request то-есть объект класса Request в котором и содержаться наши данные. Далее метод validate() извлекает их из массива и позволяет нам указать для них условия валидации. В качестве второго аргумента мы будем передавать массив с набором правил валидации. В этом массиве в качестве ключей мы будем указывать имена полей объекта $request которые являются ничем иным как наши данные полученые из формы. В качестве значения этого массива мы будем указывать правила валидации данного поля.

Теперь давайте разберёмся с правилами валидации, множественные правила пишутся через разделитель вертикальной черты | а дополнительные параметры через двоеточие :. Например в алиасе unique:posts мы говорим, полю slug что оно должно быть уникальным, в таблице :post для нас это критически важно, так как в при открытии статей в блоге мы в качестве аргумента передаём не ID а алиас. Так же в неочевидном запросе можно указать поле таблицы которое будет уникальным, например: 'unique:posts,slug', хотя если поле в БД совпадает, то это не обязательный параметр.

Теперь давайте разберёмся с тем какие сценарии нас ожидают, если все поля прошли валидацию, то блок валидации в нашем методе никак не воспрепятствует дальнейшему выполнению метода. В случае же когда какое-то из правил валидации не выполнено, валидация остановит выполнение метода, передасть ошибку в специальную переменную, метод не выполниться, данные не будут переданы в БД.

Ок, с валидацией разобрались, давайте-же наконец перейдём к сохранению информации в БД.

Для начала необходимо создать временную переменную куда мы будем передавать все данные полученные в POST запросе.

<code>$data = $request->all(); // Сохраняем все поля из запроса request во временную переменную
</code>

Затем нам необходимо создать пустой объект нашей модели куда мы затем будем заполнять данные для отправки их в соотвествующие ячейки таблицы в БД.

<code>$post = new Post; // Создаём переменную которая будет содержать оъбект модели поста, по сути это пустая модель
</code>

Для заполнения модели существует такой метод как fill() которым мы и воспользуемся: $post->fill($data); Ниже привожу полный код данного метода контроллера с валидацией и заполнением объекта.

<code>public function blogStore(Request $request){  
      $this->validate($request, [
          'title' => 'required|max:255',
          'slug' => 'required|unique:posts',
      ]);
      $data = $request->all(); // Сохраняем все поля из запроса request во временную переменную
      $post = new Post; // Создаём переменную которая будет содержать оъбект модели поста, по сути это пустая модель
      $post->fill($data);
}
</code>

Метод fill() существует для массового заполнения объектов модели, и для того, что бы этим воспользоваться, нам необходимо в самой модели указать поля, которые разрешены для массового заполнения.

Итак открываем модель Post и в классе модели создаём новое защищённое свойство fillable и внём при помощи массива создаём список разрешенных для массового заполнения полей. В нашем простеньком примере это будет выглядеть вот так:

<code>class Post extends Model  
{
    protected $fillable = ['title', 'body', 'slag'];
}
</code>

Теперь возвращаемся в контроллер здесь у нас уже есть модель заполненная данными $post->fill($data); нам лишь отсалось написать метод для сохранения состояния модели в БД:

<code>$post->save();
</code>

Ну и для полного счастья нам осталось только в функцию ответа добавить метод редирект, например на главную страницу return redirect('/'); далее привожу вид всего метода добавления записи:

<code>public function blogStore(Request $request){  
    $this->validate($request, [
        'title' => 'required|max:255',
        'slug' => 'required|unique:posts',
    ]);
    $data = $request->all();
    $post = new Post;
    $post->fill($data);
    $post->save();
    return redirect('/');
}
</code>

И напоследок давайте выведем специальную переменную куда в случае ошибок передается информация о них, например если не заполнены обязательные поля в форме, следующий код можно вывести как в отдельном представлении, так и в глобальном лайоуте:

<code>@if(count($errors) > 0)
    <ul>
        @foreach($errors->all() as $error)
            <li>{{ $error }}</li>
        @endforeach
    </ul>
@endif
</code>

Пожалуй на этом можно закончить вступительный обзор разработки приложения на Laravel. Больше подробностей вы сможете найти в следующих уроках и заметках по Laravel.