Live-Suche mit Tom Select ohne eigenes JavaScript
Symfony UX Autocomplete verbindet Doctrine-Entities, eigene Suchlogik und externe APIs mit einem einzigen Formularfeld-Typ und liefert dabei serverseitiges Rendering, Stimulus-Steuerung und Tom Select als Frontend, ohne dass ein einziges eigenes JavaScript-Modul geschrieben werden muss.
Inhaltsverzeichnis
- 1. Warum Symfony UX Autocomplete existiert
- 2. Installation und Grundkonfiguration
- 3. Autocomplete direkt im EntityType aktivieren
- 4. Eigene AutocompleteField-Klasse für komplexe Suchen
- 5. Externe APIs statt Doctrine-Entities anbinden
- 6. Tom Select konfigurieren: Multiple, Preload, Rendering
- 7. Styling mit Tailwind ohne eigenes CSS-Framework
- 8. Performance: Debounce, Mindestlänge und Caching
- 9. Symfony UX Autocomplete im Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Warum Symfony UX Autocomplete existiert
Symfony UX Autocomplete löst ein Problem, das in fast jedem größeren Formular auftaucht: Eine Dropdown-Liste mit tausenden Einträgen lässt sich nicht mehr sinnvoll als normales Select-Feld rendern. Vor Symfony UX war die übliche Lösung, select2 oder Choices.js manuell einzubinden, eigene JavaScript-Initialisierung zu schreiben und die Serverkommunikation per Fetch selbst zu implementieren. Das führte in jedem Projekt zu einer eigenen, leicht abweichenden Lösung mit eigenen Bugs.
Symfony UX Autocomplete kapselt genau diese Aufgabe in ein wiederverwendbares Bundle. Der Formularfeld-Typ generiert automatisch das passende Stimulus-Attribut, das Bundle liefert einen fertigen Controller mit, der Tom Select initialisiert, und die Serverkommunikation läuft über einen generierten Controller-Endpunkt, der Doctrine-Repository-Queries kapselt. Das Ergebnis: Live-Suche in Formularen ohne eine einzige Zeile eigenes JavaScript, dafür mit klar definierten PHP-Erweiterungspunkten für alles, was über den Standardfall hinausgeht.
Der Unterschied zu einer reinen JavaScript-Bibliothek liegt in der Integration. Symfony UX Autocomplete versteht Doctrine-Entities, Validierungsregeln und CSRF-Schutz von Haus aus, weil es Teil des Symfony-Form-Systems ist und nicht nebenher als unabhängiges Frontend-Widget existiert.
2. Installation und Grundkonfiguration
Die Installation von Symfony UX Autocomplete erfolgt über Composer und bringt bei AssetMapper-Projekten die JavaScript-Abhängigkeiten automatisch über den Symfony Asset Mapper mit. Bei Encore-Projekten muss zusätzlich yarn beziehungsweise npm ausgeführt werden, um Tom Select in den Build aufzunehmen. Nach der Installation registriert das Bundle automatisch einen Stimulus-Controller und stellt den Formularfeld-Typ Symfony\UX\Autocomplete\Form\AutocompleteField sowie den Options-Parameter autocomplete für EntityType bereit.
Ein wichtiger Punkt bei der Grundkonfiguration: Symfony UX Autocomplete registriert für jede Entity, die Autocomplete nutzt, automatisch einen internen Controller unter /_ux_entity_autocomplete/{alias}. Dieser Endpunkt muss nicht manuell in der routes.yaml eingetragen werden, wird aber durch den Security-Layer wie jede andere Route behandelt. Wer Autocomplete auf sensiblen Entities einsetzt, muss also explizit prüfen, ob der generierte Endpunkt durch Firewalls und Access-Control-Regeln abgedeckt ist.
# Install Symfony UX Autocomplete
composer require symfony/ux-autocomplete
# AssetMapper projects: assets are wired automatically
bin/console importmap:require symfony/ux-autocomplete
# Encore projects only: build the JS integration
yarn add @symfony/ux-autocomplete --dev
yarn encore dev
3. Autocomplete direkt im EntityType aktivieren
Der schnellste Einstieg in Symfony UX Autocomplete ist die Option autocomplete direkt am EntityType. Damit wird aus einem gewöhnlichen Dropdown mit tausenden Produkten oder Kunden ein Live-Suchfeld, ohne dass eine eigene Klasse geschrieben werden muss. Symfony generiert im Hintergrund den passenden Endpunkt, der über das Repository der Entity sucht und die Ergebnisse als JSON an Tom Select liefert.
Diese einfache Form von Symfony UX Autocomplete eignet sich für den Standardfall: Suche über ein oder zwei Felder, keine komplexe Sortierlogik, keine Rechteprüfung pro Ergebnis. Sobald die Suchlogik komplizierter wird, etwa wenn mehrere Felder kombiniert oder Berechtigungen pro Zeile geprüft werden müssen, wechselt man zur eigenen AutocompleteField-Klasse aus Abschnitt vier.
// src/Form/OrderType.php
namespace App\Form;
use App\Entity\Customer;
use App\Entity\Order;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
final class OrderType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('customer', EntityType::class, [
'class' => Customer::class,
'choice_label' => 'fullName',
// Enable Symfony UX Autocomplete for this field
'autocomplete' => true,
'placeholder' => 'Kunde suchen...',
]);
}
public function getBlockPrefix(): string
{
return '';
}
}
4. Eigene AutocompleteField-Klasse für komplexe Suchen
Sobald die Suche über Symfony UX Autocomplete mehr können soll als ein einfaches LIKE-Query auf ein Feld, wird eine eigene Klasse angelegt, die AbstractType erweitert und das Interface EntityAutocompleteFieldInterface implementiert. Diese Klasse definiert eine createFilteredQueryBuilder-Methode, in der beliebig komplexe Doctrine-QueryBuilder-Logik stehen kann, inklusive Joins über verknüpfte Entities, Sortierung nach Relevanz oder Filterung nach dem aktuell eingeloggten Benutzer.
Der entscheidende Vorteil dieser Variante von Symfony UX Autocomplete: Die Suchlogik lebt an genau einer Stelle im Code und kann in mehreren Formularen wiederverwendet werden, ohne dass Optionen dupliziert werden. Die Klasse kann zusätzlich eine getLabel-Methode überschreiben, um das angezeigte Label pro Treffer individuell zusammenzusetzen, etwa Kundenname plus Kundennummer plus Stadt in einer Zeile.
// src/Form/CustomerAutocompleteField.php
namespace App\Form;
use App\Entity\Customer;
use App\Security\CurrentUserProvider;
use Doctrine\ORM\QueryBuilder;
use Symfony\UX\Autocomplete\Doctrine\EntityAutocompleteFieldInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
final class CustomerAutocompleteField extends EntityType implements EntityAutocompleteFieldInterface
{
public function __construct(
private readonly CurrentUserProvider $currentUserProvider,
) {
}
public function getLabel(object $entity): string
{
/** @var Customer $entity */
return sprintf('%s (%s, %s)', $entity->getFullName(), $entity->getCustomerNumber(), $entity->getCity());
}
public function createFilteredQueryBuilder(QueryBuilder $queryBuilder, string $query): void
{
$tenantId = $this->currentUserProvider->getCurrentTenantId();
$queryBuilder
->andWhere('entity.tenant = :tenantId')
->setParameter('tenantId', $tenantId)
->andWhere('entity.fullName LIKE :query OR entity.customerNumber LIKE :query')
->setParameter('query', '%' . $query . '%')
->orderBy('entity.fullName', 'ASC')
->setMaxResults(20);
}
public function getOptionsForAutocomplete(): array
{
return [
'class' => Customer::class,
'choice_label' => 'fullName',
];
}
}
5. Externe APIs statt Doctrine-Entities anbinden
Nicht jede Suche in Symfony UX Autocomplete muss gegen die eigene Datenbank laufen. Für Produktdaten aus einem externen PIM-System, Adressvorschläge aus einer Geodaten-API oder Artikelsuchen gegen Elasticsearch bietet das Bundle die Möglichkeit, einen eigenen Controller als Datenquelle zu registrieren, statt auf ein Doctrine-Repository zurückzugreifen. Der Controller muss lediglich das erwartete JSON-Format zurückgeben, das Tom Select für die Ergebnisliste erwartet.
Dieser Ansatz von Symfony UX Autocomplete entkoppelt die Frontend-Komponente vollständig von der Datenquelle. Das Stimulus-Attribut zeigt statt auf den internen Entity-Endpunkt direkt auf eine eigene Route, die intern einen HTTP-Client für die externe API nutzt, das Ergebnis normalisiert und als JSON zurückgibt. Für den Nutzer sieht die Interaktion identisch zur Entity-Variante aus, technisch läuft im Hintergrund aber eine komplett andere Anfrage.
// src/Controller/ProductSearchAutocompleteController.php
namespace App\Controller;
use App\Client\ExternalPimClient;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Attribute\Route;
final class ProductSearchAutocompleteController extends AbstractController
{
public function __construct(
private readonly ExternalPimClient $pimClient,
) {
}
#[Route('/autocomplete/products', name: 'app_autocomplete_products', methods: ['GET'])]
public function __invoke(Request $request): JsonResponse
{
$query = (string) $request->query->get('query', '');
if (mb_strlen($query) < 2) {
return new JsonResponse(['results' => []]);
}
$products = $this->pimClient->search($query, limit: 15);
// Tom Select expects a "results" array with id/text pairs
$results = array_map(
static fn (array $product): array => [
'id' => $product['sku'],
'text' => sprintf('%s (%s)', $product['name'], $product['sku']),
],
$products,
);
return new JsonResponse(['results' => $results]);
}
}
6. Tom Select konfigurieren: Multiple, Preload, Rendering
Unter der Haube nutzt Symfony UX Autocomplete Tom Select als Rendering-Bibliothek. Die meisten Tom-Select-Optionen lassen sich über data-Attribute am generierten Element oder über Optionen des Formularfeld-Typs steuern, ohne eine eigene Stimulus-Controller-Klasse schreiben zu müssen. Die Option multiple aktiviert Mehrfachauswahl mit Tags, die Option tom_select_options reicht ein assoziatives Array direkt an die Tom-Select-Instanz durch, etwa für eigene render-Callbacks.
Für Anwendungsfälle, in denen bereits beim Laden der Seite ein vorausgewählter Wert angezeigt werden soll, unterstützt Symfony UX Autocomplete das Preloading des aktuellen Wertes, indem das Label serverseitig mitgerendert wird. Das verhindert den sonst üblichen kurzen Moment, in dem nur die ID sichtbar ist, bis JavaScript den passenden Text nachlädt. Wer eigene Render-Templates für die Dropdown-Optionen benötigt, etwa mit Avatar-Bild und Zusatzinformationen, kann dafür render_option und render_item als JavaScript-Funktionen über die Optionen einreichen.
{# templates/order/new.html.twig #}
{{ form_start(form) }}
{{ form_row(form.customer, {
attr: {
'data-tom-select-multiple': false,
'data-tom-select-preload': 'focus'
}
}) }}
{{ form_row(form.products, {
attr: { 'data-tom-select-multiple': true }
}) }}
{{ form_end(form) }}
7. Styling mit Tailwind ohne eigenes CSS-Framework
Tom Select bringt ein eigenes Standard-Theme mit, das visuell nicht zu einem Hyvä-nahen Tailwind-Frontend passt. Statt das komplette Tom-Select-CSS zu importieren, überschreibt man in Symfony UX Autocomplete-Projekten gezielt die generierten CSS-Klassen von Tom Select mit eigenen Tailwind-Utility-Klassen. Tom Select erzeugt vorhersehbare Klassennamen wie ts-control, ts-dropdown und ts-wrapper, die sich über eine dünne CSS-Schicht an das eigene Design-System anpassen lassen.
Wichtig bei diesem Ansatz von Symfony UX Autocomplete ist, nicht das komplette Standard-Stylesheet von Tom Select zu laden, sondern nur die minimale Basisdatei ohne Theme, und darauf die eigenen Tailwind-Klassen zu setzen. Das reduziert die CSS-Bundle-Größe erheblich und verhindert Konflikte zwischen Tailwinds Utility-First-Ansatz und Tom Selects vorgefertigten Klassen.
/* assets/styles/autocomplete.css */
/* Minimal overrides on top of Tom Select's base (unstyled) CSS */
.ts-wrapper.single .ts-control {
@apply rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm shadow-sm;
}
.ts-wrapper.focus .ts-control {
@apply border-gray-700 ring-2 ring-gray-200;
}
.ts-dropdown {
@apply mt-1 rounded-lg border border-gray-200 bg-white shadow-lg;
}
.ts-dropdown .option.active {
@apply bg-gray-100 text-gray-900;
}
8. Performance: Debounce, Mindestlänge und Caching
Ohne Feinabstimmung sendet Symfony UX Autocomplete bei jedem Tastendruck eine Anfrage an den Server, was bei schnellem Tippen unnötig viele parallele Requests erzeugt. Das Bundle debounced Eingaben standardmäßig, die Wartezeit lässt sich über die Option debounce in Millisekunden anpassen. Zusätzlich verhindert eine konfigurierbare Mindestlänge der Sucheingabe, dass bei ein oder zwei Zeichen bereits tausende Treffer aus der Datenbank geladen werden.
Auf Serverseite lohnt es sich, bei häufig wiederholten Suchanfragen einen kurzen Cache einzuziehen, etwa über den Symfony Cache-Layer mit einer TTL von wenigen Sekunden. Das reduziert die Datenbanklast bei populären Suchbegriffen, ohne dass Ergebnisse merklich veraltet wirken. Wer Symfony UX Autocomplete gegen eine externe API einsetzt, sollte den Cache zusätzlich nutzen, um Rate-Limits der externen API nicht zu überschreiten.
9. Symfony UX Autocomplete im Vergleich
Bevor man sich für Symfony UX Autocomplete entscheidet, lohnt sich ein Blick auf die Alternativen, die in bestehenden Projekten häufig anzutreffen sind. Jeder Ansatz hat unterschiedliche Auswirkungen auf Wartungsaufwand, Bundle-Größe und Integrationstiefe mit dem Symfony-Formsystem.
| Ansatz | Integration | Eigener JS-Code | Bundle-Größe |
|---|---|---|---|
| select2 manuell eingebunden | Lose, eigenes Fetch nötig | Viel | Mittel |
| Choices.js manuell | Lose, eigenes Fetch nötig | Viel | Mittel |
| Symfony UX Autocomplete | Tief, Form-System-nativ | Keiner | Gering |
| Eigener Stimulus-Controller | Mittel, selbst gebaut | Viel | Variabel |
| Native HTML datalist | Sehr lose | Keiner | Keine |
Die native datalist-Lösung ohne jede Bibliothek wirkt verlockend, weil sie null Bytes an zusätzlichem JavaScript kostet, scheitert in der Praxis aber an fehlender Server-Suche, fehlendem Debouncing und inkonsistentem Browser-Rendering. Symfony UX Autocomplete liegt genau zwischen den beiden Extremen: minimaler eigener Code, aber volle Kontrolle über Serverlogik und Darstellung, wo sie gebraucht wird.
Mironsoft
Symfony-Entwicklung mit modernem UX-Frontend
Formulare, die sich anfühlen wie eine SPA, ganz ohne eigenes JavaScript?
Wir integrieren Symfony UX Autocomplete in bestehende Formulare, bauen eigene AutocompleteField-Klassen für komplexe Suchen und binden externe APIs sauber an euer Symfony-Projekt an.
UX-Audit
Bestehende Formulare auf Autocomplete-Potenzial prüfen
Implementierung
Eigene AutocompleteField-Klassen und externe API-Anbindung
Tailwind-Styling
Tom Select nahtlos in euer Design-System integrieren
10. Zusammenfassung
Symfony UX Autocomplete löst das klassische Problem großer Dropdown-Listen, indem es Doctrine-Repositories, eigene Suchlogik und externe APIs unter einer einheitlichen Stimulus-Integration mit Tom Select vereint. Für den einfachen Fall genügt die Option autocomplete am EntityType, für komplexere Anforderungen übernimmt eine eigene AutocompleteField-Klasse mit vollständiger QueryBuilder-Kontrolle die Suche. Externe Datenquellen lassen sich über einen eigenen Controller anbinden, ohne die Frontend-Integration zu verändern.
Der größte Vorteil gegenüber manuell eingebundenen JavaScript-Bibliotheken liegt in der Wartbarkeit: Symfony UX Autocomplete bleibt Teil des Symfony-Ökosystems, profitiert von Sicherheitsupdates über Composer und benötigt keinen separaten Build-Prozess für die Kernfunktionalität. Wer zusätzlich Debounce, Mindestlänge und serverseitigen Cache konfiguriert, bekommt eine Live-Suche, die sich unter Last genauso zuverlässig verhält wie unter normalen Bedingungen.
Symfony UX Autocomplete — Das Wichtigste auf einen Blick
Schnellstart
Option autocomplete: true am EntityType reicht für den Standardfall, ganz ohne eigenen JavaScript-Code.
Komplexe Suchen
Eigene AutocompleteField-Klasse mit createFilteredQueryBuilder für Joins, Rechte und individuelle Labels.
Externe Quellen
Eigener Controller als Datenquelle für PIM-Systeme, Elasticsearch oder Geodaten-APIs.
Performance
Debounce, Mindestlänge und Server-Cache verhindern unnötige Datenbank- oder API-Last.