Laravel & PHP5 min lezen

Bruikbare full-text search met Laravel en MySQL

Een praktische Laravel-gids over FULLTEXT-indexen, rechten, exacte identifiers, relevantie, beperkingen, tests en veilige uitrol.

JDoor Jeffrey Klaassen van Oorschot

Search begint vaak als een LIKE-query en blijft zo staan totdat de tabel groot genoeg is om pijn te doen. MySQL heeft al full-text-indexen en Laravel stelt ze beschikbaar via de query builder. Voor veel productcatalogi, kennisbanken en beheertools is dat genoeg. Je moet wel weten waar full-text search goed in is, waar het verrassingen oplevert en hoe autorisatie onderdeel van dezelfde query blijft.

Definieer welke zoekopdrachten gebruikers echt uitvoeren

Ik scheid exacte lookup van zoeken in tekst. Ordernummers, SKU’s, e-mailadressen en bekende ID’s vragen om equality- of prefix-indexen. Titels, omschrijvingen, notities en artikelen passen beter bij full-text search. Eén full-text-query voor beide soorten zoekwerk maakt exacte identifiers meestal onbetrouwbaar.

Voor een schemawijziging verzamel ik echte queries met de resultaten die gebruikers bovenaan verwachten. Ik neem spelfouten, korte woorden, productcodes, frases, veelgebruikte termen en een query zonder resultaat mee. Zo ontstaat een herhaalbare relevantietest in plaats van een paar willekeurige zoekpogingen tijdens review.

Indexeer de velden die samen één zoekbaar document vormen

Een FULLTEXT-index werkt op CHAR-, VARCHAR- en TEXT-kolommen. Velden die samen worden doorzocht, zoals titel en body, groepeer ik in één index. Gestructureerde filters zoals workspace_id, status, categorie en published_at blijven normale kolommen met passende indexen. De kolommen in de query moeten overeenkomen met een bruikbare full-text-index.

Laravel-migraties kunnen die index direct maken. Op een drukke bestaande tabel inspecteer ik de SQL en test ik de operatie op dezelfde MySQL-versie als productie. De eerste InnoDB FULLTEXT-index kan een serieuze tabelbewerking zijn; een korte migration file garandeert geen korte of lockvrije productieactie.

Eén index voor velden die samen worden doorzocht
Schema::table('articles', function (Blueprint $table) {
    $table->fullText(
        ['title', 'summary', 'body'],
        'articles_content_fulltext'
    );
});

Pas rechten toe voordat je resultaten begrenst

Search is gewoon een read-endpoint en heeft dezelfde tenant- en zichtbaarheidsregels nodig als een normale listing. Ik voeg die voorwaarden toe in SQL vóór de limit. Eerst een globale top twintig ophalen en verboden rijen daarna in PHP verwijderen is onveilig en onvolledig, omdat ontoegankelijke resultaten geldige resultaten kunnen verdringen.

Bij het openen van een resultaat controleer ik de resource opnieuw via de normale policy. Searchconstraints maken de candidateset correct; de policy blijft de laatste grens. Zo kan een latere refactor van search niet ongemerkt de enige autorisatiecontrole worden.

Full-text search binnen de toegestane scope
$articles = Article::query()
    ->where('workspace_id', $user->workspace_id)
    ->where('is_published', true)
    ->whereIn('audience', $user->searchAudiences())
    ->whereFullText(
        ['title', 'summary', 'body'],
        $request->string('q')->trim()->value(),
    )
    ->limit(20)
    ->get();

Weet welke tokens de parser overslaat

Full-text search werkt met tokens. Stopwoorden kunnen worden genegeerd, korte woorden kunnen onder de ingestelde minimumlengte vallen en een deelwoord is niet hetzelfde als prefix search. Dat is relevant voor afkortingen, korte productnamen, Nederlandse samenstellingen en identifiers. Ik controleer dit op dezelfde engine, versie, parser en configuratie als productie.

Ik val niet stilletjes terug op LIKE ‘%query%’ over een grote tabel wanneer full-text niets vindt. Door de wildcard aan het begin kan een normale B-tree-index meestal niet helpen. Korte codes krijgen een aparte genormaliseerde kolom met equality of prefix search. Typotolerantie behandel ik als aparte productfunctie, want native full-text search levert niet vanzelf een goede fuzzy-search-ervaring.

  • Test woorden rond de minimale tokenlengte
  • Controleer de actieve stopwoorden
  • Bewaar exacte identifiers in eigen geïndexeerde kolommen
  • Test de talen die gebruikers echt schrijven
  • Verberg nul resultaten niet met een dure tabelscan

Combineer begrijpelijke signalen in plaats van een magische score

Voor een kleine applicatie kan native relevantie met sterke filters al voldoende zijn. Wanneer een exacte titelmatch bovenaan hoort, voeg ik die regel expliciet toe. Een genormaliseerde exacte match, titelprefix, publicatiestatus en actualiteit zijn signalen die een andere developer kan uitleggen.

Natural-language- en boolean-mode gedragen zich anders. Ik kies een MySQL-zoekmodus vanuit de productbehoefte en houd de query geparameteriseerd via Laravel. Gebruikersinvoer wordt nooit aan een ruwe MATCH- of AGAINST-expressie vastgeplakt.

Meet kwaliteit en databasekosten samen

Naast bekende zoekresultaten bekijk ik gegenereerde SQL, execution plans, latencyverdeling, aantallen resultaten en queryvolume. Search-as-you-type veroorzaakt snel veel requests, dus de UI debouncet invoer, gebruikt een redelijke minimumlengte en annuleert verouderde requests. Server-side rate limits beschermen tegen misbruik en ongelukjes.

Mijn securitytest maakt unieke frases in twee workspaces, plus drafts en ingetrokken inhoud. Geen verboden ID mag PHP bereiken. Tijdens uitrol vergelijk ik oude en nieuwe resultaten en volg ik slow-query-logs en databasebelasting. Search is niet geslaagd wanneer relevantie beter wordt maar gewone writes of pagina’s instabiel worden.

Praktische checklist

  • Scheid exacte identifiers van zoeken in tekst
  • Maak een passende full-text-index
  • Pas tenant- en zichtbaarheidsfilters toe in SQL
  • Test stopwoorden en korte tokens op de productie-engine
  • Houd ruwe gebruikersinvoer uit SQL-expressies
  • Meet relevantie, queryplans en databasebelasting
  • Test verboden records met unieke zoekfrases

Verder lezen

Laravel & PHP

Betrouwbare webhookverwerking in Laravel

Een praktische Laravel-webhookarchitectuur voor signatures, duplicaten, snelle acknowledgement, queues, ordering, herstel en observability.

5 min lezenLees artikel