Symfony Live Components en UX 3.0: Reactieve Applicaties Zonder JavaScript in 2026

Deze tutorial laat zien hoe reactieve interfaces gebouwd kunnen worden met Symfony Live Components en UX 3.0, volledig zonder JavaScript. Met praktische codevoorbeelden voor zoeken, winkelwagen en formulieren.

Diagram van de Symfony Live Components-architectuur met gegevensstroom tussen PHP-server en reactieve gebruikersinterface

Symfony Live Components hebben de manier waarop PHP-ontwikkelaars reactieve interfaces bouwen fundamenteel veranderd. Met de release van Symfony UX 3.0 in 2026 heeft deze bibliotheek zich gevestigd als de ultieme oplossing voor het bouwen van dynamische webapplicaties op basis van puur PHP en Twig – zonder complexe JavaScript-code.

Deze tutorial behandelt de kernconcepten van Live Components, demonstreert praktische use cases en belicht de belangrijkste vernieuwingen van UX 3.0 die de ontwikkeling van reactieve interfaces binnen het Symfony-ecosysteem ingrijpend hebben veranderd.

Belangrijk

Live Components werken door automatisch AJAX-verzoeken naar de server te sturen wanneer een eigenschap gemarkeerd met #[LiveProp(writable: true)] wordt gewijzigd. Symfony rendert alleen de delen van het template die daadwerkelijk zijn veranderd, wat een vloeiende gebruikerservaring creëert zonder de volledige pagina te herladen.

Installatie en Configuratie van Symfony UX 3.0

De eerste stap bij het werken met Live Components is het installeren van het pakket symfony/ux-live-component. Versie 3.0 biedt volledige compatibiliteit met PHP 8.4+ en Symfony 7.4+, plus een native integratie met de AssetMapper waardoor Webpack of Vite niet meer nodig zijn voor het beheer van frontend-dependencies.

bash
# terminal
composer require symfony/ux-live-component

# With AssetMapper (recommended for Symfony 7.4+)
php bin/console importmap:require @symfony/ux-live-component

# Verify installation
php bin/console debug:twig --filter=component

Na de installatie zou het commando debug:twig alle beschikbare Twig-componenten in het project moeten tonen. De AssetMapper vereenvoudigt de configuratie aanzienlijk door JavaScript-compilers overbodig te maken, wat het ontwikkelproces versnelt in lijn met de Symfony-filosofie.

Een Reactieve Zoekcomponent Bouwen met LiveProp

Een van de meest voorkomende use cases voor Live Components is het implementeren van zoeksystemen die resultaten in real-time bijwerken terwijl de gebruiker typt. Het volgende voorbeeld toont een productzoekcomponent met dynamische filters en URL-synchronisatie.

php
<?php
// src/Twig/Components/ProductSearch.php

namespace App\Twig\Components;

use App\Repository\ProductRepository;
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveProp;
use Symfony\UX\LiveComponent\DefaultActionTrait;

#[AsLiveComponent]
class ProductSearch
{
    use DefaultActionTrait;

    // writable: true allows the frontend to modify this property
    // url: true syncs the value with ?query= in the address bar
    #[LiveProp(writable: true, url: true)]
    public string $query = '';

    #[LiveProp(writable: true)]
    public string $category = 'all';

    public function __construct(
        private ProductRepository $productRepository,
    ) {
    }

    public function getProducts(): array
    {
        if (strlen($this->query) < 2) {
            return [];
        }

        return $this->productRepository->search(
            query: $this->query,
            category: $this->category === 'all' ? null : $this->category,
            limit: 20,
        );
    }
}

Het attribuut #[AsLiveComponent] transformeert een PHP-klasse in een dynamische component. Eigenschappen gemarkeerd met #[LiveProp(writable: true)] kunnen door de frontend worden aangepast via data-model-attributen in de HTML. De optie url: true maakt deep linking mogelijk, zodat gebruikers URL's met toegepaste filters kunnen delen.

Het bijbehorende Twig-template maakt gebruik van de data-model-directive om invoervelden te koppelen aan de componenteigenschappen:

twig
{# templates/components/ProductSearch.html.twig #}
<div {{ attributes }}>
    <input
        type="search"
        data-model="debounce(300)|query"
        placeholder="Search products..."
        class="form-input w-full"
    />

    <select data-model="category" class="form-select mt-2">
        <option value="all">All categories</option>
        <option value="electronics">Electronics</option>
        <option value="books">Books</option>
    </select>

    <div class="mt-4">
        {% for product in this.products %}
            <div key="{{ product.id }}" class="border-b py-2">
                <h3>{{ product.name }}</h3>
                <span>{{ product.price|format_currency('EUR') }}</span>
            </div>
        {% else %}
            {% if query|length >= 2 %}
                <p>No results for "{{ query }}".</p>
            {% endif %}
        {% endfor %}
    </div>
</div>

De modifier debounce(300) zorgt ervoor dat verzoeken aan de server pas na 300 ms inactiviteit worden verstuurd, waardoor overbelasting van de backend tijdens het typen wordt voorkomen. Deze techniek is bijzonder relevant voor Symfony-sollicitatievragen over performance-optimalisatie.

Winkelwagen-Implementatie met LiveAction

Terwijl LiveProp reactieve eigenschappen beheert, maakt #[LiveAction] het mogelijk om methoden te creëren die reageren op gebruikersacties zoals klikken op knoppen. De winkelwagen-component demonstreert hoe meerdere acties gecoördineerd worden en hoe componenten onderling communiceren.

php
<?php
// src/Twig/Components/ShoppingCart.php

namespace App\Twig\Components;

use App\Entity\CartItem;
use App\Service\CartService;
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\ComponentToolsTrait;
use Symfony\UX\LiveComponent\DefaultActionTrait;

#[AsLiveComponent]
class ShoppingCart
{
    use DefaultActionTrait;
    use ComponentToolsTrait;

    #[LiveProp]
    public array $items = [];

    public function __construct(
        private CartService $cartService,
    ) {
    }

    #[LiveAction]
    public function addItem(#[LiveArg] int $productId, #[LiveArg] int $quantity = 1): void
    {
        $this->cartService->add($productId, $quantity);
        $this->items = $this->cartService->getItems();

        // Notify other components on the page
        $this->emit('cart:updated', [
            'count' => count($this->items),
        ]);
    }

    #[LiveAction]
    public function removeItem(#[LiveArg] int $itemId): void
    {
        $this->cartService->remove($itemId);
        $this->items = $this->cartService->getItems();
        $this->emit('cart:updated', ['count' => count($this->items)]);
    }

    public function getTotal(): float
    {
        return array_sum(array_map(
            fn(CartItem $item) => $item->getPrice() * $item->getQuantity(),
            $this->items,
        ));
    }
}

De methode emit(), beschikbaar via het ComponentToolsTrait, stelt componenten in staat om events uit te zenden waar andere componenten naar kunnen luisteren. Deze functionaliteit is cruciaal voor het bouwen van complexe interfaces waar meerdere componenten moeten communiceren zonder directe koppeling.

twig
{# templates/components/ShoppingCart.html.twig #}
<div {{ attributes }}>
    <h2>Cart ({{ items|length }})</h2>

    {% for item in items %}
        <div key="{{ item.id }}" class="flex justify-between py-2">
            <span>{{ item.name }} x{{ item.quantity }}</span>
            <span>{{ (item.price * item.quantity)|format_currency('EUR') }}</span>
            <button {{ live_action('removeItem', { itemId: item.id }) }}
                    class="text-red-600">
                Remove
            </button>
        </div>
    {% endfor %}

    <div class="border-t pt-2 font-bold">
        Total: {{ this.total|format_currency('EUR') }}
    </div>
</div>

De Twig-functie live_action() genereert de benodigde HTML-attributen om klikken op knoppen te koppelen aan #[LiveAction]-methoden in de PHP-component. Deze declaratieve aanpak elimineert de noodzaak om JavaScript-event-listeners handmatig te schrijven.

Integratie met Symfony Forms en Validatie

Een van de krachtigste kenmerken van Live Components is de native integratie met het Symfony-formuliersysteem. Het trait ComponentWithFormTrait maakt het mogelijk om formulieren te bouwen die in real-time valideren zonder de pagina te herladen, terwijl alle validatielogica centraal in PHP blijft.

php
<?php
// src/Twig/Components/RegistrationForm.php

namespace App\Twig\Components;

use App\Entity\User;
use App\Form\RegistrationType;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\FormInterface;
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveAction;
use Symfony\UX\LiveComponent\Attribute\LiveProp;
use Symfony\UX\LiveComponent\ComponentWithFormTrait;
use Symfony\UX\LiveComponent\DefaultActionTrait;

#[AsLiveComponent]
class RegistrationForm extends AbstractController
{
    use DefaultActionTrait;
    use ComponentWithFormTrait;

    #[LiveProp]
    public ?User $initialFormData = null;

    protected function instantiateForm(): FormInterface
    {
        return $this->createForm(RegistrationType::class, $this->initialFormData ?? new User());
    }

    #[LiveAction]
    public function save(EntityManagerInterface $em): mixed
    {
        // Submits + validates — re-renders with errors if invalid
        $this->submitForm();
        $user = $this->getForm()->getData();

        $em->persist($user);
        $em->flush();

        $this->addFlash('success', 'Account created.');

        return $this->redirectToRoute('app_login');
    }
}

De methode instantiateForm() wordt automatisch door het trait aangeroepen en moet de Symfony-formulierinstantie retourneren. De methode submitForm() voert de volledige validatie uit volgens de gedefinieerde constraints en rendert de component opnieuw met foutmeldingen als de validatie mislukt.

twig
{# templates/components/RegistrationForm.html.twig #}
<div {{ attributes }}>
    {{ form_start(form, {
        attr: {
            'data-action': 'live#action:prevent',
            'data-live-action-param': 'save'
        }
    }) }}
        {{ form_row(form.email) }}
        {{ form_row(form.plainPassword, { label: 'Password' }) }}
        {{ form_row(form.fullName) }}

        <button type="submit"
                class="btn-primary w-full"
                data-loading="addAttribute(disabled) addClass(opacity-50)">
            <span data-loading="hide">Create Account</span>
            <span data-loading="show" class="animate-spin">Loading...</span>
        </button>
    {{ form_end(form) }}
</div>

Het attribuut data-loading maakt het mogelijk om visuele staten te controleren tijdens AJAX-verzoeken, wat directe feedback aan de gebruiker biedt. Deze functionaliteit is bijzonder belangrijk in applicaties waar bewerkingen enkele seconden kunnen duren.

Uitgesteld Laden en Lazy Loading van Componenten

Componenten die zware databasequeries of externe API-aanroepen uitvoeren, kunnen de initiële laadtijd van de pagina negatief beïnvloeden. UX 3.0 introduceerde geavanceerde strategieën voor uitgesteld laden die de waargenomen performance aanzienlijk verbeteren.

php
<?php
// src/Twig/Components/AnalyticsDashboard.php

namespace App\Twig\Components;

use App\Service\AnalyticsService;
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveProp;
use Symfony\UX\LiveComponent\DefaultActionTrait;

#[AsLiveComponent]
class AnalyticsDashboard
{
    use DefaultActionTrait;

    #[LiveProp]
    public string $period = '30d';

    public function __construct(
        private AnalyticsService $analytics,
    ) {
    }

    public function getMetrics(): array
    {
        // Expensive query — runs only after page load
        return $this->analytics->getMetrics($this->period);
    }
}

Om uitgesteld laden te activeren, wordt het loading-attribuut gebruikt bij het opnemen van de component in het template:

twig
{# Parent page: load the dashboard after the page renders #}
<twig:AnalyticsDashboard loading="defer" period="30d" />

{# Or load when scrolled into view #}
<twig:AnalyticsDashboard loading="lazy" period="30d" />

De optie loading="defer" rendert aanvankelijk een lege placeholder en laadt de werkelijke component via AJAX nadat het DOM volledig is geladen. loading="lazy" maakt gebruik van de Intersection Observer API om de component pas te laden wanneer deze in de viewport van de gebruiker verschijnt.

Voor een aangepaste placeholder tijdens het laden wordt een placeholder-macro gedefinieerd in het template van de component:

twig
{# templates/components/AnalyticsDashboard.html.twig #}
<div {{ attributes }}>
    <h2>Analytics — {{ period }}</h2>
    {% for metric in this.metrics %}
        <div class="stat-card">
            <span class="stat-label">{{ metric.label }}</span>
            <span class="stat-value">{{ metric.value|number_format }}</span>
        </div>
    {% endfor %}
</div>

{% macro placeholder(props) %}
    <div class="animate-pulse space-y-4">
        <div class="h-6 bg-gray-200 rounded w-1/3"></div>
        <div class="h-20 bg-gray-200 rounded"></div>
        <div class="h-20 bg-gray-200 rounded"></div>
    </div>
{% endmacro %}

Deze techniek is essentieel voor het optimaliseren van applicaties met complexe dashboards of zware rapporten – een onderwerp dat regelmatig terugkomt in Symfony-sollicitatievragen over performance.

Communicatie Tussen Componenten met LiveListener

Het eventsysteem van Live Components maakt ontkoppelde architecturen mogelijk waarbij componenten via berichten communiceren. Het attribuut #[LiveListener] definieert methoden die automatisch worden uitgevoerd wanneer specifieke events worden uitgestuurd.

php
<?php
// src/Twig/Components/CartBadge.php

namespace App\Twig\Components;

use App\Service\CartService;
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveListener;
use Symfony\UX\LiveComponent\Attribute\LiveProp;
use Symfony\UX\LiveComponent\DefaultActionTrait;

#[AsLiveComponent]
class CartBadge
{
    use DefaultActionTrait;

    #[LiveProp]
    public int $count = 0;

    public function __construct(CartService $cartService)
    {
        $this->count = $cartService->getItemCount();
    }

    // Automatically re-renders when ShoppingCart emits 'cart:updated'
    #[LiveListener('cart:updated')]
    public function onCartUpdated(#[LiveArg] int $count): void
    {
        $this->count = $count;
    }
}

Wanneer de ShoppingCart-component artikelen toevoegt of verwijdert, stuurt deze het event cart:updated uit met het nieuwe totaal. De CartBadge-component, die zich overal op de pagina kan bevinden (doorgaans in de header), luistert naar dit event en werkt automatisch de telling bij zonder extra JavaScript-code.

Deze eventgebaseerde architectuur vergemakkelijkt het onderhoud van complexe applicaties en bevordert hergebruik van componenten – fundamentele principes in moderne Symfony-ontwikkeling.

Belangrijke Wijzigingen in Symfony UX 3.0

De release van Symfony UX 3.0 bracht significante wijzigingen met zich mee die beveiliging, performance en ontwikkelaarservaring verbeteren. De volgende tabel geeft een overzicht van de belangrijkste veranderingen:

| Wijziging | Voorheen (2.x) | Nu (3.0) | |-----------|---------------|----------| | CSRF-bescherming | csrf: true op #[AsLiveComponent] | Same-Origin/CORS (automatisch) | | Twig CVA-functie | cva() | html_cva() uit twig/html-extra 3.12+ | | Componentstandaarden | Optioneel | twig_component.defaults verplicht | | Verwijderde pakketten | Swup, LazyImage, Typed, TogglePassword | Native API's of UX Toolkit | | PHP-vereiste | 8.1+ | 8.4+ | | Symfony-vereiste | 6.4+ | 7.4+ |

De vervanging van expliciete CSRF-bescherming door automatische Same-Origin-policies vereenvoudigt de configuratie van componenten. De functie html_cva() vervangt de vorige cva() en vereist de installatie van het pakket twig/html-extra 3.12+.

Pakketten die voorheen in UX 2.x waren opgenomen, zijn verwijderd ten gunste van native browser-API's of gemigreerd naar het UX Toolkit. Deze beslissing verkleint de JavaScript-bundlegrootte en verbetert de compatibiliteit met moderne buildtools.

De verhoogde minimumvereisten naar PHP 8.4+ en Symfony 7.4+ stellen UX 3.0 in staat om moderne taalfeatures te benutten zoals readonly-properties, verbeterde enums en performance-optimalisaties van de JIT-compiler.

Geavanceerde Use Cases en Aanbevolen Patronen

Naast de basisvoorbeelden ondersteunen Live Components geavanceerde patronen die veelvoorkomende uitdagingen in moderne webontwikkeling oplossen. De combinatie van meerdere #[LiveProp]-eigenschappen, conditionele validatie en integratie met externe services maakt het mogelijk om rijke gebruikerservaringen te creëren zonder onnodige complexiteit.

Een veelgebruikt patroon is het maken van autocomplete-componenten die externe API's bevragen terwijl de gebruiker typt, intelligente caching implementeren om dubbele verzoeken te vermijden en contextuele suggesties tonen op basis van het gebruikersgedrag. Dit type component demonstreert de flexibiliteit van het systeem bij het combineren van reactieve staat, asynchrone acties en communicatie met andere componenten.

Een ander geavanceerd gebruik betreft multi-step formulieren waarbij elke stap van de wizard een onafhankelijke Live Component is die eigen businessregels valideert, maar de globale status deelt via een sessieservice. Deze architectuur houdt elke component gefocust op één verantwoordelijkheid terwijl een vloeiende gebruikerservaring behouden blijft.

Voor enterprise-applicaties die volledige audit vereisen, kunnen Live Components worden gecombineerd met Symfony event subscribers om automatisch alle gebruikersacties, wijzigingstijdstempels en eerdere eigenschapsstaten te loggen. Deze integratie toont aan hoe UX 3.0 naadloos past binnen het volwassen Symfony-ecosysteem zonder incompatibele abstracties te introduceren.

Klaar om je Symfony gesprekken te halen?

Oefen met onze interactieve simulatoren, flashcards en technische tests.

Conclusie: De Toekomst van Reactieve Interfaces in PHP

Symfony Live Components vertegenwoordigen een paradigmaverschuiving in de ontwikkeling van webapplicaties met PHP. Door de noodzaak te elimineren om complexe JavaScript-code te schrijven voor veelvoorkomende reactieve functionaliteit, stelt de bibliotheek PHP-ontwikkelaars in staat om al hun kennis van het Symfony-ecosysteem te benutten voor het bouwen van moderne en responsieve interfaces.

De release van UX 3.0 heeft de bibliotheek gevestigd als een volwassen, productieklare oplossing met significante verbeteringen op het gebied van beveiliging, performance en ontwikkelaarservaring. De native integratie met Symfony Forms, het geavanceerde systeem voor uitgesteld laden en de eventgebaseerde architectuur bieden de nodige tools om complexe applicaties te bouwen zonder onderhoudbaarheid op te offeren.

Voor ontwikkelaars die zich willen verdiepen in het Symfony-ecosysteem is het beheersen van Live Components essentieel – zowel voor praktische projecten als om op te vallen bij technische sollicitatiegesprekken. Het vermogen om reactieve interfaces te bouwen zonder afhankelijkheid van zware JavaScript-frameworks demonstreert geavanceerde platformkennis en aansluiting bij moderne webontwikkelingstrends.

De toekomst van webinterfaces in PHP richt zich steeds meer op oplossingen die de logica op de server houden terwijl ze gebruikerservaringen bieden die vergelijkbaar zijn met JavaScript-SPA's. Live Components en UX 3.0 leiden deze transformatie en bieden Symfony-ontwikkelaars de tools om te concurreren met moderne frontend-frameworks, met behoud van de voordelen van server-side architectuur.

Tags

#symfony
#live components
#symfony ux
#php
#twig
#reactive ui

Delen

Gerelateerde artikelen