Junior: питання на співбесіді з теми «Пошук»
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
2 питання
Найпростіший пошук - фільтр за кількома колонками, і для більшості сайтів цього достатньо надовго.
Запит:
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 дає єдиний API пошуку для моделей (Post::search('laravel')->paginate()), а сам пошук виконує рушій: Meilisearch, Typesense, Algolia - або власна база даних.
Два рушії не потребують жодного зовнішнього сервісу.
Рушій database:
SCOUT_DRIVER=database
- шукає прямо в таблицях через повнотекстові індекси MySQL/PostgreSQL та умови
LIKE; - окремого індексування немає - дані завжди актуальні,
scout:importне потрібен; - стратегію пошуку для колонок можна задати атрибутами:
use Laravel\Scout\Attributes\{SearchUsingFullText, SearchUsingPrefix};
#[SearchUsingPrefix(['id', 'email'])]
#[SearchUsingFullText(['title', 'body'])]
public function toSearchableArray(): array
{
return ['id' => $this->id, 'title' => $this->title, 'body' => $this->body];
}
SearchUsingPrefix шукає текст% (може використати звичайний індекс), SearchUsingFullText - через повнотекстовий індекс, решта колонок - %текст%.
Рушій collection:
SCOUT_DRIVER=collection
- завантажує всі записи моделі з бази й фільтрує їх у PHP;
- працює з будь-якою базою, включно з SQLite, - зручно для тестів і прототипів;
- для кількох сотень записів - нормально, для таблиці на сотні тисяч - пам'ять і час ростуть лінійно.
Порівняння:
| collection | database | Meilisearch/Typesense | |
|---|---|---|---|
| інфраструктура | нічого | нічого | окремий сервер |
| обсяг даних | сотні записів | десятки-сотні тисяч | мільйони |
| стійкість до опечаток | ні | ні | так |
| ранжування, фасети, підсвічування | ні | обмежено | так |
| актуальність | миттєва | миттєва | після індексування |
Практичний підхід: почати з database - код пошуку однаковий для всіх рушіїв, тож коли знадобиться стійкість до опечаток чи релевантність, достатньо змінити драйвер і проіндексувати дані. А collection - у тестах, щоб не піднімати пошуковий сервер.
Докладніше в документації: Scout: рушії database і collection