+ ```
+
## 2.0.0
- Support for `stimulus` version 2 was removed and support for `@hotwired/stimulus`
diff --git a/src/LiveComponent/composer.json b/src/LiveComponent/composer.json
index 90f578db7f6..21ff0e06f7e 100644
--- a/src/LiveComponent/composer.json
+++ b/src/LiveComponent/composer.json
@@ -27,7 +27,7 @@
},
"require": {
"php": ">=8.0",
- "symfony/ux-twig-component": "^2.0"
+ "symfony/ux-twig-component": "^2.1"
},
"require-dev": {
"doctrine/annotations": "^1.0",
diff --git a/src/LiveComponent/src/DependencyInjection/LiveComponentExtension.php b/src/LiveComponent/src/DependencyInjection/LiveComponentExtension.php
index c782afe7e30..1f8ed8c8bd9 100644
--- a/src/LiveComponent/src/DependencyInjection/LiveComponentExtension.php
+++ b/src/LiveComponent/src/DependencyInjection/LiveComponentExtension.php
@@ -21,6 +21,7 @@
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\ComponentValidator;
use Symfony\UX\LiveComponent\ComponentValidatorInterface;
+use Symfony\UX\LiveComponent\EventListener\AddLiveAttributesSubscriber;
use Symfony\UX\LiveComponent\EventListener\LiveComponentSubscriber;
use Symfony\UX\LiveComponent\LiveComponentHydrator;
use Symfony\UX\LiveComponent\PropertyHydratorInterface;
@@ -46,6 +47,7 @@ function (ChildDefinition $definition, AsLiveComponent $attribute) {
'key' => $attribute->name,
'template' => $attribute->template,
'default_action' => $attribute->defaultAction,
+ 'live' => true,
]))
->addTag('controller.service_arguments')
;
@@ -78,6 +80,7 @@ function (ChildDefinition $definition, AsLiveComponent $attribute) {
$container->register('ux.live_component.twig.component_runtime', LiveComponentRuntime::class)
->setArguments([
+ new Reference('twig'),
new Reference('ux.live_component.component_hydrator'),
new Reference('ux.twig_component.component_factory'),
new Reference(UrlGeneratorInterface::class),
@@ -90,6 +93,11 @@ function (ChildDefinition $definition, AsLiveComponent $attribute) {
->addTag('container.service_subscriber', ['key' => 'validator', 'id' => 'validator'])
;
+ $container->register('ux.live_component.add_attributes_subscriber', AddLiveAttributesSubscriber::class)
+ ->addTag('kernel.event_subscriber')
+ ->addTag('container.service_subscriber', ['key' => LiveComponentRuntime::class, 'id' => 'ux.live_component.twig.component_runtime'])
+ ;
+
$container->setAlias(ComponentValidatorInterface::class, ComponentValidator::class);
}
}
diff --git a/src/LiveComponent/src/EventListener/AddLiveAttributesSubscriber.php b/src/LiveComponent/src/EventListener/AddLiveAttributesSubscriber.php
new file mode 100644
index 00000000000..ab7297623cc
--- /dev/null
+++ b/src/LiveComponent/src/EventListener/AddLiveAttributesSubscriber.php
@@ -0,0 +1,56 @@
+
+ */
+final class AddLiveAttributesSubscriber implements EventSubscriberInterface, ServiceSubscriberInterface
+{
+ public function __construct(private ContainerInterface $container)
+ {
+ }
+
+ public function onPreRender(PreRenderEvent $event): void
+ {
+ if (!$event->getMetadata()->get('live', false)) {
+ // not a live component, skip
+ return;
+ }
+
+ /** @var ComponentAttributes $attributes */
+ $attributes = $this->container->get(LiveComponentRuntime::class)
+ ->getLiveAttributes($event->getComponent(), $event->getMetadata())
+ ;
+
+ $variables = $event->getVariables();
+
+ if (isset($variables['attributes']) && $variables['attributes'] instanceof ComponentAttributes) {
+ // merge with existing attributes if available
+ $attributes = $attributes->defaults($variables['attributes']->all());
+ }
+
+ $variables['attributes'] = $attributes;
+
+ $event->setVariables($variables);
+ }
+
+ public static function getSubscribedEvents(): array
+ {
+ return [PreRenderEvent::class => 'onPreRender'];
+ }
+
+ public static function getSubscribedServices(): array
+ {
+ return [
+ LiveComponentRuntime::class,
+ ];
+ }
+}
diff --git a/src/LiveComponent/src/EventListener/LiveComponentSubscriber.php b/src/LiveComponent/src/EventListener/LiveComponentSubscriber.php
index 06569b083fc..dd54f30f051 100644
--- a/src/LiveComponent/src/EventListener/LiveComponentSubscriber.php
+++ b/src/LiveComponent/src/EventListener/LiveComponentSubscriber.php
@@ -31,6 +31,7 @@
use Symfony\UX\LiveComponent\Attribute\LiveArg;
use Symfony\UX\LiveComponent\LiveComponentHydrator;
use Symfony\UX\TwigComponent\ComponentFactory;
+use Symfony\UX\TwigComponent\ComponentMetadata;
use Symfony\UX\TwigComponent\ComponentRenderer;
/**
@@ -73,24 +74,28 @@ public function onKernelRequest(RequestEvent $event): void
$componentName = (string) $request->get('component');
try {
- $config = $this->container->get(ComponentFactory::class)->configFor($componentName);
+ /** @var ComponentMetadata $metadata */
+ $metadata = $this->container->get(ComponentFactory::class)->metadataFor($componentName);
} catch (\InvalidArgumentException $e) {
throw new NotFoundHttpException(sprintf('Component "%s" not found.', $componentName), $e);
}
- $request->attributes->set('_component_template', $config['template']);
+ if (!$metadata->get('live', false)) {
+ throw new NotFoundHttpException(sprintf('"%s" (%s) is not a Live Component.', $metadata->getClass(), $componentName));
+ }
+
+ $request->attributes->set('_component_metadata', $metadata);
if ('get' === $action) {
- $defaultAction = trim($config['default_action'] ?? '__invoke', '()');
- $componentClass = $config['class'];
+ $defaultAction = trim($metadata->get('default_action', '__invoke'), '()');
- if (!method_exists($componentClass, $defaultAction)) {
+ if (!method_exists($metadata->getClass(), $defaultAction)) {
// todo should this check be in a compiler pass to ensure fails at compile time?
- throw new \LogicException(sprintf('Live component "%s" requires the default action method "%s".%s', $componentClass, $defaultAction, '__invoke' === $defaultAction ? ' Either add this method or use the DefaultActionTrait' : ''));
+ throw new \LogicException(sprintf('Live component "%s" (%s) requires the default action method "%s".%s', $metadata->getClass(), $componentName, $defaultAction, '__invoke' === $defaultAction ? ' Either add this method or use the DefaultActionTrait' : ''));
}
// set default controller for "default" action
- $request->attributes->set('_controller', sprintf('%s::%s', $config['service_id'], $defaultAction));
+ $request->attributes->set('_controller', sprintf('%s::%s', $metadata->getServiceId(), $defaultAction));
$request->attributes->set('_component_default_action', true);
return;
@@ -106,7 +111,7 @@ public function onKernelRequest(RequestEvent $event): void
throw new BadRequestHttpException('Invalid CSRF token.');
}
- $request->attributes->set('_controller', sprintf('%s::%s', $config['service_id'], $action));
+ $request->attributes->set('_controller', sprintf('%s::%s', $metadata->getServiceId(), $action));
}
public function onKernelController(ControllerEvent $event): void
@@ -232,7 +237,7 @@ private function createResponse(object $component, Request $request): Response
$html = $this->container->get(ComponentRenderer::class)->render(
$component,
- $request->attributes->get('_component_template')
+ $request->attributes->get('_component_metadata')
);
return new Response($html);
diff --git a/src/LiveComponent/src/Resources/doc/index.rst b/src/LiveComponent/src/Resources/doc/index.rst
index 9ad6ac1748a..41891718cc3 100644
--- a/src/LiveComponent/src/Resources/doc/index.rst
+++ b/src/LiveComponent/src/Resources/doc/index.rst
@@ -43,10 +43,15 @@ A real-time product search component might look like this::
The ability to reference local variables in the template (e.g. ``query``) was added in TwigComponents 2.1.
Previously, all data needed to be referenced through ``this`` (e.g. ``this.query``).
+.. versionadded:: 2.1
+
+ The ability to initialize live component with the ``attributes`` twig variable was added in LiveComponents
+ 2.1. Previously, the ``init_live_component()`` function was required (this function was removed in 2.1).
+
.. code-block:: twig
{# templates/components/product_search.html.twig #}
-
+
- +
+ +
{{ this.randomNumber }}
@@ -176,7 +181,7 @@ and give the user a new random number:
.. code-block:: twig
-
+
{{ this.randomNumber }}