Eliminierung technischer Schulden mit PHPStan, Rector PHP und PHPUnit. Über 20 Jahre Praxiserfahrung in skalierbaren Backends.
- Aktualisiert:
- Autor:
- Roland Golla
Was sind Symfony UX Turbo Stream Actions?
Symfony UX Turbo Stream Actions sind HTML Fragmente, die der Server an verbundene Browser sendet, um gezielte DOM Manipulationen auszulösen, ohne eine ganze Seite neu zu laden. Jede Action hat ein Ziel Element im DOM (target) und beschreibt, was genau passieren soll: einfügen, ersetzen, aktualisieren oder entfernen.
Das Prinzip heißt HTML over the wire: Symfony Controller rendern Twig Templates im speziellen Format text/vnd.turbo-stream.html, Turbo im Browser interpretiert diese Antworten und wendet die enthaltenen Actions direkt auf die Seite an. So entstehen reaktive, Single Page App ähnliche Oberflächen komplett ohne eigenes JavaScript Framework.
Turbo Stream Actions sind Teil von Symfony UX Turbo, das Hotwire Turbo nativ in Symfony integriert. Kombiniert mit Mercure lassen sich Updates sogar in Echtzeit an alle aktiven Clients broadcasten, was Chats, Kollaborationstools und Live Dashboards direkt mit PHP und Twig möglich macht.
Erreichen Sie unsere PHP Consultant Spezialisten
Wir sind Experten für PHP und helfen Ihnen, Ihre digitalen Herausforderungen zu meistern. Unser erfahrenes Team unterstützt Sie bei PHP Updates, PHP Refactoring und berät Sie remote zu allen Fragen rund um PHP. Mit unseren vollautomatischen CI/CD Deployments und einer robusten Docker-Infrastruktur bringen wir Ihre PHP-Projekte auf das nächste Level. Vertrauen Sie auf unsere Expertise für zuverlässige und skalierbare PHP-Lösungen.
Die neun Turbo Stream Actions im Überblick
Turbo Streams unterstützt neun klar abgegrenzte Actions. Jede Action beschreibt genau eine DOM Operation, bezogen auf ein target (per DOM ID) oder targets (per CSS Selector). Diese bewusste Beschränkung hält Responses sauber und wiederverwendbar.
- append: hängt den Inhalt ans Ende des Ziel Containers an
- prepend: fügt den Inhalt am Anfang des Ziel Containers ein
- before: platziert den Inhalt vor dem Ziel Element
- after: platziert den Inhalt nach dem Ziel Element
- replace: ersetzt das komplette Ziel Element inklusive seiner Hülle
- update: tauscht nur den inneren HTML Inhalt aus, das Ziel Element bleibt bestehen
- remove: entfernt das Ziel Element ersatzlos, Template wird ignoriert
- morph: moderne DOM Morphing Strategie, die nur veränderte Knoten austauscht
- refresh: löst einen Page Refresh aus, ideal für Broadcast Szenarien
Mehrere Actions können in einer einzigen Response zusammengefasst werden. Das macht es einfach, nach einer Formular Aktion an mehreren Stellen der Seite gleichzeitig zu aktualisieren, etwa ein Formular zurücksetzen, die Liste ergänzen und einen Zähler hochzählen in einem einzigen Request.
append, prepend, before, after: Inhalte ins DOM einfügen
Diese vier Actions sind die Basis für das Einfügen neuer Elemente. append hängt ans Ende eines Containers an, prepend setzt vorn ein. before und after platzieren das neue Element relativ zu einem Geschwister Element, nicht innerhalb eines Containers.
Klassischer Anwendungsfall: Eine neue Nachricht wird an die Chat Liste gehängt. Das Template enthält den kompletten Turbo Stream, der vom Symfony Controller als .stream.html.twig gerendert wird.
<turbo-stream action="append" target="messages">
<template>
<div id="message_42">
Neue Nachricht aus dem Symfony Controller
</div>
</template>
</turbo-stream>
<turbo-stream action="prepend" target="notifications">
<template>
<div class="toast">Erfolg gespeichert</div>
</template>
</turbo-stream>
<turbo-stream action="after" target="current_row">
<template>
<tr id="row_43"><td>Neue Zeile danach</td></tr>
</template>
</turbo-stream>
Ein wichtiges Detail: Wenn das eingefügte Element eine ID trägt, die schon im Container existiert, wird statt angehängt oder vorangestellt ersetzt. Das verhindert Duplikate bei mehrfachen Broadcasts und macht den Code idempotent.
replace, update, remove: Elemente ersetzen und entfernen
Der Unterschied zwischen replace und update ist im Alltag der häufigste Stolperstein. replace tauscht das komplette Element samt äußerer Hülle aus, update ersetzt nur den inneren HTML Inhalt und behält die Hülle bei. Das ist entscheidend, wenn Stimulus Controller oder Event Handler am Element hängen.
Bei update bleiben gebundene Handler erhalten, bei replace müssen sie neu initialisiert werden. Für einen Zähler, der nur die Zahl austauscht, ist update die richtige Wahl.
<turbo-stream action="replace" target="product_card_12">
<template>
<div id="product_card_12" class="card">
Komplett neue Karte mit neuer Hülle
</div>
</template>
</turbo-stream>
<turbo-stream action="update" target="unread_count">
<template>
42
</template>
</turbo-stream>
<turbo-stream action="remove" target="message_42">
</turbo-stream>
remove ist die einfachste Action: Das Ziel Element wird entfernt, das template Tag wird komplett ignoriert. Nützlich für gelöschte Datenbankeinträge, geschlossene Toasts oder abgelaufene Sessions.
Beide Actions können auf mehrere Elemente gleichzeitig wirken, wenn statt target das Attribut targets mit einem CSS Selektor genutzt wird: <turbo-stream action="remove" targets=".toast-expired"> entfernt alle passenden Elemente in einem Rutsch.
morph und refresh: Moderne DOM Updates 2026
Ab Turbo 8 kamen zwei neue Actions dazu, die moderne Realtime Anwendungen deutlich vereinfachen. morph nutzt DOM Morphing statt eines harten Austauschs: Nur tatsächlich veränderte Knoten werden angefasst, Scroll Position, Fokus und Eingabestatus bleiben erhalten. Das ist ein Gamechanger für Formulare und Listen mit Interaktion.
Technisch wird method="morph" als Attribut an replace oder update gehängt. Unter der Haube wird eine effiziente Diff Implementierung verwendet, die mit morphdom verwandt ist.
<turbo-stream action="replace" method="morph" target="order_form">
<template>
<form id="order_form">
<!-- neues Formular, aber aktuelle Eingabe bleibt fokussiert -->
</form>
</template>
</turbo-stream>
<turbo-stream action="update" method="morph" target="current_step">
<template>
<li>Neuer Schritt im Wizard</li>
</template>
</turbo-stream>
refresh löst einen vollständigen Page Refresh bei allen verbundenen Clients aus. Zusammen mit der request-id lassen sich Mehrfach Aufrufe debouncen: Wird die gleiche ID innerhalb kurzer Zeit mehrfach gesendet, führt Turbo den Refresh nur einmal aus. Ideal für Mercure Broadcasts nach großen Datenbank Änderungen.
<turbo-stream action="refresh" request-id="order_updated_789"></turbo-stream>
<turbo-stream action="refresh" method="morph" scroll="preserve"></turbo-stream>
Turbo Streams in Symfony Controllern
In Symfony UX Turbo liefert der Controller eine Stream Response, indem er das Request Format auf TurboBundle::STREAM_FORMAT setzt und ein Twig Template im Stream Format rendert. Der Response Content Type wird dadurch automatisch zu text/vnd.turbo-stream.html, was Turbo im Browser als Signal nutzt, die Antwort als Stream statt als normale Seite zu behandeln.
Ein Controller für ein Task Formular, das nach erfolgreichem Speichern eine neue Zeile an die Liste anhängt und den Zähler aktualisiert, sieht so aus:
<?php
namespace App\Controller;
use App\Entity\Task;
use App\Form\TaskType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\UX\Turbo\TurboBundle;
class TaskController extends AbstractController
{
public function new(Request $request): Response
{
$task = new Task();
$form = $this->createForm(TaskType::class, $task);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// Task speichern
if (TurboBundle::STREAM_FORMAT === $request->getPreferredFormat()) {
$request->setRequestFormat(TurboBundle::STREAM_FORMAT);
return $this->render('task/new_success.stream.html.twig', [
'task' => $task,
]);
}
return $this->redirectToRoute('task_index');
}
return $this->render('task/new.html.twig', [
'form' => $form,
]);
}
}
Das passende Template task/new_success.stream.html.twig enthält mehrere Stream Actions in einer einzigen Response:
<turbo-stream action="append" target="tasks">
<template>
{{ include('task/_row.html.twig', { task: task }) }}
</template>
</turbo-stream>
<turbo-stream action="update" target="task_count">
<template>{{ task_count }}</template>
</turbo-stream>
<turbo-stream action="replace" target="task_form">
<template>
{{ include('task/_form.html.twig') }}
</template>
</turbo-stream>
Das .stream.html.twig im Dateinamen ist reine Konvention, hat aber keinen technischen Effekt. Entscheidend sind der gesetzte Request Format und der Content Type in der Antwort.
Broadcasting mit Mercure: Realtime für alle Clients
Die wahre Stärke von Turbo Streams zeigt sich, wenn sie über Mercure an alle verbundenen Clients gesendet werden. Jede Änderung im Backend, ausgelöst durch einen Nutzer, erscheint innerhalb von Millisekunden auf allen offenen Browser Tabs. Das funktioniert ohne eigene WebSocket Infrastruktur und ohne JavaScript Framework.
In Doctrine Entities reicht das #[Broadcast] Attribut, um automatisch bei jeder Änderung entsprechende Turbo Streams zu erzeugen. Symfony UX Turbo kümmert sich um die Übersetzung in Actions und den Versand über Mercure.
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\UX\Turbo\Attribute\Broadcast;
#[ORM\Entity]
#[Broadcast]
class Task
{
#[ORM\Id, ORM\GeneratedValue, ORM\Column]
public ?int $id = null;
#[ORM\Column]
public string $title = '';
#[ORM\Column]
public bool $done = false;
}
Im Template wird auf den Stream gehört mit turbo_stream_listen(), das automatisch den passenden Stimulus Controller für den Transport einhängt:
<div {{ turbo_stream_listen('App\\Entity\\Task') }}>
<ul id="tasks">
{% for task in tasks %}
{{ include('task/_row.html.twig', { task: task }) }}
{% endfor %}
</ul>
</div>
Für DSGVO konforme Deployments ist entscheidend, dass Mercure als selbst gehosteter Hub betrieben wird, nicht als externer Cloud Service. Der Mercure Hub ist ein schlankes Go Binary, das auf dem gleichen Server oder in einem eigenen Container laufen kann. Bei NCA hosten wir Mercure grundsätzlich on premise, sodass keine Chat Daten, Formulareingaben oder Live Updates an Dritte Dienste außerhalb der EU fließen.
Kombiniert mit einem europäischen Hoster und eigenem TLS ist das die saubere Alternative zu proprietären Realtime Services wie Pusher oder Firebase. Mehr dazu im Rahmen unseres Vibe Coding Consulting für moderne PHP Teams.
Custom Stream Actions: Eigene Aktionen registrieren
Die neun eingebauten Actions decken den Großteil aller Fälle ab. Für spezielle Anforderungen, etwa ein Toast Alert, ein Modal öffnen oder eine Animation starten, lassen sich eigene Actions im JavaScript registrieren. Turbo stellt dafür das StreamActions Modul bereit.
Ein Beispiel für eine Custom Action toast, die eine Nachricht einblendet:
import { StreamActions } from '@hotwired/turbo';
StreamActions.toast = function () {
const message = this.getAttribute('message');
const level = this.getAttribute('level') || 'info';
const toast = document.createElement('div');
toast.className = 'toast toast-' + level;
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => toast.remove(), 3000);
};
Im Symfony Template sieht der Aufruf dann so aus:
<turbo-stream action="toast" message="Task erfolgreich gespeichert" level="success"></turbo-stream>
Das Prinzip lässt sich auf jedes JavaScript Snippet oder npm Paket anwenden. Wichtig bleibt: Turbo Streams sind dazu gedacht, HTML über die Leitung zu liefern. Komplexe Interaktionen gehören in Stimulus Controller, nicht in Custom Actions. So bleibt der Code testbar und wiederverwendbar.
With Symfony UX Turbo, you can get rid of JavaScript and enjoy using Twig again.
NCA Erfahrung: Hotwire Migration für Symfony Teams
In unseren Symfony Projekten sehen wir immer wieder die gleiche Situation: Ein PHP Team hat irgendwann eine React oder Vue Single Page App vor die Symfony API gebaut, doppelte Businesslogik entstanden, die Deploys koordinieren sich nicht mehr, und neue Features brauchen dreimal so lang. Turbo Stream Actions kombiniert mit Symfony UX sind für viele dieser Teams der einfachere Weg.
Unser Consulting Angebot rund um Turbo Streams umfasst:
- Audit bestehender Symfony Anwendungen und Identifikation von Turbo fähigen Interaktionen
- Schrittweise Migration weg von schwerem Frontend Code, inklusive Rückbau von JSON APIs, die nur UI Zwecken dienten
- Einrichtung eines selbst gehosteten Mercure Hubs auf europäischer Infrastruktur, DSGVO konform
- Integration von Broadcast Attributen an Doctrine Entities und sauberer Stream Templates
- Cypress E2E Tests für Turbo Flows, damit Refactorings abgesichert sind
Ergänzend setzen wir KI gestützte Workflows im Terminal ein, um Boilerplate für Stream Templates, Stimulus Controller und Controller Actions zu beschleunigen. Details dazu findest du in unserem Vibe Coding Consulting sowie in der Übersicht zum Symfony UX Ökosystem.
Für eine kostenlose Erstberatung zu Turbo Streams, Mercure Hub Setup oder einer Hotwire Migration erreichst du Roland unter roland@nevercodealone.de oder telefonisch unter +49 176 24747727.
Lass uns sprechen
Finde das passende Angebot für dein Projekt
Anfrage-Konfiguration
Starten Sie Ihre Anfrage
Gesetzliche Konformität & Inklusion. Optimierung von Performance und Conversion durch radikal nutzerzentriertes, universelles Design.
Skalierbare KI-Systeme mit echtem Code Ownership. CI/CD, Backup-Strategien und Infrastruktur, die mit deinem Team wächst.
Anfrage-Konfiguration
Worauf liegt dein Fokus?
Wähle die Expertise, die dein Projekt jetzt am dringendsten benötigt.
Häufige Fragen zu Symfony UX Turbo Stream Actions
Die wichtigsten Fragen zu Turbo Stream Actions in Symfony UX, zur Integration mit Mercure und zu DSGVO konformen Deployments, gesammelt aus echten Projektgesprächen mit Entwicklungsteams.
Welche Turbo Stream Actions gibt es in Symfony UX 2026?
Turbo Streams unterstützt neun Actions: append, prepend, before, after, replace, update, remove, morph und refresh. Jede Action beschreibt eine DOM Operation auf ein Ziel Element und lässt sich in einem einzigen Response mehrfach kombinieren.
Was ist der Unterschied zwischen replace und update 2026?
replace tauscht das komplette Ziel Element samt Hülle aus, update ersetzt nur den inneren HTML Inhalt. Bei update bleiben gebundene Stimulus Controller und Event Handler erhalten, bei replace müssen sie neu initialisiert werden.
Wie setze ich Turbo Streams in Symfony Controllern 2026 ein?
Der Controller prüft mit TurboBundle::STREAM_FORMAT den Preferred Format, setzt den Request Format entsprechend und rendert ein Twig Template mit turbo-stream Elementen. Symfony liefert dann automatisch den Content Type text/vnd.turbo-stream.html aus.
Wie funktioniert Broadcasting mit Mercure 2026?
Das Broadcast Attribut an einer Doctrine Entity genügt, damit Symfony UX Turbo bei jeder Änderung automatisch Stream Actions erzeugt und über Mercure an alle verbundenen Clients sendet. Im Template wird mit turbo_stream_listen auf die Entity gehört.
Kann ich Turbo Streams DSGVO konform betreiben 2026?
Ja, Mercure lässt sich als selbst gehostetes Go Binary on premise oder bei einem europäischen Hoster betreiben. So verlassen keine Chat Daten, Formulareingaben oder Live Updates die EU. Das ist die saubere Alternative zu Pusher oder Firebase.
Was macht die morph Methode bei Turbo Streams?
Mit method=morph nutzt Turbo DOM Morphing statt eines harten Austauschs. Nur tatsächlich veränderte Knoten werden angefasst, Scroll Position, Fokus und Eingabestatus im Formular bleiben erhalten. Das ist ideal für Formulare und interaktive Listen.
Was bedeutet die refresh Action in Turbo Streams?
refresh löst einen kompletten Page Refresh bei allen Clients aus. Mit einer request-id lassen sich Mehrfach Aufrufe debouncen, sodass nicht bei jedem Broadcast die ganze Seite erneut geladen wird. Ideal nach großen Datenbank Änderungen.
Brauche ich JavaScript für Turbo Streams?
Für die Standard Actions nicht. Symfony UX Turbo bringt alles mit, um ohne eigene JavaScript Zeile reaktive Oberflächen zu bauen. Erst bei Custom Actions oder komplexer Interaktion greifst du zu Stimulus Controllern.
Was ist der Unterschied zwischen Turbo Frames und Turbo Streams?
Turbo Frames isolieren einen Bereich der Seite, der unabhängig geladen und aktualisiert wird. Turbo Streams liefern gezielte DOM Operationen an beliebige Elemente. Frames sind der Behälter, Streams sind die Befehle, die in diesen Behältern oder direkt im Dokument wirken.
Wie kann ich eigene Stream Actions schreiben?
Mit StreamActions.actionName = function() { ... } lassen sich in JavaScript beliebige Actions registrieren. Der Funktionsrumpf greift über this.getAttribute auf die Attribute des turbo-stream Elements zu und führt die gewünschte Operation aus.
Wie teste ich Turbo Stream Flows sinnvoll?
Wir setzen bei NCA ausschließlich Cypress für End to End Tests ein. Cypress prüft sowohl die initiale Seite als auch die reaktiven Updates nach Stream Responses und funktioniert gut mit Mercure basierten Broadcasts. Unit Tests für Controller laufen wie gewohnt mit PHPUnit.
Welche Symfony Version brauche ich für Turbo Streams?
Symfony UX Turbo läuft ab Symfony 5.4, empfohlen sind Symfony 6.4 oder 7 mit PHP 8.2 oder höher. Die aktuelle Version 2 wird parallel zu Version 3 aktiv gepflegt und erhält regelmäßige Updates im Symfony Release Zyklus.