Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Создаем поведение (behaviour) для Yii2

Anna | 31.05.2014 | нет комментариев

Часто, а на самом деле фактически неизменно, при создании сайта нужно, Дабы страницы сайта открывались не по id сущности в базе, а по текстовому идентификатору, назовем его slug.

post/view/1 => post/view/testovaya-novost

(из url’а стоило бы убрать и view, но урок не о том)

Самым примитивным путем дозволено сделать в таблице post поле slug, в модели Post соответственно возникает новейший признак, в представление (view) добавляем новейший input, в тот, что ручками вбиваем slug.


<?php

use yii\helpers\Html;
use yii\widgets\ActiveForm;

/**
 * @var yii\web\View           $this
 * @var common\models\Post     $model
 * @var yii\widgets\ActiveForm $form
 */
?>

<div>

	<?php $form = ActiveForm::begin(); ?>

	<?= $form->field( $model, 'name' )->textInput( [ 'maxlength' => 255 ] ) ?>
	<?= $form->field( $model, 'slug' )->textInput( [ 'maxlength' => 255 ] ) ?>
	<?= $form->field( $model, 'content' )->textarea( [ 'rows' => 6 ] ) ?>

	<div>
		<?= Html::submitButton( $model->isNewRecord ? Yii::t( 'app', 'Create' ) : Yii::t( 'app', 'Update' ), [ 'class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary' ] ) ?>
	</div>

	<?php ActiveForm::end(); ?>

</div>

Но ручками это делать не неизменно увлекательно (да кого я вру, вообще неинтересно), следственно мы дописываем в модель способы, которые при сохранении модели генерируют slug механически из name, проверяют его уникальность в таблице (чай мы по slug’у будем извлекать post из базы, а, значит, slug не может быть не уникальным), ну и, допустимо, транслитерируем его (тестовая-новость => testovaya-novost) — это тоже может быть благотворно.
Что ж, пишем, привязываемся к событию, тестируем — все работает. И здесь при разработке сайта мы сталкиваемся с тем, что slug’и еще необходимы в модели Page. А еще в каталоге для товара — пускай это будет модель Item. Дозволено пойти по пути наименьшего сопротивления — копипаста. Но…

В Yii существует такая вещь как поведения (behaviours) — функционал, разрешающий применять одни и те же функции в разных моделях. Выходит, напишем поведение для slug’ификации.

В нашей модели Post (она же \commoin\models\Post на каждый случай) подключаем еще не сделанное поведение:

public function behaviors()
{
	return [

		'slug' => [
			'class' => 'common\behaviors\Slug',
			'in_attribute' => 'name',
			'out_attribute' => 'slug',
			'translit' => true
		]
	];
}

Сделали функцию behaviours, нужную для подключения, прописали класс, в котором будем находиться поведение и передали в данный класс три признака:
1. in_attribute — признак модели, из которого будет генерироваться slug (в различных моделях он может отличаться, скажем name либо title)
2. out_attribute — это признак соответственно slug’а (slug либо alias)
3. translit — здесь все ясно

При создании поведения был еще четвертый признак — unique, но потом я исключил данный функционал, т.к. дюже редко необходимо, Дабы slug был не уникальным.

Упомяну, что я использую конструкция приложения yii2-app-advanced, то есть у меня есть папки backend и frontend, в которых лежат контроллеры и вьюшки, и папка common, в которой всеобщие модели и поведения.

Создаем common/behaviours/Slug.php:

<?php

namespace common\behaviors;

use yii;
use yii\base\Behavior;
use yii\db\ActiveRecord;

class Slug extends Behavior
{
	public $in_attribute = 'name';
	public $out_attribute = 'slug';
	public $translit = true;

	public function events()
	{
		return [
			ActiveRecord::EVENT_BEFORE_VALIDATE => 'getSlug'
		];
	}	
}

Класс наследуем от yii\base\Behavior, прописываем три признака с исходными установками, создаем способ events, тот, что привяжет поведение к какому-то событию при сохранении модели. Так как slug традиционно нужен и может быть прописан в rules как required, то привяжем генерацию slug’а до валидации.

ActiveRecord::EVENT_BEFORE_VALIDATE => 'getSlug'

Сейчас сделаем способ getSlug:

public function getSlug( $event )
{
	if ( empty( $this->owner->{$this->out_attribute} ) ) {
		$this->owner->{$this->out_attribute} = $this->generateSlug( $this->owner->{$this->in_attribute} );
	} else {
		$this->owner->{$this->out_attribute} = $this->generateSlug( $this->owner->{$this->out_attribute} );
	}
}

Сам объект модели передается в поведение как $this->owner. Таким образом slug нам будет доступен через обращение к $this->owner->slug либо в нашем случае $this->owner->{$this->out_attribute}, так как наименование признака slug’а передается в переменную $this->out_attribute.
Делаем проверку пуст ли slug при сохранении и, если пуст, то генерируем его из name (заголовок записи). Если же не пуст, то обрабатываем поступивший slug.

private function generateSlug( $slug )
{
	$slug = $this->slugify( $slug );
	if ( $this->checkUniqueSlug( $slug ) ) {
		return $slug;
	} else {
		for ( $suffix = 2; !$this->checkUniqueSlug( $new_slug = $slug . '-' . $suffix ); $suffix   ) {}
		return $new_slug;
	}
}

В первой строке способа мы функцией slugify убираем непотребные символы и переводим в транслит, если необходимо. Давайте сразу ее и разглядим:

private function slugify( $slug )
{
	if ( $this->translit ) {
		return Inflector::slug( TransliteratorHelper::process( $slug ), '-', true );
	} else {
		return $this->slug( $slug, '-', true );
	}
}

Что такое транслит? Это передача национальных символов их аналогами в стандартной латинице. Множество сниппетов, обнаруженных в зарубежном интернете, очищают текст только от умляутов, крышечек и прочих символов (‘->owner->{$pk}; } return !$this->owner->find() ->where( $condition, $params ) ->one(); }

Первичный ключ у нас теоритически может быть и не id, следственно находим его функцией primaryKey(). Дальше делаем запрос в таблицу на предмет существования такого slug’а. Если же запись не новая, а мы делаем update (!$this->owner->isNewRecord), то slug теснее может существовать и делаем исключение данного id:

$condition .= ' and ' . $pk . ' != :pk';

Функция возвращает true, если slug уникален, и false, если нет. Дальше:

if ( $this->checkUniqueSlug( $slug ) ) {
	return $slug;
} else {
	for ( $suffix = 2; !$this->checkUniqueSlug( $new_slug = $slug . '-' . $suffix ); $suffix   ) {}
	return $new_slug;
}

Если slug уникален, мы его возвращаем, присваиваем признаку модели и сберегаем модель в базу. Если же не уникален, то добавим цифровой суффикс testovaya-novost-2

for ( $suffix = 2; !$this->checkUniqueSlug( $new_slug = $slug . '-' . $suffix ); $suffix   ) {}

Способом перебора находим 1-й вольный суффикс и добавляем его к slug’у. Решение подсмотрено в WordPress, но мне не нравится, что для всякого суффикса мы делаем по запросу, соответственно при занятых testovaya-novost, testovaya-novost-2, testovaya-novost-3, testovaya-novost-4, testovaya-novost-5 нам необходимо будет сделать 6 запросов для проверки уникальности. Если кто может предложить лучшее решение, буду признателен.

Выходит, slug сгенирован, передан в модель, сохранен в базу, а получившееся поведение мы используем в других моделях.

Полный текст поведения:

<?php

namespace common\behaviors;

use dosamigos\helpers\TransliteratorHelper;
use yii;
use yii\base\Behavior;
use yii\db\ActiveRecord;
use yii\helpers\Inflector;

class Slug extends Behavior
{
	public $in_attribute = 'name';
	public $out_attribute = 'slug';
	public $translit = true;

	public function events()
	{
		return [
			ActiveRecord::EVENT_BEFORE_VALIDATE => 'getSlug'
		];
	}

	public function getSlug( $event )
	{
		if ( empty( $this->owner->{$this->out_attribute} ) ) {
			$this->owner->{$this->out_attribute} = $this->generateSlug( $this->owner->{$this->in_attribute} );
		} else {
			$this->owner->{$this->out_attribute} = $this->generateSlug( $this->owner->{$this->out_attribute} );
		}
	}

	private function generateSlug( $slug )
	{
		$slug = $this->slugify( $slug );
		if ( $this->checkUniqueSlug( $slug ) ) {
			return $slug;
		} else {
			for ( $suffix = 2; !$this->checkUniqueSlug( $new_slug = $slug . '-' . $suffix ); $suffix   ) {}
			return $new_slug;
		}
	}

	private function slugify( $slug )
	{
		if ( $this->translit ) {
			return Inflector::slug( TransliteratorHelper::process( $slug ), '-', true );
		} else {
			return $this->slug( $slug, '-', true );
		}
	}

	private function slug( $string, $replacement = '-', $lowercase = true )
	{
		$string = preg_replace( '/[^\p{L}\p{Nd}] /u', $replacement, $string );
		$string = trim( $string, $replacement );
		return $lowercase ? strtolower( $string ) : $string;
	}

	private function checkUniqueSlug( $slug )
	{
		$pk = $this->owner->primaryKey();
		$pk = $pk[0];

		$condition = $this->out_attribute . ' = :o ut_attribute';
		$params = [ ':out_attribute' => $slug ];
		if ( !$this->owner->isNewRecord ) {
			$condition .= ' and ' . $pk . ' != :pk';
			$params[':pk'] = $this->owner->{$pk};
		}

		return !$this->owner->find()
			->where( $condition, $params )
			->one();
	}
}

Код подключения поведения дан выше. А пример транслитерации:

тест Тест й test

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB