Skip to content
Moritz Klaßen

Moritz Klassen

Laravel 5 Min. Lesezeit

Filament Select mit Vor- und Nachname richtig anzeigen

Ein Relationship-Select in Filament kann nur dann verständliche Treffer liefern, wenn Anzeige und Suche getrennt konfiguriert sind. Mit einem eigenen Datensatz-Label kombinierst du Vor- und Nachname, während Filament weiterhin auf echten Datenbankspalten sucht.

Im Kundenfeld stehen dreimal „Müller“, die Suche nach „Anna“ liefert keinen Treffer und beim Bearbeiten ist kaum zu erkennen, welcher Datensatz ausgewählt wurde. Ein zusammengesetztes Label allein behebt das nicht. Tragfähig wird das Filament Select erst, wenn du das sichtbare Label aus dem Datensatz erzeugst und die durchsuchbaren Datenbankspalten ausdrücklich festlegst.

Warum ein einzelnes titleAttribute nicht ausreicht

Ein typisches Relationship-Feld beginnt mit einer knappen Konfiguration:

Select::make('customer_id')
    ->relationship('customer', 'last_name');

Filament verwendet hier last_name als titleAttribute, also als Spalte für die sichtbare Bezeichnung. Das funktioniert technisch, ist für Personen aber oft zu wenig. Mehrere Kunden können denselben Nachnamen haben, und eine Suche nach dem Vornamen greift nicht automatisch auf eine weitere Spalte zu.

Naheliegend wäre ein Eloquent-Accessor wie full_name, der Vor- und Nachname verbindet. Ein Accessor ist jedoch keine echte Datenbankspalte. Wird er einfach als titleAttribute eingesetzt, kann Filament ihn nicht wie eine Spalte in der Relationship-Abfrage durchsuchen. Hübsche Ausgabe und funktionierende Suche sind zwei getrennte Aufgaben.

Der schnelle Fix für kombinierte Namen

Die Relationship bleibt für die Zuordnung zuständig. Über getOptionLabelFromRecordUsing() bestimmst du dagegen, wie ein gefundener Datensatz angezeigt wird. Mit searchable() legst du fest, welche echten Spalten Filament durchsuchen soll.

use App\Models\Customer;
use Filament\Forms\Components\Select;

Select::make('customer_id')
    ->label('Kunde')
    ->relationship(
        name: 'customer',
        titleAttribute: 'last_name',
    )
    ->searchable(['first_name', 'last_name'])
    ->getOptionLabelFromRecordUsing(
        fn (Customer $record): string => trim(
            $record->first_name . ' ' . $record->last_name
        )
    );

Die Aufgaben sind damit klar verteilt:

  • relationship() verbindet das Formularfeld mit der Eloquent-Beziehung customer.
  • titleAttribute bleibt eine vorhandene Datenbankspalte und liefert Filament eine verlässliche Grundlage für die Beziehung.
  • searchable() ermöglicht die Suche über Vor- und Nachname.
  • getOptionLabelFromRecordUsing() baut aus dem geladenen Customer-Datensatz die sichtbare Bezeichnung.

Das Label wird nicht nur für die Trefferliste gebraucht. Filament kann damit auch beim Öffnen eines bestehenden Datensatzes die bereits gespeicherte Auswahl verständlich darstellen. Du vermeidest dadurch den klassischen Schnellfix, der beim Anlegen funktioniert und im Bearbeitungsformular plötzlich nur noch eine ID oder ein unvollständiges Label zeigt.

Bei gleichen Namen braucht das Label ein Unterscheidungsmerkmal

„Anna Müller“ kann immer noch mehrfach vorkommen. Dann sollte das Label ein Merkmal enthalten, das die Person im Arbeitskontext eindeutig genug macht. Dafür eignen sich beispielsweise Kundennummer, Firma oder E-Mail-Adresse. Welche Angabe sinnvoll ist, hängt davon ab, was die bearbeitende Person tatsächlich kennt und suchen darf.

Mit der E-Mail-Adresse könnte das Feld so aussehen:

Select::make('customer_id')
    ->label('Kunde')
    ->relationship(
        name: 'customer',
        titleAttribute: 'last_name',
    )
    ->searchable(['first_name', 'last_name', 'email'])
    ->getOptionLabelFromRecordUsing(
        function (Customer $record): string {
            $name = trim(
                $record->first_name . ' ' . $record->last_name
            );

            return $record->email
                ? $name . ' · ' . $record->email
                : $name;
        }
    );

Die zusätzliche Spalte gehört auch in searchable(), wenn Nutzer nach ihr suchen sollen. Eine Information nur anzuzeigen, macht sie noch nicht durchsuchbar.

Prüfe außerdem, ob das zusätzliche Merkmal im Adminpanel sichtbar sein darf. Eine interne Kundennummer ist häufig unkritischer als eine private E-Mail-Adresse. Der praktischste Bezeichner ist nicht automatisch der datenschutzfreundlichste.

Warum options() mit allen Kunden meist der falsche Abkürzer ist

Ein verbreiteter Ausweg besteht darin, alle Kunden selbst zu laden und daraus Optionen zu bauen:

Select::make('customer_id')
    ->options(
        Customer::all()->mapWithKeys(
            fn (Customer $customer): array => [
                $customer->id => trim(
                    $customer->first_name . ' ' . $customer->last_name
                ),
            ]
        )
    );

Für wenige Datensätze kann das sichtbar funktionieren. Der Code lädt allerdings sämtliche Kunden bereits beim Aufbau des Formulars. Außerdem verlässt du die bestehende Relationship-Konfiguration und musst Einschränkungen wie Mandantenzuordnung oder fachliche Sichtbarkeit selbst korrekt nachbauen.

Das Relationship-Select mit gezielter Suche ist deshalb der bessere schnelle Fix. Es nutzt die bestehende Beziehung und lädt Treffer anhand einer Suchanfrage, statt vorsorglich den gesamten Kundenbestand in das Formular zu kippen. Ein preload() solltest du bei einer größeren Auswahl ebenfalls nicht reflexartig ergänzen, weil dadurch wieder Optionen vor der eigentlichen Suche geladen werden.

Die vier Prüfungen nach der Änderung

Ein Select ist erst repariert, wenn es beim Anlegen, Suchen und Bearbeiten konsistent funktioniert. Prüfe die Änderung deshalb mit realistischen Datenkonstellationen:

  1. Suche nach dem Vornamen: Ein Fragment wie „Ann“ muss den passenden Datensatz liefern.
  2. Suche nach dem Nachnamen: Auch häufige Nachnamen müssen erwartbare Treffer anzeigen.
  3. Öffne einen bestehenden Datensatz: Die gespeicherte Auswahl muss sofort mit dem vollständigen Label erscheinen.
  4. Prüfe unvollständige und doppelte Namen: Fehlende Vornamen dürfen keine merkwürdigen Leerzeichen erzeugen, identische Namen brauchen ein erlaubtes Unterscheidungsmerkmal.

Falls bestimmte Kunden aufgrund von Mandanten-, Rollen- oder Statusregeln nicht auswählbar sein dürfen, gehört auch das in den Test. Ein gut lesbares Label darf keine Datensätze sichtbar machen, die durch die fachliche Abfrage ausgeschlossen sein sollten.

Wann eine eigene Suchabfrage nötig wird

Die gezeigte Lösung reicht, solange die Suchbegriffe in Spalten der verknüpften Tabelle liegen. Schwieriger wird es, wenn das Label Informationen aus weiteren Beziehungen enthält, etwa den Firmennamen aus einer separaten Organisationstabelle. Dann kann ein vollständig angepasstes Suchverhalten erforderlich sein.

Dasselbe gilt für spezielle Normalisierungen, externe Kennungen oder Suchregeln, die mehrere Tabellen verbinden. In solchen Fällen solltest du die Suchabfrage bewusst definieren, statt immer mehr Darstellungslogik in das Label zu schieben. Das Label beantwortet die Frage „Was soll der Nutzer sehen?“. Die Abfrage beantwortet „Welche Datensätze darf er mit diesem Begriff finden?“.

Fazit

Wenn ein Filament Select nur Nachnamen zeigt oder den Vornamen nicht findet, brauchst du weder eine künstliche Datenbankspalte noch eine vorab geladene Liste aller Kunden. Lass die Relationship auf einer echten Spalte arbeiten, definiere Vor- und Nachname als Suchfelder und erzeuge das sichtbare Label direkt aus dem gefundenen Datensatz. So bleibt der Fix klein, während Anzeige, Suche und bestehende Auswahl zusammenpassen.

Häufige Fragen

Kurz beantwortet, damit du schneller einschätzen kannst, was für dein Projekt wichtig ist.

Wie zeige ich in einem Filament Select Vor- und Nachname an?

Nutze bei einem Relationship-Select getOptio
LabelFromRecordUsing(), um das Label aus first_name und last_name zusammenzusetzen. Die beiden echten Datenbankspalten gibst du zusätzlich an searchable() weiter.

Kann ich einen Eloquent-Accessor als titleAttribute verwenden?

Für die reine Ausgabe kann ein Accessor praktisch sein, er ist aber keine echte Datenbankspalte. Für die Relationship-Suche solltest du deshalb vorhandene Spalten angeben und das zusammengesetzte Label separat erzeugen.

Warum sollte ich nicht alle Kunden mit options() laden?

Damit werden alle Datensätze bereits beim Aufbau des Formulars geladen. Außerdem musst du fachliche Einschränkungen und Sichtbarkeitsregeln außerhalb der bestehenden Relationship selbst berücksichtigen.

Wie unterscheide ich Kunden mit identischem Namen?

Ergänze das Label um ein erlaubtes Merkmal wie Kundennummer, Firma oder E-Mail-Adresse. Soll danach gesucht werden können, muss die entsprechende Spalte ebenfalls in searchable() stehen.

Kontakt

Lass uns über dein Projekt sprechen.

Ob neue Website, Laravel-Tool, Relaunch oder technischer Sparringstermin: Buch dir gern direkt einen Slot oder schreib mir eine Mail.

Erstgespräch buchen Oder direkt per Mail hello@moritzklassen.com
Moritz Klaßen
Moritz Klaßen Webentwickler & Ansprechpartner