- Laravel umożliwia implementację wszystkiego, od prostych wyszukiwarek z wykorzystaniem AJAX po zaawansowane wyszukiwanie pełnotekstowe z wykorzystaniem Laravel Scout i zewnętrznych wyszukiwarek, takich jak Algolia, Meilisearch lub Elasticsearch.
- W przypadku lekkich wyszukiwań filtrowanie w interfejsie użytkownika za pomocą Alpine.js lub natywnych żądań pobierania pozwala uniknąć przeciążenia serwera i usprawnia korzystanie z małych list.
- Laravel Scout centralizuje integrację z różnymi wyszukiwarkami i ułatwia oznaczanie modeli jako możliwych do wyszukiwania, zarządzanie indeksami i jednolite uruchamianie zapytań.
- Wybór silnika (SaaS, open source lub baza danych) powinien zależeć od objętości danych, złożoności wyszukiwań oraz wymagań dotyczących wydajności i konserwacji projektu.
Gdy zaczynasz pracę z Laravel i potrzebujesz wyszukiwarki działającej w czasie rzeczywistym, która reaguje natychmiast , łatwo jest zgubić się wśród tysiąca możliwych podejść: AJAX z pobieraniem, jQuery, Alpine.js, Scout z Algolia lub Meilisearch, filtrowanie front-endu itd. Dobra wiadomość jest taka, że ekosystem Laravel zapewnia już praktycznie wszystko, czego potrzebujesz, aby zbudować płynnie działającą i szybką wyszukiwarkę, która nie przytłoczy Cię.
W tym artykule dowiesz się, jak wdrożyć różne typy wyszukiwania w czasie rzeczywistym w Laravel , od klasycznego autouzupełniania AJAX po wyszukiwanie pełnotekstowe z wykorzystaniem Laravel Scout i wyszukiwarek takich jak Algolia, Meilisearch, samej bazy danych, a nawet Elasticsearch. Poznasz również lekkie alternatywy z Alpine.js do filtrowania danych bezpośrednio w przeglądarce w przypadku małych zestawów danych.
Czym jest wyszukiwanie w czasie rzeczywistym w Laravel i jak działają jego podstawy?
Idea stojąca za wyszukiwaniem w czasie rzeczywistym polega na tym, że gdy użytkownik wpisuje tekst w polu tekstowym , uruchamiane jest zapytanie, a wyniki są aktualizowane bez przeładowywania strony. Technicznie rzecz biorąc, wymaga to trzech kluczowych komponentów: zaplecza Laravel, JavaScriptu przeglądarki oraz wymiany danych w formacie JSON.
Z jednej strony Laravel działa jako warstwa serwerowa odpowiedzialna za odbieranie żądań, interpretowanie parametrów wyszukiwania (wprowadzonego tekstu), przeszukiwanie bazy danych i zwracanie ustrukturyzowanej odpowiedzi, zazwyczaj w formacie JSON. Odpowiedź ta może wskazywać na powodzenie, błąd lub brak wyników.
Z drugiej strony JavaScript obsługuje nasłuchiwanie zdarzeń użytkownika w polu wyszukiwania, wysyłanie asynchronicznych żądań (AJAX) do zaplecza i renderowanie zwróconych danych na stronie bez konieczności pełnego odświeżania przeglądarki. Można to zrobić za pomocą natywnego pobierania, jQuery AJAX lub małych bibliotek reaktywnych, takich jak Alpine.js.
Dzięki temu podstawowemu mechanizmowi możesz zbudować wszystko, od prostego autouzupełniania z kilkoma rekordami po zaawansowaną wyszukiwarkę pełnotekstową z uwzględnieniem trafności, paginacji i filtrów, opierając się na bibliotekach takich jak Laravel Scout i zewnętrznych wyszukiwarkach zoptymalizowanych pod kątem przeszukiwania.
Model, trasy i kontroler dla podstawowej wyszukiwarki czasu rzeczywistego
Zanim zagłębisz się w JavaScript, strona Laravel musi być dobrze zorganizowana: model Eloquent do wyszukiwania, przejrzyste ścieżki i kontroler przeznaczony do obsługi logiki wyszukiwania w czasie rzeczywistym.
Pierwszym krokiem jest stworzenie modelu Eloquent reprezentującego tabelę, w której zamierzasz przeszukiwać. Wyobraź sobie tabelę krajów i bardzo prosty model o nazwie „Kraj” , bez znaczników czasu i z możliwością zbiorczego przypisywania:
Przykład minimalnego modelu Eloquent do wyszukiwania :
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Pais extends Model
{
use HasFactory;
protected $guarded = [];
public $timestamps = false;
}
Oznacza to, że model Pais znajduje się w standardowej przestrzeni nazw Laravel , dziedziczy po Modelu i pozwala na przypisanie dowolnego pola za pomocą funkcji create() poprzez pozostawienie tablicy chronionej pustej. Wyłączając znaczniki czasu za pomocą public $timestamps = false, unikniesz problemów, jeśli tabela nie ma kolumn created_at i updated_at.
Kolejnym krokiem jest zdefiniowanie tras, które będą obsługiwać zarówno wyświetlanie w wyszukiwarce, jak i żądania AJAX . Bardzo popularny schemat łączy trasę GET do wyświetlania widoku z trasą POST przeznaczoną do odbierania zapytań w czasie rzeczywistym:
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\BuscadorController;
Route::get('/', function () {
return view('welcome');
});
Route::get('buscador', [BuscadorController::class, 'index']);
Route::post('buscador', [BuscadorController::class, 'buscar']);
Trasa główna zwraca widok powitalny, natomiast adres URL /search jest zarezerwowany dla funkcji wyszukiwania . Metoda index() kontrolera wyświetla formularz i dane wejściowe wyszukiwania, podczas gdy metoda search() przetwarza asynchroniczne żądania wysyłane z przeglądarki.
W kontrolerze możesz wdrożyć bardzo praktyczny wzorzec: przygotuj domyślną tablicę odpowiedzi na wypadek błędu i nadpisz ją tylko wtedy, gdy będzie to faktycznie prawidłowe żądanie AJAX, a zapytanie zostanie wykonane bez problemów.
Kontroler może mieć strukturę podobną do tej:
namespace App\Http\Controllers;
use App\Models\Pais;
use Illuminate\Http\Request;
class BuscadorController extends Controller
{
public function index()
{
return view('welcome');
}
public function buscar(Request $request)
{
$response = [
'success' => false,
'message' => 'Hubo un error',
];
if ($request->ajax()) {
$data = Pais::where('nombre', 'like', $request->texto.'%')
->take(10)
->get();
$response = [
'success' => true,
'message' => 'Consulta correcta',
'data' => $data,
];
}
return response()->json($response);
}
}
W tym momencie mamy już pełny cykl backendu: przychodzące żądanie AJAX, weryfikację, czy rzeczywiście jest to AJAX, zapytanie z użyciem `where like` i ograniczenie liczby wyników do rozsądnej wartości za pomocą `take(10)`, aby uniknąć przeciążenia bazy danych. Odpowiedź jest zawsze wysyłana w formacie JSON, co znacznie upraszcza pracę frontendu.
Widok Blade i pobieranie JavaScript do reaktywnego wyszukiwania
Mając już gotowy model, trasy i kontroler, można przystąpić do tworzenia widocznej części: formularza z polem wyszukiwania i blokiem wyświetlającym wyniki , a także kodu JavaScript odpowiedzialnego za wykonywanie żądań w tle.
Widok Blade może być bardzo prosty, wykorzystujący token CSRF wstrzyknięty przez Laravel w celu walidacji żądań POST oraz wygodne w użyciu pole wyszukiwania:
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<strong><meta name="csrf-token" content="{{ csrf_token() }}"></strong>
<title>Laravel</title>
</head>
<body>
<form action="" method="post">
<input type="search" name="texto" id="buscar">
</form>
<div id="resultado"></div>
<script>
window.addEventListener('load', function () {
const buscar = document.getElementById('buscar');
const resultado = document.getElementById('resultado');
buscar.addEventListener('keyup', function () {
fetch('/buscador', {
method: 'post',
body: JSON.stringify({ texto: buscar.value }),
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-Token': document.head.querySelector('[name~="csrf-token"][content]').content,
},
})
.then(response => response.json())
.then(data => {
let html = '';
if (data.success) {
html += '<ul>';
for (let i in data.data) {
html += '<li>' + data.data[i].nombre + '</li>';
}
html += '<ul>';
} else {
html += 'No existen resultados';
}
resultado.innerHTML = html;
});
});
});
</script>
</body>
</html>
W tym przykładzie skrypt nasłuchuje zdarzenia keyup w polu wyszukiwania , więc każde naciśnięcie klawisza wyzwala żądanie pobrania do ścieżki /search. Aktualny tekst pola jest wysyłany w formacie JSON, a nagłówki klucza, takie jak X-Requested-With, wskazują, że jest to AJAX, wraz z tokenem CSRF, aby ominąć natywną ochronę Laravela.
Po otrzymaniu odpowiedzi jest ona konwertowana do formatu JSON i dynamicznie generowana jest krótka lista HTML z wynikami . Jeśli zapytanie nie zwróci żadnych danych, wyświetlany jest komunikat „Nie znaleziono wyników”. Wszystko to odbywa się bez przeładowywania strony, w sposób naturalny dla użytkownika.
Ten schemat można dodatkowo udoskonalić, wprowadzając drobne szczegóły UX, np. dodając opóźnienie między naciśnięciami klawiszy, wyświetlając moduł ładujący lub obsługując błędy sieciowe, aby zapobiec zawieszaniu się interfejsu w przypadku awarii.
Wyszukiwanie na żywo z Laravel i AJAX przy użyciu jQuery
Chociaż pobieranie (fetch) zyskało znaczną popularność, jQuery AJAX pozostaje bardzo popularny w starszych projektach lub zespołach, które już je zintegrowały. Idea jest dokładnie ta sama: przechwytywanie wpisów użytkownika, wysyłanie asynchronicznego żądania i odświeżanie DOM.
Typowy przepływ pracy z jQuery w Laravel w przypadku wyszukiwania na żywo zwykle obejmuje następujące podstawowe kroki: zdefiniowanie konkretnej trasy, utworzenie dedykowanego kontrolera, zbudowanie widoku Blade z danymi wejściowymi wyszukiwania i na koniec dodanie kodu jQuery, który uruchamia AJAX w miarę jego wpisywania.
Proces działa w następujący sposób: gdy użytkownik zaczyna wpisywać, jQuery wysyła zapytanie do serwera z ciągiem wyszukiwania. Laravel filtruje informacje w bazie danych, zwraca obiekt JSON z pasującymi wynikami, a jQuery aktualizuje kontener HTML na stronie, aby odzwierciedlić dopasowania – wszystko w ciągu milisekund.
Zaletą korzystania z jQuery jest to, że znacznie upraszcza składnię AJAX i jest bardzo czytelna, jeśli biblioteka jest już w projekcie. Wprowadza jednak dodatkową zależność, która może być zbędna, jeśli potrafisz pracować z nowoczesnym JavaScriptem i natywną funkcją pobierania.
Filtrowanie i wyszukiwanie w czasie rzeczywistym w interfejsie użytkownika za pomocą Alpine.js
Gdy dane do wyświetlenia są stosunkowo niewielkie (na przykład mniej niż 50 pozycji ), nie zawsze warto konfigurować zaplecza z rozbudowanymi funkcjami wyszukiwania. W takich przypadkach bardzo wygodną opcją jest filtrowanie bezpośrednio w przeglądarce za pomocą Alpine.js , bez wysyłania żądań do serwera podczas wpisywania tekstu przez użytkownika.
Pomysł polega na wstępnym obliczeniu ciągu wyszukiwania dla każdego elementu (np. nazwy, opisu i kategorii małymi literami), zapisaniu go w atrybucie data-search-text i umożliwieniu programowi Alpine.js obsługi wyświetlania lub ukrywania elementów na podstawie tekstu wprowadzonego w polu wyszukiwania.
Komponent Alpine.js może mieć strukturę podobną do tej: filterItems
{
search: '',
hasResults: true,
selectedValue: '',
init() {
this.$watch('search', () => this.filterItems());
this.$nextTick(() => this.$refs.searchInput?.focus());
},
filterItems() {
const searchLower = this.search.toLowerCase().trim();
const cards = this.$el.querySelectorAll('.item-card');
let visibleCount = 0;
cards.forEach(card => {
const text = card.dataset.searchText || '';
const isVisible = searchLower === '' || text.includes(searchLower);
card.style.display = isVisible ? '' : 'none';
if (isVisible) visibleCount++;
});
this.hasResults = visibleCount > 0;
},
}
W tym widoku każda karta lub wiersz danych miałby atrybut data-search-text z już przygotowanym tekstem pisanym małymi literami , dzięki czemu filtr jest zredukowany do include() w JavaScript, co jest bardzo szybkie w przypadku krótkich list:
<input type="search" x-model="search" x-ref="searchInput" placeholder="Buscar..." />
<div>
<div class="item-card" data-search-text="formulario contacto simple">
<h3>Formulario de contacto</h3>
<p>Formulario de contacto simple</p>
</div>
</div>
Dodatkowo możesz wyświetlić pusty blok statusu tylko wtedy, gdy nie ma żadnych wyników dla bieżącego terminu wyszukiwania , zachęcając użytkownika do modyfikacji tekstu lub wyczyszczenia pola za pomocą przycisku, który po prostu resetuje wyszukiwanie do pustego ciągu.
To podejście ma wyraźne zalety: brak wywołań serwera podczas wyszukiwania , interakcja jest praktycznie natychmiastowa, a logika pozostaje wysoce lokalna i łatwa do debugowania. Idealnie nadaje się do szybkich selektorów, okien modalnych wyboru elementów lub małych katalogów osadzonych na stronie Laravel.
Laravel Scout: wyszukiwanie pełnotekstowe z wykorzystaniem specjalistycznych silników
Gdy sprawy stają się poważne i potrzebujesz szybkiego, trafnego i skalowalnego wyszukiwania pełnotekstowego , naturalnym rozwiązaniem w Laravel jest Laravel Scout. Scout to warstwa integracyjna, która pozwala łatwo połączyć modele Eloquent z wyszukiwarkami takimi jak Algolia, Meilisearch, bazą danych, kolekcjami w pamięci, a nawet Elasticsearch za pośrednictwem zewnętrznych kontrolerów.
Aby rozpocząć korzystanie ze Scout, zazwyczaj należy utworzyć nowy projekt Laravel lub wykorzystać istniejący , uruchomić go za pomocą Dockera (na przykład Laravel Sail), a następnie zainstalować bibliotekę za pomocą Composera. Po wykonaniu tej czynności należy opublikować plik konfiguracyjny scout.php i dostosować zmienne środowiskowe do sterownika, którego chcemy użyć.
Typowy obieg pracy polegałby na zainstalowaniu Scouta z Composerem, opublikowaniu jego konfiguracji i włączeniu kolejki indeksowania z parametrem `SCOUT_QUEUE=true` w pliku `.env`, aby operacje intensywnie wykorzystujące zasoby były przetwarzane w tle, co skraca czas reakcji aplikacji. Dodatkowo należy upewnić się, że `DB_HOST` wskazuje na używaną bazę danych, co jest szczególnie ważne w przypadku korzystania z kontenerów Docker.
Aby model mógł uczestniczyć w wyszukiwaniach Scout, musi być wyraźnie oznaczony jako wyszukiwalny poprzez dodanie cechy „Wyszukiwalny” . Na przykład, jeśli masz model „Pociąg”, który reprezentuje tabelę pociągów z polem „tytuł”, możesz go zdefiniować w następujący sposób:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;
class Train extends Model
{
use Searchable;
protected $fillable = ['title'];
public function searchableAs()
{
return 'trains_index';
}
}
Metoda `searchableAs` pozwala dostosować nazwę indeksu w wyszukiwarce , zamiast używać domyślnej nazwy pochodzącej z modelu. Następnie Scout zajmuje się synchronizacją operacji tworzenia, aktualizacji i usuwania z indeksem zdalnym lub lokalnym, w zależności od wybranego sterownika.
Laravel Scout z Algolią: błyskawiczne wyszukiwanie w SaaS
Algolia to usługa SaaS, której celem jest zapewnienie bardzo szybkich i trafnych wyszukiwań w dużych wolumenach danych . Oferuje panel internetowy do zarządzania indeksami, regułami trafności, synonimami i wieloma innymi funkcjami oraz płynną integrację z Laravel za pośrednictwem Scout i oficjalnego klienta PHP.
Aby używać Algolii ze Scoutem, musisz zainstalować klienta PHP za pomocą Composera, zarejestrować swoje dane uwierzytelniające w pliku .env (identyfikator aplikacji i klucz API administratora) oraz skonfigurować parametr SCOUT_DRIVER=algolia , aby Scout mógł korzystać z tego silnika. Identyfikator aplikacji i klucz administratora można uzyskać z pulpitu Algolii.
Po skonfigurowaniu środowiska można używać metod takich jak Train::search('text')->paginate(6) bezpośrednio w kontrolerach, aby wykonywać wyszukiwania w polach indeksowanych i otrzymywać wyniki w podzielonym na strony formacie Eloquent, gotowe do przekazania do widoku Blade.
Na przykład możesz mieć kontroler indeksu , który wyświetla wszystkie pociągi lub wykonuje wyszukiwanie, jeśli przekazany zostanie parametr titlesearch, a także metodę create, która wstawia nowe pociągi do indeksu:
public function index(Request $request)
{
if ($request->has('titlesearch')) {
$trains = Train::search($request->titlesearch)->paginate(6);
} else {
$trains = Train::paginate(6);
}
return view('Train-search', compact('trains'));
}
public function create(Request $request)
{
$this->validate($request, ['title' => 'required']);
Train::create($request->all());
return back();
}
W odpowiednim widoku możesz połączyć formularz dodawania nowych pociągów z innym formularzem GET z polem wyszukiwania tytułu, który uruchamia wyszukiwanie po przesłaniu. Następnie wystarczy iterować po kolekcji pociągów i wyświetlać ich pola w tabeli, korzystając z linków paginacji generowanych przez Laravel.
Zwiad z Meilisearch, bazą danych i zbiorami
Jeśli wolisz unikać usług zewnętrznych, Meilisearch to wyszukiwarka open source , którą możesz wdrożyć lokalnie lub w swojej infrastrukturze. Scout integruje się z Meilisearch w bardzo podobny sposób jak Algolia, po prostu zmieniając sterownik i dodając zmienne MEILISEARCH_HOST i MEILISEARCH_KEY do pliku .env.
Aby z niego skorzystać, zainstaluj klienta PHP Meilisearch, ustaw SCOUT_DRIVER=meilisearch i wskaż MEILISEARCH_HOST na adres URL instancji (na przykład http://127.0.0.1:7700). Jeśli masz już rekordy, możesz je zindeksować poleceniem php artisan scout:import "App\Models\Train", aby wyszukiwarka miała je dostępne.
W przypadku mniejszych lub średniej wielkości aplikacji możesz również wybrać sterownik bazy danych Scout , który wykorzystuje indeksy pełnotekstowe i instrukcje LIKE w bazie danych MySQL lub PostgreSQL. W takim przypadku nie potrzebujesz usługi zewnętrznej; wystarczy ustawić parametr SCOUT_DRIVER=database, aby Scout używał samej bazy danych jako swojej wyszukiwarki.
Kolejną interesującą opcją jest sterownik kolekcji, który współpracuje z kolekcjami Eloquent przechowywanymi w pamięci . Ten silnik filtruje wyniki za pomocą `where` i metod filtrowania kolekcji i jest kompatybilny z każdą bazą danych obsługiwaną przez Laravel. Można go aktywować poleceniem `SCOUT_DRIVER=collection` lub, jeśli zależy Ci na bardziej szczegółowych parametrach, poprzez dostosowanie pliku konfiguracyjnego Scout.
Integracja z Elasticsearch za pomocą Eksploratora
Jeśli Twoje potrzeby wyszukiwania obejmują pracę z ogromnymi wolumenami danych i analitykę w czasie rzeczywistym , Elasticsearch to klasyka. W ekosystemie Laravel nowoczesnym sposobem na integrację z platformą Scout jest użycie kontrolera Explorer, który działa jak pomost między modelami a klastrem Elasticsearch.
Zazwyczaj wymaga to pracy z Dockerem i rozbudowanym plikiem docker-compose, który uruchamia, oprócz standardowych usług (Laravel, MySQL, Redis, Meilisearch itp.), kontenery Elasticsearch i Kibana . Następnie instalujesz pakiet jeroen-g/explorer za pośrednictwem Composera i publikujesz jego plik konfiguracyjny, aby określić, które modele powinny być indeksowane.
W pliku config/explorer.php możesz zarejestrować swoje modele w kluczu indexes, na przykład dodając App\Models\Train::class . Dodatkowo w pliku .env zmieniasz sterownik ze Scout na Elasticsearch z parametrem SCOUT_DRIVER=elastic, aby wszystko wskazywało na Elasticsearch.
W modelu Train należy zaimplementować interfejs Explored i zastąpić metodę mappableAs , która definiuje mapę pól do wysłania do indeksu. Minimalny przykład:
use JeroenG\Explorer\Application\Explored;
use Laravel\Scout\Searchable;
class Train extends Model implements Explored
{
use Searchable;
protected $fillable = ['title'];
public function mappableAs(): array
{
return [
'id' => $this->id,
'title' => $this->title,
];
}
}
Stąd możesz uruchamiać wyszukiwania w Elasticsearch za pomocą tego samego interfejsu Scout , korzystając z bardzo krótkich czasów reakcji i pełnej mocy zapytań tego silnika, ale bez opuszczania ekosystemu Laravel.
Dzięki wszystkim tym podejściom — od podstawowego autouzupełniania z wykorzystaniem Fetch lub jQuery, przez filtrowanie front-endu z wykorzystaniem Alpine.js, po pełnotekstowe wyszukiwanie z wykorzystaniem Laravel Scout i różnych sterowników — Laravel oferuje Ci szeroki wachlarz opcji wdrażania wyszukiwań w czasie rzeczywistym dostosowanych do rozmiaru Twojego projektu, potrzebnej wydajności i infrastruktury, którą jesteś w stanie utrzymać.