Настройка ЧПУ – неотъемлемый атрибут работы по SEO-оптимизации любого сайта. Формировать URL только по id записи – не лучший вариант.

В данном уроке мы добавим псевдонимы записей (слаг/алиас) для постов блога.

Суть такова. В форме (создания, редактирования) поста есть текстовове поле, в которое мы можем прописать свой собственный slug и при этом оно должно валидироваться. Если мы оставим поле пустым, то Yii2 сам сделает эту работу методом Inflector::slug(), куда мы передадим название поста (то есть, по умолчанию слаг будет транслитом названия поста). В базе данных поле slug обязательно для заполнения (NOT NULL) и является уникальным.

Добавим в базу данных поле slug (если его не было изначально). Пример добавления поля через миграцию:

// Из консоли
php yii migrate/create add_post_field_slug

В созданной миграции:

public function safeUp()
{
  $this->addColumn('{{%post}}', 'slug', $this->string(255)->notNull()->after('status')->unique());
  $this->createIndex('{{%idx-post-slug}}', '{{%post}}', 'slug', true);
}

public function safeDown()
{
  $this->dropColumn('{{%post}}', 'slug');
}

В форме (backend\views\post\_form.php) добавим поле:

<?= $form->field($model, 'slug')->textInput(['maxlength' => true]) ?>

В модель Post добавляем следующий код:

private $_post;

public function rules()
{
  return [
    [['name'], 'required'], // На стороне клиента "slug" необязательный          
    [['name', 'slug'], 'string', 'max' => 255], // "slug" - ограниченная строка
    ['slug', SlugValidator::class], // "slug" валидируется классом SlugValidator
    // "name" и "slug" - уникальны (фильтр по ID поста при редактировании)
    [['name', 'slug'], 'unique', 'targetClass' => Post::class, 'filter' => $this->_post ? ['<>', 'id', $this->_post->id] : null]
  ];
}

Класс SlugValidator:

<?php

namespace common\models\validators;

use yii\validators\RegularExpressionValidator;

class SlugValidator extends RegularExpressionValidator
{
  public $pattern = '#^[a-z0-9_-]*$#s';
  public $message = 'Only [a-z0-9_-] symbols are allowed.';
}

Контроллер backend\controllers\PostController.php:

public function actionCreate()
{
  $model = new Post();
  if ($model->load(Yii::$app->request->post())) {

    $model->slug = $model->slug ?: Inflector::slug($model->name);
   
    if($model->save()) {
      return $this->redirect(['view', 'id' => $model->id]);
    }
  }

  return $this->render('create', [
      'model' => $model,
  ]);
}

Обновление записи происходит аналогично. В метод actionUpdate($id) необходимо тоже добавить:

$model->slug = $model->slug ?: Inflector::slug($model->name);

Теперь ссылка на пост во фронтенде будет выглядеть так:

<a href="<?= Url::to(['/blog/post/view', 'slug' => $model->slug]) ?>"><?= Html::encode($model->name) ?></a>

// Или
<?
$name = Html::encode($model->name);
$url = Url::to(['/blog/post/view', 'slug' => $model->slug]);
$options = [
  'title' => $name    
];
echo Html::a($name, $url, $options);
?>

Метод, в котором получаем отдельный пост:

public function actionView($slug)
{
  // Получаем пост по его слагу
  if (!$post = Post::find()->where(['slug' => $slug])->one()) {
    throw new NotFoundHttpException('Page not found!');
  }

  return $this->render('view', [
    'post' => $post
  ]);
}

Правило в urlManager:

[
  'pattern' => 'blog/post/<slug:[\w-]+>',
  'route' => 'blog/post/view',
  'suffix' => '.html'
]

Теперь посты открываются по красивым адресам по типу:

http://site.ru/blog/post/primer-slaga.html

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

http://site.ru/blog/post/slug-example.html

Это был, наверное, самый простой способ сделать слаги (алиасы, псевдонимы) у постов на фреймворке Yii2. Есть, конечно, готовые решения, типа SluggableBehavior. Это поведение, которое будет производить всю валидацию и транслитерацию за вас. В примере, который я описал выше есть возможность управлять всем этим делом. Фишка заключается в том, что по большому счёту никакой автоматической генерации в принципе не нужно. В большинстве случаев имеет бОльший смысл прописать его самостоятельно. Ну, а если вам вдруг стало просто лень, то пусть себе транслитерирует и сохраняет в базу транслит. Это уже будет ЧПУ.