Symfony Live Components dan UX 3.0: Aplikasi Reaktif Tanpa JavaScript di 2026
Symfony Live Components memungkinkan pengembangan antarmuka reaktif dengan PHP dan Twig tanpa JavaScript. Tutorial lengkap LiveProp, LiveAction, form, dan deferred loading.

Symfony Live Components menghadirkan antarmuka pengguna yang reaktif dan real-time sepenuhnya dalam PHP dan Twig. Dirilis pada April 2026, Symfony UX 3.0 menghapus semua deprecation dari versi 2.x, menaikkan kebutuhan minimum ke PHP 8.4 dan Symfony 7.4, serta membuang empat paket usang — menghasilkan toolkit UX paling ramping hingga saat ini.
Live Components mengubah setiap komponen Twig menjadi elemen stateful dan interaktif yang di-render ulang di sisi server saat pengguna berinteraksi — tanpa menulis JavaScript. Data binding, aksi, validasi, dan penanganan form semuanya terjadi di PHP.
Menyiapkan Symfony UX 3.0 dengan Live Components
Instalasi memerlukan Symfony 7.4+, PHP 8.4+, dan AssetMapper atau Webpack Encore untuk aset frontend.
# 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=componentAssetMapper menangani pengiriman JavaScript tanpa Node.js, Webpack, atau langkah build apapun. Stimulus controller yang mendukung Live Components dimuat secara otomatis.
Membuat Komponen Pencarian Reaktif dengan LiveProp
Sebuah search bar yang menyaring hasil saat pengguna mengetik — tanpa JavaScript sama sekali. Atribut #[LiveProp] menandai properti sebagai stateful, yang dipertahankan antar re-render melalui token terenkripsi yang aman untuk URL.
<?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,
);
}
}Template Twig yang bersesuaian mengikat input ke properti dengan data-model.
{# 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>Modifier debounce(300) menunggu 300ms setelah ketikan terakhir sebelum mengirim request. Atribut key pada item daftar memungkinkan DOM morphing yang efisien — hanya elemen yang berubah yang diperbarui.
Mengatur url: true pada LiveProp menyinkronkan nilai properti dengan URL browser sebagai query parameter. Hasil pencarian yang bisa di-bookmark dan dibagikan — dengan satu atribut saja.
Menangani Aksi Pengguna dengan LiveAction
Selain data binding, #[LiveAction] mengekspos metode PHP sebagai aksi yang dapat dipanggil dari frontend. Argumen dilewatkan melalui #[LiveArg].
<?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,
));
}
}Template memanggil aksi dengan helper live_action() atau atribut data-action.
{# 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>Metode emit() menyiarkan event ke Live Components lain pada halaman — misalnya komponen badge keranjang dapat mendengarkan dengan #[LiveListener('cart:updated')] dan memperbarui hitungannya tanpa reload halaman penuh.
Siap menguasai wawancara Symfony Anda?
Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.
Validasi Form Real-Time Tanpa JavaScript
Live Components terintegrasi langsung dengan Symfony Forms. ComponentWithFormTrait menghubungkan state form, error validasi, dan submission ke lifecycle komponen.
<?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');
}
}{# 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>Error validasi muncul secara inline saat pengguna berpindah antar field — modifier on(change) pada data-model memicu re-render saat blur, menampilkan pesan constraint dari Symfony Validator secara instan.
Deferred dan Lazy Loading untuk Performa
Komponen berat — dashboard, grafik analitik, daftar panjang — mendapat manfaat dari deferred rendering. Alih-alih memblokir muat halaman awal, komponen menampilkan placeholder dan mengambil konten via AJAX setelah halaman siap.
<?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);
}
}{# 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" />Makro placeholder di dalam template komponen mendefinisikan apa yang dilihat pengguna saat konten asli dimuat.
{# 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 %}Mode loading="defer" mengirim request AJAX segera saat halaman dimuat. Mode loading="lazy" menggunakan IntersectionObserver — request baru dikirim saat komponen masuk ke viewport. Kedua pendekatan menjaga respons halaman awal tetap cepat.
Symfony UX 3.0 mengganti token CSRF dengan perlindungan same-origin/CORS untuk Live Components. Argumen csrf pada #[AsLiveComponent] tidak lagi ada. Pastikan server menerapkan kebijakan same-origin.
Komunikasi Antar Komponen dengan Events
Live Components berkomunikasi melalui sistem event. Komponen child mengirim event; parent (atau sibling) mendengarkan dan bereaksi.
<?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;
}
}Pola ini menjaga komponen tetap terpisah. Komponen ShoppingCart tidak mereferensikan CartBadge secara langsung — event bus yang menangani koneksinya.
Perubahan di Symfony UX 3.0
UX 3.0 adalah rilis pembersihan. Aplikasi yang berjalan tanpa peringatan deprecation pada 2.x dapat di-upgrade dengan gesekan minimal.
| Perubahan | Sebelum (2.x) | Sesudah (3.0) |
|-----------|---------------|---------------|
| Perlindungan CSRF | csrf: true pada #[AsLiveComponent] | Same-origin/CORS (otomatis) |
| Fungsi Twig CVA | cva() | html_cva() dari twig/html-extra 3.12+ |
| Config defaults komponen | Opsional | twig_component.defaults wajib |
| Paket dihapus | Swup, LazyImage, Typed, TogglePassword | Gunakan native API atau UX Toolkit |
| Kebutuhan PHP | 8.1+ | 8.4+ |
| Kebutuhan Symfony | 6.4+ | 7.4+ |
UX Toolkit, yang diperkenalkan selama siklus 2.x, menyediakan komponen UI siap pakai (Button, Dialog, Card, Table, Pagination) yang di-style dengan Shadcn UI atau Flowbite 4.0 — menutup celah yang ditinggalkan oleh paket yang dihapus.
Sedang mempersiapkan pertanyaan wawancara Symfony? Memahami Live Components dan sistem event semakin relevan untuk posisi senior. Panduan pertanyaan wawancara Symfony mencakup topik-topik dasar yang cocok dipadukan dengan pengetahuan praktis ini.
Kesimpulan
- Live Components menghilangkan kebutuhan akan framework JavaScript di sebagian besar aplikasi Symfony yang berat CRUD. Data binding, aksi, validasi, dan penanganan form tetap di PHP dan Twig
#[LiveProp(writable: true, url: true)]membuat antarmuka stateful yang bisa di-bookmark dan dibagikan dengan satu atribut- Deferred dan lazy loading (
loading="defer"/loading="lazy") menjaga muat halaman awal tetap cepat sementara komponen berat di-render secara asinkron - UX 3.0 menghapus token CSRF demi perlindungan same-origin/CORS — keamanan lebih sederhana dengan lebih sedikit bagian bergerak
- Sistem event (
emit/#[LiveListener]) memungkinkan komunikasi komponen yang terpisah tanpa manajemen state global - Untuk API, padukan Live Components dengan API Platform pada Symfony 7 untuk arsitektur yang berat di backend
Mulai berlatih!
Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.
Tag
Bagikan
Artikel terkait

Doctrine ORM: Menguasai Relasi di Symfony
Panduan lengkap relasi Doctrine ORM di Symfony. OneToMany, ManyToMany, strategi pemuatan, dan optimasi performa dengan contoh praktis.

Pertanyaan Wawancara Symfony: Top 25 di 2026
25 pertanyaan wawancara Symfony paling sering ditanyakan. Arsitektur, Doctrine ORM, service, keamanan, form, dan testing dengan jawaban detail dan contoh kode.

Symfony 7: API Platform dan Praktik Terbaik
Panduan lengkap membangun REST API profesional dengan Symfony 7 dan API Platform 4. State Providers, Processors, validasi, dan serialisasi dijelaskan dengan contoh praktis.