Как сделать ЧПУ (Slug, ModRewrite, Seo Friendly Url) для Laravel

Не все знают, что такое слаги (на английском slug), зачем они нужны, и как их использовать в Laravel. Итак, что такое слаг и зачем он нужен. Как нам подсказывает вики — это семантический URL. Отсюда можно сделать вывод, что это ссылка которая помогает и пользователям и поисковым роботам понять, что находится по этой ссылке. Так же эти штуки известны как ModRewrite, SEF (Seo Friendly Url), ЧПУ (Человеко-Понятный УРЛ).

Предположим, есть у нас статья, которая называется «Привет, Мир!». Так вот, без ЧПУ она будет иметь адрес /post/145, а с ЧПУ: /post/privet-mir. Согласитесь, что второй вариант выглядит красивее и изящнее. Да и пользователь уже из адресного пути может примерно понять о чем идет речь.

Когда мы разобрались, что такое SEF, давайте разберемся, как их использовать в Laravel. Для работы со слагами в Laravel нам понадобится простой и удобный пакет cviebrock/eloquent-sluggable.

А не проще при создании записи сразу создавать ‘slug’ => str_slug($title)? Конечно проще, но не совсем правильно, так как может произойти так, что будут два разных поста, но с одинаковыми заголовками. Тогда придется делать проверку на уникальность записи и новую запись делать отличной от предыдущей. А так, пакет сам на себя берет эту заботу.

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

Устанавливаем его:

 composer require cviebrock/eloquent-sluggable

Если вы захотите изменить какие-либо дефолтные настройки, то создайте файл конфигурации:

 php artisan vendor:publish --provider="Cviebrock\EloquentSluggable\ServiceProvider"

После этой команды будет создан новый конфиг: config/sluggable.php в котором можно поменять некоторые значения. Теперь выбираете файл модели, к которой хотите создать слаг. Пусть, к примеру, эта модель называется Post. В этой модели нужно указать пространство имён и создать метод для генерации seo friendly url из какого-либо поля таблицы:

use Cviebrock\EloquentSluggable\Sluggable;
class Post extends Model{ use Sluggable;
/** * Return the sluggable configuration array for this model. * * @return array */ public function sluggable() { return [ 'slug' => [ 'source' => 'title' ] ]; }}

Как видно выше, SEF будет создан из поля title (заголовок). Но это еще не всё. Ведь нам нужно этот слаг где-то хранить. А для этого нужно создать новое поле в таблице posts. Рассмотрим вариант, когда таблица уже существует и необходимо создать новую миграцию, для добавления одного поля:

php artisan make:migration AddCollumnSlugPosts

После чего будет создана новая миграция и вы увидите в командной строке, что-то наподобие этого:

Created Migration: 2017_09_21_123338_AddCollumnSlugPosts

Внутри созданного файла создаем новое поле:

<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddColumnSlugPosts extends Migration
{
/**
* Add column slug to posts table.
*
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('posts', function (Blueprint $table) {
$table->string('slug')->nullable()->index();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('posts', function (Blueprint $table) {
$table->dropColumn('slug');
});
}
}

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

php artisan migrate

Теперь к таблице posts добавится новое поле slug. Поздравляю! Часть работы выполнена. Да, кстати. Почему поле мы назвали именно slug, а не как-то иначе. Ответ становится понятен из метода модели Post:

public function sluggable() { return [ 'slug' => [ 'source' => 'title' ] ]; }

Ключ массива slug указывает на поле в таблице posts, куда автоматически будет записан слаг, созданный из поля-источника этой же таблицы. В нашем конкретном случае — это поле title. Можно создавать слаг из любого строкового поля. Вам нет нужны создавать слаг вручную. Он будет создаваться автоматически.

Идем дальше. Теперь нас интересует, как для ModRewrite создать свой роут (route). Это очень просто — так же само, как и для числового индекса {id}:

// Paths for posts.
Route::get('post', 'PostController@index')->name('post.index');
Route::post('post', 'PostController@store')->name('post.store');
Route::get('post/create', 'PostController@create')->name('post.create');
Route::get('post/{slug}', 'PostController@show')->name('post.show');
Route::patch('post/{slug}', 'PostController@update')->name('post.update');
Route::delete('post/{slug}', 'PostController@destroy')->name('post.destroy');
Route::get('post/{slug}/edit', 'PostController@edit')->name('post.edit');

А в контроллере PostController, например, в методе show, пишем следующее:

/**
* Display the specified resource.
*
* @param string $slug
*
* @return \Illuminate\Http\RedirectResponse
*/
public function show($slug)
{
// If user come from ols link where was id then make 301 redirect.
if (is_numeric($slug)) {
// Get post for slug.
$post = Post::findOrFail($slug);
return Redirect::to(route('post.show', $post->slug), 301);
}
// Get post for slug.
$post = Post::whereSlug($slug)->firstOrFail();
return view('posts.show', [
'post' => $post
]);
}

Вначале проверяем, не является ли слаг числом. Зачем мы это делаем? Часто ЧПУ внедряют в программу уже после того, как был другой механизм построения пути. Например, через числовые индексы. Тогда может получится ситуация, что пользователь, зайдя на сайт по старой ссылке, увидит 404 ошибку, что такой страницы не существует. Ведь мы теперь работаем через Mod Rewrite.

Поэтому, нам необходимо сделать 301 редирект со старой страницы, на новую. Для этого мы и проверяем полученный аргумент на числовое значение, пытаемся по этому значению получить коллекцию (collection), и если такой пост действительно существует (если нет, то выводим 404 ошибку), то делаем 301 редирект на страницу со слагом.

Если же вы создаете сайт с нуля и сразу используете seo friendly url, то такой проверки и редирект делать не нужно.

Построения ссылки в шаблоне тоже очень простое:

<a href="{!! route('post.show', $post->slug) !!}" class="btn btn-info">ReadMore</a>

Как видите, сложности нет никакой, а использование Mod Rewrite очень просто и удобно для пользователей и поисковиков.