Именованные условия как scopes в ActiveQuery
  • 1083

Именованные группы условий (scopes) ActiveQuery в yii2.

Автор: admin | 07 мая (Пн.) 2018г. в 20ч.32м.

Yii2 ActiveQuery scopes - именованные группы условий

Scopes - это именованные группы условий для более удобного и, можно сказать красивого написания
набора ActiveRecord запросов.

Рассмотрим как это работает на примере блога.

Допустим, есть таблица постов блога:
--
-- Структура таблицы `blogArticles`
--

CREATE TABLE `blogArticles` (
`id` int(10) UNSIGNED NOT NULL,
`idCategory` int(10) UNSIGNED NOT NULL,
`alias` varchar(255) NOT NULL,
`metaTitle` varchar(255) NOT NULL,
`metaDesc` text NOT NULL,
`metaKeywords` varchar(255) DEFAULT NULL,
`title` varchar(255) NOT NULL,
`text` text NOT NULL,
`faceImg` varchar(255) NOT NULL DEFAULT 'default_blog_post.jpg',
`faceImgAlt` varchar(255) NOT NULL,
`flagActive` tinyint(1) NOT NULL DEFAULT '1',
`createdAt` datetime DEFAULT NULL,
`updatedAt` datetime DEFAULT NULL,
`createdBy` int(11) UNSIGNED NOT NULL,
`updatedBy` int(11) UNSIGNED NOT NULL,
`viewCount` int(11) DEFAULT '0'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;​

Также имеется таблица категорий блога:

--
-- Структура таблицы `blogCategories`
--
CREATE TABLE `blogCategories` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`alias` varchar(255) NOT NULL,
`metaTitle` varchar(255) NOT NULL,
`metaDesc` varchar(255) NOT NULL,
`metaKeywords` varchar(255) DEFAULT NULL,
`title` varchar(255) NOT NULL,
`sort_order` int(11) NOT NULL DEFAULT '1',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8
​

У нас есть две таблицы категорий и материалов блога.
Также в таблице blogArticles есть связь один к одному (англ. one to one), а в `blogCategories` один ко многим
(англ. one to many). Таким образом к одной категории может относиться много статей, а к одной статье - одна
категория.
Сгенерируем наши модели с помощью gii.

Генерация модели в yii2

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

Выбираем модели для генерации.

А теперь непосредственно код, что создал gii:

<?php

namespace backend\models\fragments;

use Yii;

/**
* This is the model class for table "blogArticles".
*
* @property int $id
* @property int $idCategory
* @property string $alias
* @property string $metaTitle
* @property string $metaDesc
* @property string $metaKeywords
* @property string $title
* @property string $text
* @property string $faceImg
* @property string $faceImgAlt
* @property int $flagActive
* @property string $createdAt
* @property string $updatedAt
* @property int $createdBy
* @property int $updatedBy
* @property int $viewCount
* @property BlogCategories $category
* @property User $createdBy
*/
class BlogArticles extends \yii\db\ActiveRecord
{
/**
* @inheritdoc
*/
public static function tableName()
{
return 'blogArticles';
}

/**
* @inheritdoc
*/
public function rules()
{
return [
[['idCategory', 'alias', 'metaTitle', 'metaDesc', 'title', 'text', 'faceImgAlt', 'createdBy', 'updatedBy'], 'required'],
[['idCategory', 'createdBy', 'updatedBy', 'viewCount'], 'integer'],
[['metaDesc', 'text'], 'string'],
[['createdAt', 'updatedAt'], 'safe'],
[['alias', 'metaTitle', 'metaKeywords', 'title', 'faceImg', 'faceImgAlt'], 'string', 'max' => 255],
[['flagActive'], 'string', 'max' => 1],
[['alias'], 'unique'],
[['idCategory'], 'exist', 'skipOnError' => true, 'targetClass' => BlogCategories::className(), 'targetAttribute' => ['idCategory' => 'id']],
[['createdBy'], 'exist', 'skipOnError' => true, 'targetClass' => User::className(), 'targetAttribute' => ['createdBy' => 'id']],
];
}

/**
* @inheritdoc
*/
public function attributeLabels()
{
return [
'id' => Yii::t('app/admin', 'ID'),
'idCategory' => Yii::t('app/admin', 'Id Category'),
'alias' => Yii::t('app/admin', 'Alias'),
'metaTitle' => Yii::t('app/admin', 'Meta Title'),
'metaDesc' => Yii::t('app/admin', 'Meta Desc'),
'metaKeywords' => Yii::t('app/admin', 'Meta Keywords'),
'title' => Yii::t('app/admin', 'Title'),
'text' => Yii::t('app/admin', 'Text'),
'faceImg' => Yii::t('app/admin', 'Face Img'),
'faceImgAlt' => Yii::t('app/admin', 'Face Img Alt'),
'flagActive' => Yii::t('app/admin', 'Flag Active'),
'createdAt' => Yii::t('app/admin', 'Created At'),
'updatedAt' => Yii::t('app/admin', 'Updated At'),
'createdBy' => Yii::t('app/admin', 'Created By'),
'updatedBy' => Yii::t('app/admin', 'Updated By'),
'viewCount' => Yii::t('app/admin', 'View Count'),
];
}

/**
* @return \yii\db\ActiveQuery
*/
public function getCategory()
{
return $this->hasOne(BlogCategories::className(), ['id' => 'idCategory']);
}

/**
* @return \yii\db\ActiveQuery
*/
public function getCreatedBy()
{
return $this->hasOne(User::className(), ['id' => 'createdBy']);
}



/**
* @inheritdoc
* @return BlogArticlesQuery the active query used by this AR class.
*/
public static function find()
{
return new BlogArticlesQuery(get_called_class());
}
}


<?php

namespace backend\models\fragments;

/**
* This is the ActiveQuery class for [[BlogArticles]].
*
* @see BlogArticles
*/
class BlogArticlesQuery extends \yii\db\ActiveQuery
{
/*public function active()
{
return $this->andWhere('[[status]]=1');
}*/

/**
* @inheritdoc
* @return BlogArticles[]|array
*/
public function all($db = null)
{
return parent::all($db);
}

/**
* @inheritdoc
* @return BlogArticles|array|null
*/
public function one($db = null)
{
return parent::one($db);
}
}​

То же самое для категорий.
<?php

namespace backend\models\fragments;

use Yii;

/**
* This is the model class for table "blogCategories".
*
* @property int $id
* @property string $alias
* @property string $metaTitle
* @property string $metaDesc
* @property string $metaKeywords
* @property string $title
* @property int $sort_order
*
* @property BlogArticles[] $blogArticles
*/
class BlogCategories extends \yii\db\ActiveRecord
{
/**
* @inheritdoc
*/
public static function tableName()
{
return 'blogCategories';
}

/**
* @inheritdoc
*/
public function rules()
{
return [
[['alias', 'metaTitle', 'metaDesc', 'title'], 'required'],
[['sort_order'], 'integer'],
[['alias', 'metaTitle', 'metaDesc', 'metaKeywords', 'title'], 'string', 'max' => 255],
];
}

/**
* @inheritdoc
*/
public function attributeLabels()
{
return [
'id' => Yii::t('app/admin', 'ID'),
'alias' => Yii::t('app/admin', 'Alias'),
'metaTitle' => Yii::t('app/admin', 'Meta Title'),
'metaDesc' => Yii::t('app/admin', 'Meta Desc'),
'metaKeywords' => Yii::t('app/admin', 'Meta Keywords'),
'title' => Yii::t('app/admin', 'Title'),
'sort_order' => Yii::t('app/admin', 'Sort Order'),
];
}

/**
* @return \yii\db\ActiveQuery
*/
public function getBlogArticles()
{
return $this->hasMany(BlogArticles::className(), ['idCategory' => 'id']);
}

/**
* @inheritdoc
* @return BlogCategoriesQuery the active query used by this AR class.
*/
public static function find()
{
return new BlogCategoriesQuery(get_called_class());
}
}


<?php

namespace backend\models\fragments;

/**
* This is the ActiveQuery class for [[BlogCategories]].
*
* @see BlogCategories
*/
class BlogCategoriesQuery extends \yii\db\ActiveQuery
{
/*public function active()
{
return $this->andWhere('[[status]]=1');
}*/

/**
* @inheritdoc
* @return BlogCategories[]|array
*/
public function all($db = null)
{
return parent::all($db);
}

/**
* @inheritdoc
* @return BlogCategories|array|null
*/
public function one($db = null)
{
return parent::one($db);
}
}

​

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

Итак, допустим нужно сделать выборку всех статей, которые активны, т.е. $flagActive = 1

Для начала добавим константы для поля активности в класс BlogArticles
class BlogArticles extends \yii\db\ActiveRecord
{

const ACTIVE_STATUS = 1;//число активной статьи
const DISABLED_STATUS = 0;//число отключенной статьи

public static $statusesName = [
self::ACTIVE_STATUS => 'Активен',
self::DISABLED_STATUS => 'Отключен',
];

...
​

Теперь сделаем выборку (не забываем про пространства имен):
$articles = BlogArticles::find()
->where(['flagActive' => BlogArticles::ACTIVE_STATUS])
->orderBy(['createdAt' => SORT_DESC])
->all();​


Теперь добавим именованные группы условий, наши scopes в класс BlogArticlesQuery
/**
* This is the ActiveQuery class for [[BlogArticles]].
*
* @see BlogArticles
*/
class BlogArticlesQuery extends \yii\db\ActiveQuery
{
public function active()
{
return $this->andWhere(['flagActive' => BlogArticles::ACTIVE_STATUS]);
}

//от новых к старым
public function orderCreatedAt($sort = SORT_DESC)
{
return $this->orderBy(['createdAt' => $sort]);
}

public function orderUpdatedAt($sort = SORT_DESC)
{
return $this->orderBy(['updatedAt' => $sort]);
}

.....​

Изменим вид выборки с использованием scopes:
$articles = BlogArticles::find()
->active()
->orderCreatedAt()//по умолчанию SORT_DESC
->all();​

Так уже немного веселее :)

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

$category = BlogCategories::find()->where(['alias' => 'category_alias'])->one();

$articlesInCategory = $category->getBlogArticles()->active()->orderCreatedAt()->all();​


Добавим scopes в класс ActiveQuery категорий
/**
* This is the ActiveQuery class for [[BlogCategories]].
*
* @see BlogCategories
*/
class BlogCategoriesQuery extends \yii\db\ActiveQuery
{

public function findAlias($alias)
{
return $this->andWhere(['alias' => $alias]);
}



...
​

А в BlogCategories добавляем
class BlogCategories extends \yii\db\ActiveRecord
{

...

public function getOwnArticles()
{
return $this->getBlogArticles()->active()->orderCreatedAt();
}

...​

Теперь мы тот же запрос представим в таком виде:
$category = BlogCategories::find()->findAlias('yii2')->one();
$articlesInCategory = $category->getOwnArticles()->all();​

Так как getOwnArticles() это геттер, то можно получить набор статей так:

$articlesInCategory = $category->ownArticles​;

Однако в данном случае вернется массив из ActiveRecord объектов, в отличие от использования getOwnArticles()
который вернет массив объект типа yii\db\ActiveQuery. Поэтому, если нужно добавлять дополнительные условия
к запросу, то лучше использовать вариант с вызовом метода getOwnArticles().
Тогда возможно расширять запрос например так:
$articlesInCategory = $category->getOwnArticles()->limit(100)->all();​

А $category->ownArticles->limit(100)->all(); не сработает

Также можно использовать жадную загрузку и получить все сразу:
$category = BlogCategories::find()->with('ownArticles')->all();​

Или если нужно расширить запрос, то так и добавить условие к ownArticles:
$category = BlogCategories::find()->with([
'ownArticles' => function ($query) {
$query->limit(1);
},
])->all();​

Таким образом именованные условия или выражения могут улучшить читаемость кода и упорядочить код.
На этом все. Удачного программирования!

Приветствую!

Меня зовут Сергей. Я - автор этого блога.

Если Вам был полезен материал на моем сайте, поддержите пожалуйста мой проект, чтобы о нем узнали другие люди - кликните plizz :) на иконку в соц. сети, чтобы поделиться материалом с другими.