Bei unseren großen Projekten haben wir hauptsächlich Symfony- und Laravel Komponenten im Einsatz, erweitern den Kern um die spezifischen Anforderungen abzudecken und bauen dann ein Modul-Management darüber, um Erweiterbarkeit zu gewährleisten.
Beim Aufsetzen des Kerns bin ich immer wieder auf den Use-Case gestoßen, dass Daten aus unterschiedlichsten Tabellen irgendwo im System zentral benötigt werden. Das einfachste Beispiel dafür ist eine zentrale Suche, welche verschiedenste Inhaltstypen durchsucht. In diesem Beitrag werde ich euch erklären, wie einfach es ist, so eine Suche in Laravel zu integrieren.
Als Entwickler sucht man gewöhnlich immer nach fertigen Packages, bevor man selbst einen Finger rührt. Das habe ich in diesem Fall natürlich auch gemacht, und bin auf “Laravel Scout” — die hauseigene Suche von Laravel — gestoßen. Für unser Projekt ist das Package aber nicht gut geeignet, da unsere Datenstruktur & unser Ranking zu komplex ist. Keine Sorge: in diesem Beitrag konzentriere ich mich auf die Basics, um den Rahmen nicht zu sprengen 😉
Nach kurzem Grübeln war schnell mal klar:
Um die Suche performant zu halten, muss eine neue Datenbanktabelle angelegt werden, welche bei Suchen abgefragt wird.
Sollte es diese nämlich nicht geben, müssten alle Tabellen, welche durchsuchbar sind, einzeln abgefragt werden!! Das wäre für die Performance natürlich ein No-Go!
Im einfachsten Fall reicht eine Tabelle mit folgenden Spalten:
- Suchtext: nach dem Text wird bei Abfragen gesucht
- Inhaltstyp-Klasse: damit beim Ergebnis der richtige Inhaltstyp geladen werden kann
- Inhalts-ID: die ID des Inhaltselements
Diese Tabelle soll beim Erstellen, Updaten und Löschen von durchsuchbaren Inhalten automatisch geupdatet werden.
Laravel erweitern & Models vorbereiten
Für diesen Schritt wird ein “Manager” benötigt, welcher alle durchsuchbaren Inhalte überwacht, und bei bestimmten Events die Suchtabelle updatet. Ich werde den Manager in den nächsten Zeilen einfach “Search-Engine” oder “Suchmaschine” nennen.
Laravel bietet die einfache Möglichkeit, das System mit sogenannten Service-Providern zu erweitern. Ein solcher muss erstellt werden, um darin die Search-Engine zu instanzieren. Dann braucht die Suchmaschine noch die Information, welche Models durchsuchbar sind. Wie sie zu dieser Information kommt ist eigentlich egal. In kleinen Projekten löse ich das über ein config-Array, welches alle Klassen enthält. Bei großen Projekten überlasse ich diesen Task dem Modul-Management, um auch Inhalte aus Modulen für die Suche bereitzustellen.
Um sicherzustellen, dass die Inhalte korrekte Daten für die Suchtabelle zur Verfügung stellen, und bei einem Such-Request die richtigen Daten zurückgeben, verpflichte ich diese, ein Searchable Interface implementieren.
Ein einfaches Beispiel dafür wäre:
interface Searchable { public function searchFields(): array; public function searchResult(); array; }
Damit ist sichergestellt, dass alle durchsuchbaren Models angeben, welche Felder bei der Suche berücksichtigt werden. Außerdem muss das Model auch definieren, welche Daten als Suchergebnis zurückgegeben werden.
Somit sind die Models für die Suche vorbereitet.
Suchtabelle aktuell halten
Hier kommt Laravel’s Event-System zum Einsatz. Ein mächtiges Instrument, welches einem die Arbeit um ein Vielfaches erleichtert! Eloquent (Laravels ORM — Object-relational mapping) bietet bereits viele vordefinierte Events für Models. Wenn man auf mehrere Events eines Models reagieren will, kann man sogenannte “Event-Observer” registrieren. Wenn ein Model-Event auftritt, wird dann die gleichnamige Methode des Observers aufgerufen.
Das bedeutet, wenn auf das “created” Event reagiert werden soll, muss im Observer stets eine Methode namens “created” implementiert werden. Alle Event-Methoden bekommen das jeweilige Model als Parameter übergeben. Da alle Models das Searchable Interface implementieren, kann als Argumenttyp-Deklaration “Searchable” angegeben werden.
Um die Inhalte der Suchtabelle aktuell zu halten, muss für alle Searchables auf die Events “saved” und “deleted” reagiert werden. Der Observer muss sich dann nur darum kümmern, dass beim “saved”-Event ein Eintrag erstellt beziehungsweise upgedatet wird, und beim “deleted” die jeweiligen Einträge aus der Suchtabelle gelöscht werden.
Um das zu arrangieren, muss die Search-Engine über alle registrierten Searchable-Klassen iterieren und jeweils die statische Methode “observe” ausführen, um für das Model einen Event-Observer zu registrieren:
foreach ($searchableClasses as $searchableClass) { // hier prüfen, ob $searchableClass das Interface implementiert! $searchableClass::observe(SearchObserver::class); }
Ergebnisse Finden
Um nun zu Suchergebnissen zu kommen, muss jetzt einfach nur die Suchtabelle nach einem bestimmten Text abgefragt werden:
SELECT model_class, model_id FROM search_table WHERE search_text LIKE “%danach wird gesucht%”;
Diese Abfrage gibt alle Ergebnisse von allen durchsuchbaren Inhaltstypen zurück. Dann muss nur noch die model_id von der model_class laden.
$content = $modelClass::find($model_id);
Der Inhalt sollte dann existieren, da beim Löschen ja auch der Inhalt aus der Suchtabelle gelöscht wird; aber um sicherzugehen, kann geprüft werden, ob $content eine Instanz von $modelClass ist.
Andere Use-Cases
Der Einsatz von Event-Observer in Kombination mit einem “DataManager” erweist sich auch in zahlreichen anderen Anwendungsgebieten als äußerst hilfreich. Wir haben beispielsweise ein Reporting-System, welches diverse Events speichert und Statistiken auswertet, auf Basis dieses Konzepts aufgebaut. In diesem Fall waren jedoch einige Custom-Events im Einsatz (nicht nur die default Eloquent Events). Ein weiterer Anwendungsfall, welcher mir spontan dazu einfällt, wäre ein Notification-Center.
Du willst mit jemanden über das Thema plaudern?
Einen kostenlosen Termin mit CEO Susanne vereinbaren!AI-Driven UX - Möglichkeiten, Design-Prinzipien und Pflichten für UX-Designer - 2024 Update
UPDATE 2024: Ausgegraben aus 2019 dieses schmucke Fundstück über AI und UX. Irgendwie drehen sich die Trend-Themen doch alle Jahre im Kreis und man könnte glauben man findet sich diesbezüglich als Bill Murray in "Täglich grüßt das Murmeltier [...]
Jetzt lesenFolge #62 mit Susanne Liechtenecker
In Folge 62 besinnt sich Susanne auf die Anfänge dieses Podcasts und begrüßt keinen Gast, sondern erzählt über das Buch "Jäger, Hirten, Kritiker" von Richard David Precht und warum es sie inspiriert hat.
Jetzt anhören