Reactive PHP-Komponenten ohne JavaScript-Framework
Suchfelder, die während der Eingabe Ergebnisse laden. Formulare, die Felder in Echtzeit validieren. Warenkorb-Komponenten, die auf Klicks reagieren ohne Seitenreload. Das alles mit PHP und Twig — Symfony UX Live Component macht PHP-Komponenten reaktiv, ohne React oder Vue zu brauchen.
Inhaltsverzeichnis
- 1. Das Konzept: reaktive PHP-Komponenten
- 2. Installation und Grundkonfiguration
- 3. Erste Live Component bauen
- 4. LiveProp: automatisch synchronisierte Properties
- 5. LiveAction: Serveraktionen ohne eigene Route
- 6. Live-Suche mit Doctrine und Debouncing
- 7. Echtzeit-Validierung mit Symfony Constraints
- 8. Performance: Debounce, Loading-States und Optimistic UI
- 9. Live Component vs. Turbo Frames vs. Alpine.js
- 10. Zusammenfassung
- 11. FAQ
1. Das Konzept: reaktive PHP-Komponenten
Die Idee hinter Symfony UX Live Component ist, dass PHP-Klassen reaktiv werden können: Wenn sich eine Property der Komponente ändert — weil ein Nutzer in ein Eingabefeld tippt oder einen Schalter betätigt — sendet der Browser automatisch einen Server-Request, die Komponente wird mit dem neuen State neu gerendert, und der DOM wird mit dem Ergebnis aktualisiert. Das klingt wie React oder Vue, funktioniert aber vollständig serverseitig: Die Komponente ist eine PHP-Klasse, das Template ist Twig, und das Rendering passiert auf dem Server.
Das Paket symfony/ux-live-component setzt auf dem UX TwigComponent-Paket auf und ergänzt es um das Reaktivitäts-Layer. Der Stimulus-Controller im Browser überwacht Eingabefelder, die mit data-model annotiert sind, und sendet bei Änderungen einen AJAX-Request an einen internen Endpunkt. Der Server rendert die Komponente neu und gibt HTML zurück, das über Morphdom-DOM-Diffing in die Seite gemerged wird. Morphdom versucht, den DOM minimal zu verändern — wie React's Virtual DOM, aber serverseitig gerendert. Das erhält Browser-State wie Scroll-Position, Cursor-Position in Eingabefeldern und CSS-Transitions während des Updates.
2. Installation und Grundkonfiguration
Die Installation von symfony/ux-live-component setzt symfony/ux-twig-component voraus, das es als Dependency mitbringt. Das Flex-Recipe registriert das Bundle, trägt die Stimulus-Controller-JavaScript-Datei ins importmap ein und legt einen internen Routing-Eintrag an, den die Live-Component-Requests verwenden. Mit AssetMapper ist danach kein weiterer Schritt nötig — der Stimulus-Controller ist sofort aktiv.
Für die Asset-Konfiguration stellt man sicher, dass das Live-Component-Paket im AssetMapper registriert ist: bin/console debug:asset sollte @symfony/ux-live-component zeigen. Das interne Routing verwendet den Pfad /_components — dieser muss in der Firewall-Konfiguration der Security-Komponente korrekt konfiguriert sein. Wenn die Applikation Sessions für Authentifizierung nutzt, funktionieren Live-Component-Requests automatisch mit der bestehenden Session. Für stateless API-Anwendungen mit JWT-Authentifizierung muss die Security-Konfiguration angepasst werden, damit der /_components-Pfad die richtige Authentifizierungsmethode verwendet.
<?php
declare(strict_types=1);
namespace App\Twig\Components;
use App\Repository\ProductRepository;
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveAction;
use Symfony\UX\LiveComponent\Attribute\LiveProp;
use Symfony\UX\LiveComponent\DefaultActionTrait;
/**
* Live search component that reacts to user input in real time.
* Re-renders automatically whenever searchQuery changes.
*/
#[AsLiveComponent]
final class ProductSearch
{
use DefaultActionTrait;
/** @var string The search query — automatically synced from the browser input field */
#[LiveProp(writable: true)]
public string $searchQuery = '';
/** @var int Results per page — writable to allow user to change */
#[LiveProp(writable: true)]
public int $perPage = 10;
public function __construct(
private readonly ProductRepository $productRepository,
) {}
/**
* Returns filtered products based on the current searchQuery.
*
* @return Product[]
*/
public function getProducts(): array
{
if (strlen($this->searchQuery) < 2) {
return [];
}
return $this->productRepository->findBySearchQuery(
query: $this->searchQuery,
limit: $this->perPage,
);
}
}
3. Erste Live Component bauen
Eine Live Component ist eine PHP-Klasse mit dem #[AsLiveComponent]-Attribut. Das Attribut registriert die Klasse als Stimulus-Controller und verknüpft sie mit dem automatischen Server-Rendering. Der zugehörige Twig-Template-Pfad folgt der Konvention templates/components/ComponentName.html.twig — dasselbe Muster wie bei TwigComponent. Pflicht für jede Live Component ist der Import des DefaultActionTrait, der die Standard-Aktion für das Rendering bereitstellt.
Im Twig-Template wird die Komponente mit { { component('ProductSearch') } } eingebettet. Das Template der Komponente selbst muss ein einzelnes Root-Element haben — Live Component braucht einen stabilen DOM-Anker für das Morphdom-Diffing. Das Root-Element bekommt automatisch den Stimulus-Controller-Attribut data-controller und data-live-url-value. Eingabefelder werden mit data-model="searchQuery" annotiert — das weist Stimulus an, bei Eingaben den entsprechenden LiveProp mit dem neuen Wert zu senden. Der re-render passiert automatisch, sobald der Request zurückkommt, über Morphdom als minimaler DOM-Diff.
4. LiveProp: automatisch synchronisierte Properties
Das #[LiveProp]-Attribut markiert eine PHP-Property als reaktiv. Ohne weitere Parameter ist die Property read-only vom Browser aus — der Server kann sie setzen, aber der Browser kann sie nicht direkt ändern. Mit writable: true wird die Property beschreibbar: Das Stimulus-JavaScript übergibt den Wert des annotierten Eingabefelds bei jeder Änderung an den Server. Das bedeutet, dass die Property beim nächsten Rendering den vom Browser gesendeten Wert hat.
Für komplexere Fälle bietet #[LiveProp] die Option hydrateWith und dehydrateWith: Methoden, die die Property vor dem Senden an den Browser serialisieren und beim Empfang deserialisieren. Das ist nötig für Properties, die keine einfachen Scalar-Typen sind — z. B. Doctrine-Entities oder Value Objects. Eine Entity-Property wird dehydriert zu ihrer ID (einem Integer), und beim nächsten Request wird sie aus der Datenbank neu geladen. Das schützt davor, dass der Browser manipulierte Entity-Daten einschleust — nur die ID wird übertragen und die Entity wird serverseitig neu geladen.
5. LiveAction: Serveraktionen ohne eigene Route
Das #[LiveAction]-Attribut markiert eine Methode der Live Component als aufrufbare Aktion. Der Browser sendet einen POST-Request an den internen /_components-Endpunkt, der Name der Aktion wird mitgeschickt, und die Methode wird serverseitig ausgeführt. Das Ergebnis ist ein Re-render der Komponente. Klassische Anwendungsfälle: Einen Artikel zum Warenkorb hinzufügen, einen Like-Zähler erhöhen, eine Datei löschen — alles ohne eigene Route, ohne eigenen Controller.
Im Twig-Template ruft man eine LiveAction mit dem data-action-Attribut auf: data-action="live#action" data-action-name="addToCart". Das Stimulus-Framework schickt den Request, die Methode wird ausgeführt, und die Komponente re-rendert. Für Aktionen, die nach dem Ausführen zu einer anderen Seite weiterleiten sollen, gibt die LiveAction-Methode einen Symfony-Response zurück — ein Redirect-Response wird von Live Component korrekt als Redirect verarbeitet und nicht als HTML-Fragment behandelt. Das ermöglicht den Workflow: Formular absenden, Validierung auf dem Server, bei Fehler Re-render, bei Erfolg Redirect.
<?php
declare(strict_types=1);
namespace App\Twig\Components;
use App\Entity\CartItem;
use App\Repository\CartRepository;
use App\Repository\ProductRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\RouterInterface;
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveAction;
use Symfony\UX\LiveComponent\Attribute\LiveArg;
use Symfony\UX\LiveComponent\Attribute\LiveProp;
use Symfony\UX\LiveComponent\DefaultActionTrait;
/**
* Cart component with live add/remove actions.
* Re-renders automatically after each action.
*/
#[AsLiveComponent]
final class CartComponent
{
use DefaultActionTrait;
#[LiveProp]
public int $productId = 0;
public function __construct(
private readonly CartRepository $cartRepository,
private readonly ProductRepository $productRepository,
private readonly EntityManagerInterface $em,
private readonly RouterInterface $router,
) {}
/**
* Add a product to the cart — called from Twig via data-action="live#action".
*/
#[LiveAction]
public function addToCart(#[LiveArg] int $productId): void
{
$product = $this->productRepository->find($productId);
if (!$product) {
return;
}
$item = new CartItem();
$item->setProduct($product);
$item->setQuantity(1);
$this->em->persist($item);
$this->em->flush();
// Component re-renders automatically after this method returns
}
/**
* Remove an item and redirect to cart page.
*/
#[LiveAction]
public function removeAndRedirect(#[LiveArg] int $itemId): RedirectResponse
{
$item = $this->cartRepository->findItem($itemId);
if ($item) {
$this->em->remove($item);
$this->em->flush();
}
return new RedirectResponse($this->router->generate('cart_show'));
}
}
6. Live-Suche mit Doctrine und Debouncing
Live-Suche ist das häufigste Anwendungsszenario für Symfony UX Live Component. Das Sucheingabefeld ist ein normales HTML-Input mit data-model="searchQuery". Standardmäßig löst Live Component bei jedem Tastendruck einen Server-Request aus. Für Suchfelder ist das zu häufig — man möchte warten, bis der Nutzer mit der Eingabe pausiert. Das Debouncing steuert man über das Modifier-System: data-model="on(input)|debounce(300ms):searchQuery" wartet 300 Millisekunden nach dem letzten Tastendruck, bevor der Request gesendet wird. Das reduziert die Serverlast erheblich ohne spürbare Verzögerung für den Nutzer.
Die Doctrine-Repository-Methode für die Suche verwendet einen LIKE-Query oder, für bessere Relevanz-Sortierung, eine Fulltext-Suche mit MySQL MATCH AGAINST. Für kurze Suchanfragen (unter 2 Zeichen) gibt man ein leeres Array zurück und zeigt keine Ergebnisse — das verhindert nutzlose Queries. Das Ergebnis-Template rendert eine Liste oder ein Grid mit den gefundenen Produkten. Das gesamte Such-Erlebnis — Eingabe, Debounce, Server-Request, Rendering, DOM-Update — läuft in unter 100 Millisekunden auf lokalen Setups, für Nutzer fühlt es sich instant an.
7. Echtzeit-Validierung mit Symfony Constraints
Echtzeit-Formularvalidierung ist ein weiteres starkes Anwendungsszenario für Symfony UX Live Component. Das Formular-Rendering läuft vollständig in der Komponente, die Properties entsprechen den Formularfeldern, und #[LiveProp(writable: true)] markiert sie als reaktiv. Symfony-Constraint-Attribute auf den Properties (#[Assert\NotBlank], #[Assert\Email] etc.) werden beim Re-render validiert. Fehler werden über den Symfony-Validator aufgelöst und im Template ausgegeben.
Das Validierungs-Feedback erscheint, während der Nutzer das Formular ausfüllt — nicht erst nach dem Absenden. Das ist möglich, weil jede Property-Änderung einen Re-render auslöst, bei dem auch die Validierung läuft. Für die UX ist es wichtig, Validierungsfehler erst anzuzeigen, nachdem ein Feld verlassen wurde (data-model="on(blur)|live:updateModel:searchQuery") statt bereits beim ersten Tastendruck. Das Live Component-System bietet hierfür den on(blur)-Modifier, der den Re-render erst beim Verlassen des Feldes auslöst. Kombiniert mit den Symfony-Constraints entsteht ein professionelles Validierungs-Erlebnis vollständig in PHP.
8. Performance: Debounce, Loading-States und Optimistic UI
Jeder Re-render einer Live Component ist ein HTTP-Request. Für gute Performance sind drei Techniken wichtig: Debouncing (Requests verzögern), Loading-States (Nutzer-Feedback während des Requests) und Morphdom-freundliche Templates (minimale DOM-Änderungen). Debouncing haben wir in Abschnitt 6 behandelt. Loading-States werden über CSS-Klassen gesteuert, die Stimulus während des Requests setzt: data-loading="addClass(opacity-50)" am Root-Element oder an spezifischen Kindelementen blendet die Komponente während des Ladens leicht aus. Separate Spinner-Elemente mit data-loading="show" erscheinen nur während des Request-Lifecycles.
Morphdom ist das DOM-Diffing-Algorithmus, den Live Component verwendet. Für gutes Diffing sind stabile id-Attribute an Listenelementen wichtig: <li id="product-{ { product.id } }"> ermöglicht Morphdom, unveränderte Elemente zu identifizieren und nicht anzufassen. Ohne IDs vergleicht Morphdom die Elemente nur anhand ihrer Position, was zu unnötigen DOM-Änderungen und damit zu Flickern führen kann. Für Komponenten, die sich selten ändern, aber oft re-rendern (weil eine andere Property sich ändert), kann man einzelne Teile des Templates mit data-live-ignore markieren — Morphdom überspringt diese Elemente beim Diffing.
9. Live Component vs. Turbo Frames vs. Alpine.js
Die drei Technologien haben unterschiedliche Einsatzbereiche und ergänzen sich gut. Ein Überblick hilft bei der richtigen Wahl.
| Kriterium | Live Component | Turbo Frames | Alpine.js |
|---|---|---|---|
| Re-render durch | Property-Änderung | Link/Formular-Submit | JS-State-Änderung |
| Serverseitiger State | Ja, PHP-Properties | Nein (URL-Parameter) | Nein (Browser-only) |
| Live-Suche während Eingabe | Nativ mit Debounce | Nicht direkt möglich | Nur clientseitig |
| Symfony-Constraints | Nativ beim Re-render | Per Formular-Submit | Nicht nativ |
| Netzwerk-Overhead | Hoch (ein Request pro Änderung) | Niedrig (nur bei Submit) | Kein (clientseitig) |
Die Kombination aller drei Technologien in einem Projekt ist sinnvoll: Live Component für Suchfelder und Echtzeit-Formulare, Turbo Frames für Paginierung und Zeitraum-Filter, Alpine.js für rein clientseitige UI-Elemente wie Dropdowns und Modals. Die Grenzen zwischen den Technologien sind fließend — für konkrete Anwendungsfälle hilft der obige Vergleich bei der Wahl des einfachsten Werkzeugs.
Mironsoft
Symfony-Entwicklung, Live-Komponenten und reaktive PHP-Architekturen
Reaktive Symfony-Komponenten ohne JavaScript-Framework?
Wir implementieren Symfony UX Live Component in euren Stack — von Live-Suche und Echtzeit-Validierung über LiveAction-Patterns bis zur Performance-Optimierung mit Debouncing und Loading-States.
Live-Komponenten
LiveProp und LiveAction für reaktive Suchfelder, Warenkörbe und Echtzeit-Formulare in Symfony
Echtzeit-Validierung
Symfony Constraints in Live Components für sofortiges Validierungs-Feedback während der Eingabe
UX-Optimierung
Debouncing, Loading-States und Morphdom-freundliche Templates für flüssige Nutzererfahrung
10. Zusammenfassung
Symfony UX Live Component macht PHP-Klassen reaktiv — Properties, die mit #[LiveProp(writable: true)] markiert sind, werden bei Nutzeraktionen automatisch mit dem Browser synchronisiert und lösen einen Server-Re-render aus. LiveActions ermöglichen Serveroperationen ohne eigene Route oder Controller. Echtzeit-Validierung über Symfony-Constraints funktioniert beim Re-render automatisch. Live-Suche mit Doctrine-Repository und Debouncing ist in wenigen Zeilen PHP und Twig implementiert — ohne JavaScript zu schreiben.
Der Einsatz ist dort sinnvoll, wo Turbo Frames zu grob und Alpine.js zu clientseitig sind: bei Formularen, die serverseitigen State benötigen, bei Suchfeldern, die Datenbankabfragen auslösen, und bei komplexen Interaktionen, die Symfony-Services direkt ansprechen müssen. Live Component, Turbo Frames und Alpine.js ergänzen sich als Schichten: Turbo für Navigation, Live Component für reaktive Bereiche, Alpine.js für rein visuelle Interaktionen. PHP-Teams erhalten damit ein vollständiges reaktives Toolkit ohne JavaScript-Framework.
Symfony UX Live Component — Das Wichtigste auf einen Blick
LiveProp
#[LiveProp(writable: true)] synchronisiert Properties zwischen Browser und Server. Ändert sich der Wert, löst Stimulus automatisch einen Re-render aus — kein manueller AJAX.
LiveAction
#[LiveAction] macht Methoden zu Serveraktionen ohne eigene Route. Aufruf via data-action="live#action" im Twig-Template — danach automatischer Re-render.
Debouncing
data-model="on(input)|debounce(300ms):searchQuery" verzögert Re-renders bei Tasteneingaben um 300 ms — reduziert Serverlast bei Live-Suche erheblich.
Loading-States
data-loading="addClass(opacity-50)" zeigt Lade-Feedback während Server-Requests. data-loading="show" für Spinner-Elemente, data-live-ignore für stabile DOM-Bereiche.
11. FAQ: Symfony UX Live Component und reaktive PHP-Komponenten
1Was ist Symfony UX Live Component?
2Live Component vs. Turbo Frames?
3LiveProp vs. normales Property?
#[LiveProp(writable: true)] ist für Stimulus sichtbar und synchronisierbar. Normales Property ist privat für den Server — Stimulus ignoriert es vollständig.4Live-Suche mit Doctrine?
data-model="on(input)|debounce(300ms):searchQuery". Komponenten-Methode ruft Repository auf. Twig iteriert über Ergebnisse. Re-render mit 300ms Debounce automatisch.5Echtzeit-Validierung implementieren?
on(blur)-Modifier für Validierung erst nach Feldverlassen.6Warum Debouncing?
|debounce(300ms): ein Request 300ms nach Pause — reduziert Serverlast von 10+ auf 1 Request pro Suchvorgang.7Loading-States konfigurieren?
data-loading="addClass(opacity-50)" am Root. data-loading="show" für Spinner-Elemente. Stimulus setzt und entfernt automatisch. Kein JavaScript nötig.8Datenbankoperationen in LiveActions?
9Was ist Morphdom?
id-Attribute verbessern Diff-Qualität.10LiveActions absichern?
is_granted() und #[IsGranted] funktionieren in LiveActions normal. /_components-Pfad muss in Symfony-Firewall konfiguriert sein.