Питання на співбесіді: Search
Найпопулярніші питання з реальних Laravel/PHP співбесід для всіх рівнів
3 питання
Найпростіший пошук - фільтр за кількома колонками, і для більшості сайтів цього достатньо надовго.
Запит:
public function index(Request $request)
{
$term = $request->string('q')->trim()->toString();
$vacancies = Vacancy::query()
->when($term !== '', function ($query) use ($term) {
$query->where(function ($query) use ($term) {
$query->where('title', 'like', "%{$term}%")
->orWhere('description', 'like', "%{$term}%");
});
})
->paginate(15)
->withQueryString();
return view('vacancies.index', compact('vacancies'));
}
Дві деталі, на яких зазвичай помиляються:
1. Дужки навколо orWhere. Без вкладеного замикання умова «розклеїться»: where(A)->orWhere(B) разом з іншими фільтрами дасть фільтр AND A OR B, і пошук почне повертати чуже. Вкладене замикання групує умови правильно.
2. Порожній запит. Без when() пошук за порожнім рядком перетвориться на like '%%' - формально працює, але виконує зайву роботу.
Про безпеку: підставляти значення в where() безпечно - Eloquent параметризує запит. Небезпечним є лише whereRaw("title like '%{$term}%'") зі вставкою рядка напряму.
Коли цього перестане вистачати: like '%...%' не використовує індекс і не знає словоформ, тож на десятках тисяч записів пошук стає повільним, а «розробники» не знаходять «розробник». Тоді переходять на повнотекстовий індекс бази або Laravel Scout.
Scout - це загальний інтерфейс до пошукових рушіїв: модель позначається трейтом, а рушій (Meilisearch, Algolia, Typesense, база) підмінюється конфігурацією.
class Vacancy extends Model
{
use Searchable;
/**
* @return array<string, mixed>
*/
public function toSearchableArray(): array
{
return [
'title' => $this->title,
'description' => $this->description,
'company' => $this->company?->name,
];
}
}
Vacancy::search('laravel middle')->paginate(15);
Індексація відбувається автоматично на збереженні моделі, а масово - через php artisan scout:import.
Коли вистачить LIKE:
- Пошук по одній-двох колонках, записів - тисячі.
- Достатньо точного входження, без урахування словоформ.
- Не хочеться ще одного сервісу в інфраструктурі.
Vacancy::where('title', 'like', "%{$term}%")->get();
Де LIKE перестає працювати:
- Не використовує індекс із
%на початку - на великій таблиці це повний перебір. - Не знає словоформ. «розробник» не знайдеться за запитом «розробники», і жодних синонімів.
- Не ранжує. Збіг у заголовку та згадка в кінці опису однакові за вагою.
- Не прощає помилок - один зайвий символ, і результат порожній.
- Не шукає по кількох сутностях одразу.
Проміжний варіант - повнотекстовий індекс самої бази: whereFullText() у MySQL, tsvector у PostgreSQL. Він знімає перші три пункти без окремого сервісу, хоча за якістю ранжування поступається спеціалізованим рушіям.
Практичне правило: починайте з LIKE, переходьте на повнотекстовий індекс, коли обсяг виріс, і на Scout - коли потрібні релевантність, помилки в запиті й фасети.
Laravel Scout - драйверна абстракція повнотекстового пошуку. Додаєте трейт до моделі - і вона автоматично синхронізується з пошуковим індексом (Meilisearch, Algolia, Elasticsearch, навіть database).
class Post extends Model
{
use Searchable;
public function toSearchableArray(): array
{
return ['title' => $this->title, 'body' => $this->body];
}
}
Post::search('laravel queues')->paginate(15);
- Індекс оновлюється на події моделі (краще - через чергу).
- Початкове наповнення:
php artisan scout:import "App\Models\Post". - Meilisearch дає швидкий typo-tolerant пошук «з коробки»; Elasticsearch - складніші аналітичні запити.