Когда стандартных возможностей поиска не хватает, на помощь приходят кастомные фильтры. В этой статье разберём, как в Yii2 построить гибкую систему фильтрации и поиска, которая будет работать быстро и выглядеть красиво. Вы получите готовые рецепты и примеры кода.
Зачем нужны кастомные фильтры
Стандартные фильтры Yii2 (например, через GridView) хороши для простых случаев. Но когда нужно искать по связанным данным, применять сложные условия или динамически менять набор полей, стандартных средств не хватает. Кастомные фильтры дают полный контроль над запросом и позволяют строить поиск как угодно.
Вспомните интернет-магазин: пользователь хочет выбрать товары по цене, бренду, наличию на складе и рейтингу. Всё это должно работать вместе и не тормозить. Вот тут и пригодятся наши умения.
Основы: модель поиска и ActiveQuery
В Yii2 принято выносить логику поиска в отдельный класс — модель поиска (SearchModel). Она расширяет базовую модель и содержит правила валидации для полей фильтра. Внутри метода search() вы строите запрос с помощью ActiveQuery.
Пример простой модели поиска для таблицы product:
namespace app\models;
use yii\base\Model;
use yii\data\ActiveDataProvider;
class ProductSearch extends Model
{
public $name;
public $minPrice;
public $maxPrice;
public function rules()
{
return [
[['name', 'minPrice', 'maxPrice'], 'safe'],
[['minPrice', 'maxPrice'], 'number'],
];
}
public function search($params)
{
$query = Product::find();
// загружаем параметры и валидируем
if (!$this->load($params) || !$this->validate()) {
return new ActiveDataProvider([
'query' => $query,
'pagination' => ['pageSize' => 20],
]);
}
// фильтр по названию
if (!empty($this->name)) {
$query->andFilterWhere(['like', 'name', $this->name]);
}
// фильтр по цене
if (!empty($this->minPrice)) {
$query->andFilterWhere(['>=', 'price', $this->minPrice]);
}
if (!empty($this->maxPrice)) {
$query->andFilterWhere(['<=', 'price', $this->maxPrice]);
}
return new ActiveDataProvider([
'query' => $query,
'pagination' => ['pageSize' => 20],
'sort' => ['defaultOrder' => ['id' => SORT_DESC]],
]);
}
}
Этот код уже рабочий: вы можете передать параметры из формы, и поиск отфильтрует записи. Обратите внимание на andFilterWhere — он игнорирует пустые значения, что удобно.
Продвинутые фильтры: связи и сложные условия
Часто нужно фильтровать по связанным таблицам. Например, товары по категориям или тегам. В Yii2 для этого есть joinWith и andWhere с условиями по связанным полям.
Допустим, у товара есть связь category (many-to-one) и tags (many-to-many). Добавим фильтры по категории и тегам.
// в модели ProductSearch добавляем свойства
public $categoryId;
public $tagIds = [];
// в правилах валидации
[['categoryId'], 'integer'],
[['tagIds'], 'each', 'rule' => ['integer']],
// в методе search()
if (!empty($this->categoryId)) {
$query->andWhere(['category_id' => $this->categoryId]);
}
if (!empty($this->tagIds)) {
// используем подзапрос для many-to-many
$query->andWhere(['in', 'product.id',
ProductTag::find()->select('product_id')->where(['tag_id' => $this->tagIds])
]);
}
Для many-to-many лучше использовать подзапрос, чтобы избежать дублирования записей при жадной загрузке. Если нужно вывести связанные данные в списке, не забудьте про with().
andFilterWhere вместо andWhere для пользовательских фильтров, если не хотите, чтобы пустые значения ломали запрос. Исключение — когда пустое значение имеет смысл (например, «показать без категории»).
Оптимизация: индексы и жадная загрузка
Кастомные фильтры могут сильно нагружать базу. Чтобы поиск летал, следите за индексами. Поля, по которым чаще всего фильтруете (цена, название, категория), должны быть проиндексированы. В Yii2 миграции позволяют добавить индексы легко:
$this->createIndex('idx-product-price', 'product', 'price');
$this->createIndex('idx-product-category', 'product', 'category_id');
Если выводите связанные данные (например, категорию для каждого товара), используйте жадную загрузку with('category'). Это уменьшит количество запросов.
Для сложных фильтров с множеством условий применяйте filterWhere и andFilterWhere — они автоматически отбрасывают пустые параметры.
| Метод | Когда использовать |
|---|---|
andFilterWhere |
Для условий, которые нужно игнорировать при пустом значении |
andWhere |
Когда пустое значение — валидный фильтр (например, IS NULL) |
joinWith |
Для фильтрации по связанным таблицам с жадной загрузкой |
Валидация и безопасность
Никогда не доверяйте данным из формы. Всегда валидируйте поля фильтра. Yii2 предоставляет мощный валидатор. Для числовых полей используйте integer или number, для строк — string с ограничением длины. Это защитит от SQL-инъекций и неожиданных ошибок.
Пример правил для поисковой модели:
public function rules()
{
return [
[['name'], 'string', 'max' => 255],
[['minPrice', 'maxPrice'], 'number', 'min' => 0],
[['categoryId'], 'integer'],
[['tagIds'], 'each', 'rule' => ['integer']],
];
}
Также стоит ограничить количество записей на страницу, чтобы пользователь не мог запросить всё сразу. В ActiveDataProvider укажите pagination с разумным pageSize.
ProductFilter для валидации и загрузки параметров, а в ProductSearch используйте его. Это разделит ответственность и упростит тестирование.
Что в итоге
Кастомные фильтры в Yii2 — это не сложно, но требует аккуратности. Вы научились создавать модель поиска, добавлять условия для связанных данных, оптимизировать запросы и защищаться от ошибок. Помните про индексы, валидацию и жадную загрузку — и ваш поиск будет быстрым и удобным.
Пробуйте, экспериментируйте, и пусть фильтры работают как часы!
Студия WNDER


