vendor/symfony/ux-live-component/src/EventListener/AddLiveAttributesSubscriber.php line 44

  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <fabien@symfony.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Symfony\UX\LiveComponent\EventListener;
  11. use Psr\Container\ContainerInterface;
  12. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  13. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  14. use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
  15. use Symfony\Contracts\Service\ServiceSubscriberInterface;
  16. use Symfony\UX\LiveComponent\DehydratedComponent;
  17. use Symfony\UX\LiveComponent\LiveComponentHydrator;
  18. use Symfony\UX\LiveComponent\Twig\DeterministicTwigIdCalculator;
  19. use Symfony\UX\LiveComponent\Util\FingerprintCalculator;
  20. use Symfony\UX\LiveComponent\Util\JsonUtil;
  21. use Symfony\UX\LiveComponent\Util\TwigAttributeHelper;
  22. use Symfony\UX\TwigComponent\ComponentAttributes;
  23. use Symfony\UX\TwigComponent\ComponentMetadata;
  24. use Symfony\UX\TwigComponent\ComponentStack;
  25. use Symfony\UX\TwigComponent\Event\PreRenderEvent;
  26. use Symfony\UX\TwigComponent\MountedComponent;
  27. /**
  28.  * @author Kevin Bond <kevinbond@gmail.com>
  29.  *
  30.  * @experimental
  31.  *
  32.  * @internal
  33.  */
  34. final class AddLiveAttributesSubscriber implements EventSubscriberInterfaceServiceSubscriberInterface
  35. {
  36.     public function __construct(private ContainerInterface $container)
  37.     {
  38.     }
  39.     public function onPreRender(PreRenderEvent $event): void
  40.     {
  41.         if (!$event->getMetadata()->get('live'false)) {
  42.             // not a live component, skip
  43.             return;
  44.         }
  45.         if (method_exists($event'isEmbedded') && $event->isEmbedded()) {
  46.             // TODO: remove method_exists once min ux-twig-component version has this method
  47.             throw new \LogicException('Embedded components cannot be live.');
  48.         }
  49.         $metadata $event->getMetadata();
  50.         $attributes $this->getLiveAttributes($event->getMountedComponent(), $metadata);
  51.         $variables $event->getVariables();
  52.         $attributesKey $metadata->getAttributesVar();
  53.         // the original ComponentAttributes have already been processed and set
  54.         // onto the variables. So, we manually merge our new attributes in and
  55.         // override that variable.
  56.         if (isset($variables[$attributesKey]) && $variables[$attributesKey] instanceof ComponentAttributes) {
  57.             // merge with existing attributes if available
  58.             $attributes $attributes->defaults($variables[$attributesKey]->all());
  59.         }
  60.         $variables[$attributesKey] = $attributes;
  61.         $event->setVariables($variables);
  62.     }
  63.     public static function getSubscribedEvents(): array
  64.     {
  65.         return [PreRenderEvent::class => 'onPreRender'];
  66.     }
  67.     public static function getSubscribedServices(): array
  68.     {
  69.         return [
  70.             LiveComponentHydrator::class,
  71.             UrlGeneratorInterface::class,
  72.             TwigAttributeHelper::class,
  73.             ComponentStack::class,
  74.             DeterministicTwigIdCalculator::class,
  75.             FingerprintCalculator::class,
  76.             '?'.CsrfTokenManagerInterface::class,
  77.         ];
  78.     }
  79.     private function getLiveAttributes(MountedComponent $mountedComponentMetadata $metadata): ComponentAttributes
  80.     {
  81.         $name $mounted->getName();
  82.         $url $this->container->get(UrlGeneratorInterface::class)->generate('ux_live_component', ['component' => $name]);
  83.         /** @var DehydratedComponent $dehydratedComponent */
  84.         $dehydratedComponent $this->container->get(LiveComponentHydrator::class)->dehydrate($mounted);
  85.         /** @var TwigAttributeHelper $helper */
  86.         $helper $this->container->get(TwigAttributeHelper::class);
  87.         $attributes = [
  88.             'data-controller' => 'live',
  89.             'data-live-url-value' => $helper->escapeAttribute($url),
  90.             'data-live-data-value' => $helper->escapeAttribute(JsonUtil::encodeObject($dehydratedComponent->getData())),
  91.             'data-live-props-value' => $helper->escapeAttribute(JsonUtil::encodeObject($dehydratedComponent->getProps())),
  92.         ];
  93.         if ($this->container->has(CsrfTokenManagerInterface::class) && $metadata->get('csrf')) {
  94.             $attributes['data-live-csrf-value'] = $this->container->get(CsrfTokenManagerInterface::class)
  95.                 ->getToken($name)->getValue()
  96.             ;
  97.         }
  98.         if ($this->container->get(ComponentStack::class)->hasParentComponent()) {
  99.             $id $this->container->get(DeterministicTwigIdCalculator::class)->calculateDeterministicId();
  100.             $attributes['data-live-id'] = $helper->escapeAttribute($id);
  101.             $fingerprint $this->container->get(FingerprintCalculator::class)->calculateFingerprint($mounted->getInputProps());
  102.             $attributes['data-live-value-fingerprint'] = $helper->escapeAttribute($fingerprint);
  103.         }
  104.         return new ComponentAttributes($attributes);
  105.     }
  106. }