Skip to content

[TwigComponents] Fixing inconsistency with how {% component rendered vs {{ component() #1247

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,17 @@ public function onPostMount(PostMountEvent $event): void
$data = $event->getData();
if (\array_key_exists('defer', $data)) {
$event->addExtraMetadata('defer', true);
unset($event->getData()['defer']);
unset($data['defer']);
}

if (\array_key_exists('loading-template', $data)) {
$event->addExtraMetadata('loading-template', $data['loading-template']);
unset($event->getData()['loading-template']);
unset($data['loading-template']);
}

if (\array_key_exists('loading-tag', $data)) {
$event->addExtraMetadata('loading-tag', $data['loading-tag']);
unset($event->getData()['loading-tag']);
unset($data['loading-tag']);
}

$event->setData($data);
Expand Down
21 changes: 11 additions & 10 deletions src/TwigComponent/src/ComponentRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,16 +64,23 @@ public function render(MountedComponent $mounted): string

$event = $this->preRender($mounted);

$variables = $event->getVariables();
// see ComponentNode. When rendering an individual embedded component,
// *not* through its parent, we need to set the parent template.
if ($event->getTemplateIndex()) {
$variables['__parent__'] = $event->getParentTemplateForEmbedded();
}

try {
if ($this->twig::MAJOR_VERSION < 3) {
return $this->twig->loadTemplate($event->getTemplate(), $event->getTemplateIndex())->render($event->getVariables());
return $this->twig->loadTemplate($event->getTemplate(), $event->getTemplateIndex())->render($variables);
}

return $this->twig->loadTemplate(
$this->twig->getTemplateClass($event->getTemplate()),
$event->getTemplate(),
$event->getTemplateIndex(),
)->render($event->getVariables());
)->render($variables);
} finally {
$mounted = $this->componentStack->pop();

Expand All @@ -82,7 +89,7 @@ public function render(MountedComponent $mounted): string
}
}

public function embeddedContext(string $name, array $props, array $context, string $hostTemplateName, int $index): array
public function startEmbeddedComponentRender(string $name, array $props, array $context, string $hostTemplateName, int $index): PreRenderEvent
{
$context[PreRenderEvent::EMBEDDED] = true;

Expand All @@ -92,13 +99,7 @@ public function embeddedContext(string $name, array $props, array $context, stri

$this->componentStack->push($mounted);

$embeddedContext = $this->preRender($mounted, $context)->getVariables();

if (!isset($embeddedContext['outerBlocks'])) {
$embeddedContext['outerBlocks'] = new BlockStack();
}

return $embeddedContext;
return $this->preRender($mounted, $context);
}

public function finishEmbeddedComponentRender(): void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ class_exists(AbstractArgument::class) ? new AbstractArgument(sprintf('Added in %
$container->register('ux.twig_component.twig.component_extension', ComponentExtension::class)
->addTag('twig.extension')
->addTag('container.service_subscriber', ['key' => ComponentRenderer::class, 'id' => 'ux.twig_component.component_renderer'])
->addTag('container.service_subscriber', ['key' => ComponentFactory::class, 'id' => 'ux.twig_component.component_factory'])
;

$container->register('ux.twig_component.twig.lexer', ComponentLexer::class);
Expand Down
17 changes: 16 additions & 1 deletion src/TwigComponent/src/Event/PreRenderEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,13 @@ final class PreRenderEvent extends Event
/** @internal */
public const EMBEDDED = '__embedded';

/**
* Only relevant when rendering a specific embedded component.
* This is the "component template" that the embedded component
* should extend.
*/
private string $parentTemplateForEmbedded;
private string $template;

private ?int $templateIndex = null;

/**
Expand All @@ -36,6 +41,7 @@ public function __construct(
private array $variables
) {
$this->template = $this->metadata->getTemplate();
$this->parentTemplateForEmbedded = $this->template;
}

public function isEmbedded(): bool
Expand All @@ -58,6 +64,10 @@ public function setTemplate(string $template, int $index = null): self
{
$this->template = $template;
$this->templateIndex = $index;
// only if we are *not* targeting an embedded component, change the parent template
if (null === $index) {
$this->parentTemplateForEmbedded = $template;
}

return $this;
}
Expand All @@ -70,6 +80,11 @@ public function getTemplateIndex(): ?int
return $this->templateIndex;
}

public function getParentTemplateForEmbedded(): string
{
return $this->parentTemplateForEmbedded;
}

public function getComponent(): object
{
return $this->mounted->getComponent();
Expand Down
11 changes: 5 additions & 6 deletions src/TwigComponent/src/Twig/ComponentExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@

use Psr\Container\ContainerInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
use Symfony\UX\TwigComponent\ComponentFactory;
use Symfony\UX\TwigComponent\ComponentRenderer;
use Symfony\UX\TwigComponent\Event\PreRenderEvent;
use Twig\Error\RuntimeError;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
Expand All @@ -34,7 +34,6 @@ public static function getSubscribedServices(): array
{
return [
ComponentRenderer::class,
ComponentFactory::class,
];
}

Expand All @@ -48,7 +47,7 @@ public function getFunctions(): array
public function getTokenParsers(): array
{
return [
new ComponentTokenParser(fn () => $this->container->get(ComponentFactory::class)),
new ComponentTokenParser(),
new PropsTokenParser(),
];
}
Expand All @@ -62,7 +61,7 @@ public function render(string $name, array $props = []): string
}
}

public function preRender(string $name, array $props): ?string
public function extensionPreCreateForRender(string $name, array $props): ?string
{
try {
return $this->container->get(ComponentRenderer::class)->preCreateForRender($name, $props);
Expand All @@ -71,10 +70,10 @@ public function preRender(string $name, array $props): ?string
}
}

public function embeddedContext(string $name, array $props, array $context, string $hostTemplateName, int $index): array
public function startEmbeddedComponentRender(string $name, array $props, array $context, string $hostTemplateName, int $index): PreRenderEvent
{
try {
return $this->container->get(ComponentRenderer::class)->embeddedContext($name, $props, $context, $hostTemplateName, $index);
return $this->container->get(ComponentRenderer::class)->startEmbeddedComponentRender($name, $props, $context, $hostTemplateName, $index);
} catch (\Throwable $e) {
$this->throwRuntimeError($name, $e);
}
Expand Down
108 changes: 93 additions & 15 deletions src/TwigComponent/src/Twig/ComponentNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,37 +11,53 @@

namespace Symfony\UX\TwigComponent\Twig;

use Symfony\UX\TwigComponent\BlockStack;
use Twig\Compiler;
use Twig\Node\EmbedNode;
use Twig\Node\Expression\AbstractExpression;
use Twig\Node\Node;

/**
* @author Fabien Potencier <fabien@symfony.com>
* @author Kevin Bond <kevinbond@gmail.com>
*
* @internal
*/
final class ComponentNode extends EmbedNode
final class ComponentNode extends Node
{
public function __construct(string $component, string $template, int $index, AbstractExpression $variables, bool $only, int $lineno, string $tag)
public function __construct(string $component, string $embeddedTemplateName, int $embeddedTemplateIndex, ?AbstractExpression $props, bool $only, int $lineno, string $tag)
{
parent::__construct($template, $index, $variables, $only, false, $lineno, $tag);
$nodes = [];
if (null !== $props) {
$nodes['props'] = $props;
}

parent::__construct($nodes, [], $lineno, $tag);

$this->setAttribute('only', $only);
$this->setAttribute('embedded_template', $embeddedTemplateName);
$this->setAttribute('embedded_index', $embeddedTemplateIndex);
$this->setAttribute('component', $component);
}

public function compile(Compiler $compiler): void
{
$compiler->addDebugInfo($this);

/*
* Block 1) PreCreateForRender handling
*
* We call code to trigger the PreCreateForRender event. If the event returns
* a string, we return that string and skip the rest of the rendering process.
*/
$compiler
->write('$preRendered = $this->extensions[')
->string(ComponentExtension::class)
->raw(']->preRender(')
->raw(']->extensionPreCreateForRender(')
->string($this->getAttribute('component'))
->raw(', ')
->raw('twig_to_array(')
->subcompile($this->getNode('variables'))
;
$this->writeProps($compiler)
->raw(')')
->raw(");\n")
;
Expand All @@ -58,32 +74,85 @@ public function compile(Compiler $compiler): void
->indent()
;

/*
* Block 2) Create the component & return render info
*
* We call code that creates the component and dispatches the
* PreRender event. The result $preRenderEvent variable holds
* the final template, template index & variables.
*/
$compiler
->write('$embeddedContext = $this->extensions[')
->write('$preRenderEvent = $this->extensions[')
->string(ComponentExtension::class)
->raw(']->embeddedContext(')
->raw(']->startEmbeddedComponentRender(')
->string($this->getAttribute('component'))
->raw(', twig_to_array(')
->subcompile($this->getNode('variables'))
;
$this->writeProps($compiler)
->raw('), ')
->raw($this->getAttribute('only') ? '[]' : '$context')
->raw(', ')
->string(TemplateNameParser::parse($this->getAttribute('name')))
->string(TemplateNameParser::parse($this->getAttribute('embedded_template')))
->raw(', ')
->raw($this->getAttribute('index'))
->raw($this->getAttribute('embedded_index'))
->raw(");\n")
;
$compiler
->write('$embeddedContext = $preRenderEvent->getVariables();')
->raw("\n")
// Add __parent__ to the embedded context: this is used in its extends
// Note: PreRenderEvent::getTemplateIndex() is not used here. This is
// only used during "normal" {{ component() }} rendering, which allows
// you to target rendering a specific "embedded template" that originally
// came from a {% component %} tag. This is used by LiveComponents to
// allow an "embedded component" syntax live component to be re-rendered.
// In this case, we are obviously rendering an entire template, which
// happens to contain a {% component %} tag. So we don't need to worry
// about trying to allow a specific embedded template to be targeted.
->write('$embeddedContext["__parent__"] = $preRenderEvent->getTemplate();')
->raw("\n")
;

/*
* Block 3) Add & update the block stack
*
* We add the outerBlock to the context if it doesn't exist yet.
* Then add them to the block stack and get the converted embedded blocks.
*/
$compiler->write('if (!isset($embeddedContext["outerBlocks"])) {')
->raw("\n")
->indent()
->write(sprintf('$embeddedContext["outerBlocks"] = new \%s();', BlockStack::class))
->raw("\n")
->outdent()
->write('}')
->raw("\n");

$compiler->write('$embeddedBlocks = $embeddedContext[')
->string('outerBlocks')
->raw(']->convert($blocks, ')
->raw($this->getAttribute('index'))
->raw($this->getAttribute('embedded_index'))
->raw(");\n")
;

$this->addGetTemplate($compiler);
$compiler->raw('->display($embeddedContext, $embeddedBlocks);');
$compiler->raw("\n");
/*
* Block 4) Render the component template
*
* This will actually render the child component template.
*/
$compiler
->write('$this->loadTemplate(')
->string($this->getAttribute('embedded_template'))
->raw(', ')
->repr($this->getTemplateName())
->raw(', ')
->repr($this->getTemplateLine())
->raw(', ')
->string($this->getAttribute('embedded_index'))
->raw(')')
->raw('->display($embeddedContext, $embeddedBlocks);')
->raw("\n")
;

$compiler->write('$this->extensions[')
->string(ComponentExtension::class)
Expand All @@ -97,4 +166,13 @@ public function compile(Compiler $compiler): void
->raw("\n")
;
}

private function writeProps(Compiler $compiler): Compiler
{
if ($this->hasNode('props')) {
return $compiler->subcompile($this->getNode('props'));
}

return $compiler->raw('[]');
}
}
Loading