Как составить сложный WHERE в Laravel Eloquent: 4 способа с примерами
Представьте: вы пишете интернет-магазин на Laravel. Нужно найти все товары, которые стоят от 1000 до 5000 рублей, в наличии, но не из категории "Распродажа", и при этом созданы после 1 января 2024 года. Как это сделать одним запросом, не убивая производительность?
Этот вопрос — один из самых популярных на Stack Overflow (тысячи просмотров!). И не зря: умение строить сложные условия WHERE в Laravel Eloquent отличает новичка от продакшен-разработчика.
В этой статье я покажу 4 проверенных способа создавать множественные условия WHERE. Вы узнаете, когда использовать массивы, а когда — замыкания, как группировать условия и как не допустить SQL-инъекций. Поехали!
1. Базовый способ: массив условий (для простых AND)
Самый простой случай — когда все условия соединяются через AND. Laravel позволяет передать в where() обычный массив:
<?php
// Хотим найти активных пользователей из России, созданных в 2024 году
$users = User::where([
['status', '=', 'active'],
['country', '=', 'Russia'],
['created_at', '>=', '2024-01-01'],
])->get();
// SQL: SELECT * FROM users WHERE status = 'active' AND country = 'Russia' AND created_at >= '2024-01-01'
Когда использовать: когда у вас 2-5 простых условий с оператором = или >. Код получается читаемым, но не гибким.
Минус: если нужно смешивать AND и OR, массив не подойдёт.
2. Цепочка методов where() (для динамических условий)
Это самый популярный способ среди разработчиков. Вы просто вызываете where() несколько раз подряд:
<?php
$products = Product::where('price', '>', 1000)
->where('price', '<', 5000)
->where('in_stock', true)
->where('category_id', '!=', 5)
->get();
// SQL: ... WHERE price > 1000 AND price < 5000 AND in_stock = 1 AND category_id <> 5
Хитрость: вы можете комбинировать с другими условиями:
<?php
// Условие "ИЛИ" внутри цепочки
$orders = Order::where('status', 'paid')
->orWhere('status', 'shipped')
->where('total', '>', 100) // Внимание: это условие применяется к предыдущему orWhere!
->get();
// SQL: ... WHERE status = 'paid' OR (status = 'shipped' AND total > 100)
⚠️ Важно! В последнем примере где total > 100 применяется только к status = 'shipped'. Если вам нужно, чтобы оба статуса были с total > 100 — читайте следующий пункт.
3. Группировка условий через замыкания (AND + OR вместе)
Здесь мы подходим к самому мощному инструменту. Когда нужно создать группу условий в скобках — используйте замыкание (closure) внутри where():
<?php
// Задача: найти заказы со статусом "paid" или "shipped",
// но при этом total должен быть > 100 в обоих случаях
$orders = Order::where(function($query) {
$query->where('status', 'paid')
->orWhere('status', 'shipped');
})
->where('total', '>', 100)
->get();
// SQL: ... WHERE (status = 'paid' OR status = 'shipped') AND total > 100
Теперь условия сгруппированы правильно! Вы можете вкладывать замыкания друг в друга сколько угодно раз:
<?php
// Сложный фильтр для аналитики
$data = Sale::where('date', '>=', '2024-01-01')
->where(function($q) {
$q->where('amount', '>', 10000)
->orWhere('is_vip', true);
})
->where(function($q) {
$q->where('region', 'Europe')
->orWhere('region', 'Asia');
})
->get();
// SQL: ... WHERE date >= '2024-01-01'
// AND (amount > 10000 OR is_vip = 1)
// AND (region = 'Europe' OR region = 'Asia')
Жизненный пример: представьте, что вы делаете фильтр товаров на сайте. Пользователь выбрал: цена от 1000 до 5000, бренд Nike или Adidas, скидка не менее 20%. Вот как это будет выглядеть:
<?php
$filteredProducts = Product::whereBetween('price', [1000, 5000])
->where(function($q) {
$q->where('brand', 'Nike')
->orWhere('brand', 'Adidas');
})
->where('discount', '>=', 20)
->orderBy('price')
->paginate(20);
4. Специализированные where-методы (whereIn, whereNull, whereHas)
Laravel даёт множество удобных сокращений. Вот самые полезные:
whereIn — когда нужно проверить множество значений
<?php
// Вместо цепочки orWhere
$users = User::whereIn('role', ['admin', 'editor', 'moderator'])->get();
// SQL: ... WHERE role IN ('admin', 'editor', 'moderator')
whereBetween — диапазон значений
<?php
$events = Event::whereBetween('event_date', ['2024-06-01', '2024-06-30'])->get();
whereNull / whereNotNull — проверка на NULL
<?php
// Найти пользователей, которые не подтвердили email
$unverified = User::whereNull('email_verified_at')->get();
// Найти товары с заполненным описанием
$withDescription = Product::whereNotNull('description')->get();
whereHas — условия по связанным моделям
Это отдельная магия Eloquent. Допустим, у нас есть модель Post с отношением comments(). Хотим найти посты, у которых есть хотя бы 5 комментариев от активных пользователей:
<?php
$popularPosts = Post::whereHas('comments', function($query) {
$query->where('status', 'approved')
->whereHas('user', function($q) {
$q->where('is_active', true);
});
}, '>