SearchFilter
- это специализированный фильтр для полнотекстового поиска по нескольким полям одновременно. Поддерживает
поиск как по простым полям модели, так и по полям связанных моделей через точечную нотацию.
Request → CustomQueryBuilder::applySearch()
→ SearchFilter::apply()
→ RelationFieldParser::parse() (для каждого поля)
→ applySearchDirect() или applySearchWithRelation()
-
SearchFilter (
app/Support/QueryBuilders/Filters/SearchFilter.php
)- Основной класс обработки поиска
- Обрабатывает массив полей для поиска
- Автоматически определяет наличие relations
-
RelationFieldParser (
app/Support/QueryBuilders/Filters/Support/RelationFieldParser.php
)- Парсит поля с точечной нотацией
- Разделяет relation path и конечное поле
- Пример:
user.profile.city
→ relations:['user', 'profile']
, field:'city'
{
"search": {
"query": "значение для поиска",
"fields": [
"поле1",
"поле2",
"relation.поле3"
]
}
}
Параметр | Тип | Обязательный | Описание |
---|---|---|---|
query |
string|number |
✅ Да | Поисковый запрос |
value |
string|number |
Алиас для query |
|
fields |
array |
✅ Да | Массив полей для поиска |
WHERE (field ILIKE '%query%')
- Использует ILIKE (регистронезависимый поиск в PostgreSQL)
- Автоматически добавляет wildcards
%
с обеих сторон - Поиск вхождения подстроки в любом месте поля
WHERE (field ILIKE '%query%' OR field = query)
- Выполняет два условия:
ILIKE
поиск (для частичного совпадения, например: "123" найдет "12345")- Точное совпадение
=
(для полного совпадения числа)
Все поля объединяются через OR:
WHERE (
field1 ILIKE '%query%'
OR field2 ILIKE '%query%'
OR field3 ILIKE '%query%'
)
Система автоматически определяет relations по наличию точки в имени поля:
"user.email" → relation detected
"email" → direct field
Для полей с relations используется Laravel orWhereHas:
$builder->orWhereHas('relationName', function($query) use ($field, $searchQuery) {
$query->where($field, 'ILIKE', "%{$searchQuery}%");
if (is_numeric($searchQuery)) {
$query->orWhere($field, '=', $searchQuery);
}
});
Поддерживается неограниченная вложенность:
{
"search": {
"query": "Moscow",
"fields": [
"user.profile.city.name"
]
}
}
Преобразуется в:
OR EXISTS (
SELECT * FROM users
WHERE table.user_id = users.id
AND EXISTS (
SELECT * FROM profiles
WHERE users.profile_id = profiles.id
AND EXISTS (
SELECT * FROM cities
WHERE profiles.city_id = cities.id
AND cities.name ILIKE '%Moscow%'
)
)
)
Запрос:
{
"search": {
"query": "laptop",
"fields": [
"name"
]
}
}
SQL:
WHERE (name ILIKE '%laptop%')
Запрос:
{
"search": {
"query": "apple",
"fields": [
"name",
"description",
"brand"
]
}
}
SQL:
WHERE (
name ILIKE '%apple%'
OR description ILIKE '%apple%'
OR brand ILIKE '%apple%'
)
Запрос:
{
"search": {
"query": "1000",
"fields": [
"price",
"sku"
]
}
}
SQL:
WHERE (
(price ILIKE '%1000%' OR price = 1000)
OR (sku ILIKE '%1000%' OR sku = 1000)
)
Результаты:
- Товары с ценой ровно 1000
- Товары с ценой 10000, 21000 (содержат "1000")
- SKU вида "ABC-1000-XYZ" или "1000"
Запрос:
{
"search": {
"query": "electronics",
"fields": [
"name",
"category.name"
]
}
}
SQL:
WHERE (
name ILIKE '%electronics%'
OR EXISTS (
SELECT * FROM categories
WHERE products.category_id = categories.id
AND categories.name ILIKE '%electronics%'
)
)
Запрос:
{
"search": {
"query": "john",
"fields": [
"title",
"author.name",
"author.profile.bio",
"publisher.city.name"
]
}
}
SQL:
WHERE (
title ILIKE '%john%'
OR EXISTS (
SELECT * FROM authors
WHERE books.author_id = authors.id
AND authors.name ILIKE '%john%'
)
OR EXISTS (
SELECT * FROM authors
WHERE books.author_id = authors.id
AND EXISTS (
SELECT * FROM profiles
WHERE authors.profile_id = profiles.id
AND profiles.bio ILIKE '%john%'
)
)
OR EXISTS (
SELECT * FROM publishers
WHERE books.publisher_id = publishers.id
AND EXISTS (
SELECT * FROM cities
WHERE publishers.city_id = cities.id
AND cities.name ILIKE '%john%'
)
)
)
Запрос:
{
"search": {
"query": "@gmail.com",
"fields": [
"email",
"user.email",
"contact.email"
]
}
}
SQL:
WHERE (
email ILIKE '%@gmail.com%'
OR EXISTS (
SELECT * FROM users
WHERE records.user_id = users.id
AND users.email ILIKE '%@gmail.com%'
)
OR EXISTS (
SELECT * FROM contacts
WHERE records.contact_id = contacts.id
AND contacts.email ILIKE '%@gmail.com%'
)
)
Запрос:
{
"search": {
"query": "+7",
"fields": [
"phone",
"mobile",
"user.phone"
]
}
}
SQL:
WHERE (
phone ILIKE '%+7%'
OR mobile ILIKE '%+7%'
OR EXISTS (
SELECT * FROM users
WHERE orders.user_id = users.id
AND users.phone ILIKE '%+7%'
)
)
Запрос:
{
"search": {
"query": "12345",
"fields": [
"sku",
"barcode",
"article"
]
}
}
SQL:
WHERE (
(sku ILIKE '%12345%' OR sku = 12345)
OR (barcode ILIKE '%12345%' OR barcode = 12345)
OR (article ILIKE '%12345%' OR article = 12345)
)
Найдет:
- SKU: "12345" (точное совпадение)
- SKU: "ABC-12345-XYZ" (частичное совпадение)
- Barcode: "1234567890" (содержит "12345")
Запрос:
{
"search": {
"query": "laptop",
"fields": [
"name",
"description",
"brand.name"
]
},
"where": {
"status": "active",
"price": {
"operator": ">=",
"value": 500
}
},
"where_has": {
"category": {
"where": {
"slug": "electronics"
}
}
}
}
SQL:
WHERE (
name ILIKE '%laptop%'
OR description ILIKE '%laptop%'
OR EXISTS (
SELECT * FROM brands
WHERE products.brand_id = brands.id
AND brands.name ILIKE '%laptop%'
)
)
AND status = 'active'
AND price >= 500
AND EXISTS (
SELECT * FROM categories
WHERE products.category_id = categories.id
AND categories.slug = 'electronics'
)
Запрос:
{
"search": {
"value": "search term",
"fields": [
"title",
"content"
]
}
}
Эквивалентно:
{
"search": {
"query": "search term",
"fields": [
"title",
"content"
]
}
}
{
"search": {
"query": "",
"fields": [
"name"
]
}
}
Результат: фильтр не применяется, возвращаются все записи.
{
"search": {
"query": "laptop",
"fields": []
}
}
Результат: фильтр не применяется.
Если указано несуществующее поле:
{
"search": {
"query": "test",
"fields": [
"non_existent_field"
]
}
}
Результат: SQL ошибка при выполнении запроса.
Решение: Валидация полей в Form Request:
public function rules()
{
return [
'search.query' => 'required|string|max:255',
'search.fields' => 'required|array|min:1',
'search.fields.*' => 'string|in:name,description,sku,brand.name',
];
}
При указании несуществующего relation:
{
"search": {
"query": "test",
"fields": [
"non_existent_relation.field"
]
}
}
Результат: Laravel выбросит исключение RelationNotFoundException
.
Search:
{
"search": {
"query": "laptop",
"fields": [
"name",
"description"
]
}
}
Where с LIKE:
{
"or_where": {
"name": {
"operator": "%like%",
"value": "laptop",
"type": "string"
},
"description": {
"operator": "%like%",
"value": "laptop",
"type": "string"
}
}
}
Разница:
search
компактнее для множественных полейsearch
автоматически добавляет точное совпадение для чиселwhere
с%like%
дает больше контроля (можно использовать разные операторы для каждого поля)
SearchFilter
предоставляет:
✅ Простоту - компактный синтаксис для поиска по множеству полей
✅ Умный поиск - автоматическое определение числовых значений
✅ Relations - поддержка вложенных связей через точечную нотацию
✅ Регистронезависимость - ILIKE для PostgreSQL
✅ Производительность - использование нативных Laravel методов
✅ Гибкость - комбинирование с другими фильтрами
Используйте SearchFilter для:
- Поиска по каталогу товаров
- Поиска пользователей по имени/email/телефону
- Поиска заказов по номеру/статусу
- Глобального поиска по сайту
Не используйте SearchFilter для:
- Точной фильтрации (используйте
where
) - Диапазонов (используйте
where_between
) - Сложных условий (используйте
where_has
с вложенными фильтрами)