vendor/symfony/ux-twig-component/src/ComponentRenderer.php line 77

Open in your IDE?
  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\TwigComponent;
  11. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  12. use Symfony\Contracts\Service\ResetInterface;
  13. use Symfony\UX\TwigComponent\Event\PostRenderEvent;
  14. use Symfony\UX\TwigComponent\Event\PreCreateForRenderEvent;
  15. use Symfony\UX\TwigComponent\Event\PreRenderEvent;
  16. use Twig\Environment;
  17. /**
  18. * @author Kevin Bond <kevinbond@gmail.com>
  19. *
  20. * @internal
  21. */
  22. final class ComponentRenderer implements ComponentRendererInterface, ResetInterface
  23. {
  24. private array $templateClasses = [];
  25. public function __construct(
  26. private Environment $twig,
  27. private EventDispatcherInterface $dispatcher,
  28. private ComponentFactory $factory,
  29. private ComponentProperties $componentProperties,
  30. private ComponentStack $componentStack,
  31. ) {
  32. }
  33. /**
  34. * Allow the render process to be short-circuited.
  35. */
  36. public function preCreateForRender(string $name, array $props = []): ?string
  37. {
  38. $event = new PreCreateForRenderEvent($name, $props);
  39. $this->dispatcher->dispatch($event);
  40. return $event->getRenderedString();
  41. }
  42. public function createAndRender(string $name, array $props = []): string
  43. {
  44. if ($preRendered = $this->preCreateForRender($name, $props)) {
  45. return $preRendered;
  46. }
  47. return $this->render($this->factory->create($name, $props));
  48. }
  49. public function render(MountedComponent $mounted): string
  50. {
  51. $this->componentStack->push($mounted);
  52. $event = $this->preRender($mounted);
  53. $variables = $event->getVariables();
  54. // see ComponentNode. When rendering an individual embedded component,
  55. // *not* through its parent, we need to set the parent template.
  56. if ($templateIndex = $event->getTemplateIndex()) {
  57. $variables['__parent__'] = $event->getParentTemplateForEmbedded();
  58. }
  59. try {
  60. return $this->twig->loadTemplate(
  61. $this->templateClasses[$template = $event->getTemplate()] ??= $this->twig->getTemplateClass($template),
  62. $template,
  63. $templateIndex,
  64. )->render($variables);
  65. } finally {
  66. $mounted = $this->componentStack->pop();
  67. $event = new PostRenderEvent($mounted);
  68. $this->dispatcher->dispatch($event);
  69. }
  70. }
  71. public function startEmbeddedComponentRender(string $name, array $props, array $context, string $hostTemplateName, int $index): PreRenderEvent
  72. {
  73. $context[PreRenderEvent::EMBEDDED] = true;
  74. $mounted = $this->factory->create($name, $props);
  75. $mounted->addExtraMetadata('hostTemplate', $hostTemplateName);
  76. $mounted->addExtraMetadata('embeddedTemplateIndex', $index);
  77. $this->componentStack->push($mounted);
  78. return $this->preRender($mounted, $context);
  79. }
  80. public function finishEmbeddedComponentRender(): void
  81. {
  82. $mounted = $this->componentStack->pop();
  83. $event = new PostRenderEvent($mounted);
  84. $this->dispatcher->dispatch($event);
  85. }
  86. private function preRender(MountedComponent $mounted, array $context = []): PreRenderEvent
  87. {
  88. $component = $mounted->getComponent();
  89. $metadata = $this->factory->metadataFor($mounted->getName());
  90. $classProps = [];
  91. if (!$metadata->isAnonymous()) {
  92. $classProps = $this->componentProperties->getProperties($component, $metadata->isPublicPropsExposed());
  93. }
  94. // expose public properties and properties marked with ExposeInTemplate attribute
  95. $props = [...$mounted->getInputProps(), ...$classProps];
  96. $event = new PreRenderEvent($mounted, $metadata, [
  97. ...$context,
  98. ...$props,
  99. $metadata->getAttributesVar() => $mounted->getAttributes(),
  100. ]);
  101. $this->dispatcher->dispatch($event);
  102. $event->setVariables([
  103. ...$event->getVariables(),
  104. // add the component as "this"
  105. 'this' => $component,
  106. 'computed' => new ComputedPropertiesProxy($component),
  107. 'outerScope' => $context,
  108. // keep this line for BC break reasons
  109. '__props' => $classProps,
  110. // add the context in a separate variable to keep track
  111. // of what is coming from outside the component, excluding props
  112. // as they override initial context values
  113. '__context' => array_diff_key($context, $props),
  114. ]);
  115. return $event;
  116. }
  117. public function reset(): void
  118. {
  119. $this->templateClasses = [];
  120. }
  121. }