twigphp / Twig

Twig, the flexible, fast, and secure template language for PHP
https://twig.symfony.com/
BSD 3-Clause "New" or "Revised" License
8.15k stars 1.24k forks source link

Strange behavior with block function inside parent block #3321

Open hlecorche opened 4 years ago

hlecorche commented 4 years ago

Hello,

I found a strange behavior with all Twig versions.

{{ block('block1', 'theme.html.twig') }}`

{# theme.html.twig #}
{% extends 'base.html.twig' %}

{%- block block2 -%}
    BLOCK2THEME/
{%- endblock -%}

{# base.html.twig #}
{%- block block1 -%}
    BLOCK1BASE/
    {{- block('block2') -}}
{%- endblock -%}

{%- block block2 -%}
    BLOCK2BASE/
{%- endblock -%}

https://twigfiddle.com/jae42d

The result is the expected result : BLOCK1BASE/BLOCK2THEME/

But I have a strange result if block1 is overridden and parent() is called :

{{ block('block1', 'theme.html.twig') }}`

{# theme.html.twig #}
{% extends 'base.html.twig' %}

{%- block block1 -%}
    {{- parent() -}}
    BLOCK1THEME/
{%- endblock -%}

{%- block block2 -%}
    BLOCK2THEME/
{%- endblock -%}

{# base.html.twig #}
{%- block block1 -%}
    BLOCK1BASE/
    {{- block('block2') -}}
{%- endblock -%}

{%- block block2 -%}
    BLOCK2BASE/
{%- endblock -%}

https://twigfiddle.com/jae42d/2

Expected result : BLOCK1BASE/BLOCK2THEME/BLOCK1THEME/ Result: BLOCK1BASE/BLOCK2BASE/BLOCK1THEME/

SanderVerkuil commented 4 years ago

I'm having a similar issue, so I'm interested into why there is a difference between including the template, and rendering a block from a template.

Defining the template as such:

{# main.html.twig #}
{{ block('block1', 'theme.html.twig') }}

{% include 'theme.html.twig' %}

{# theme.html.twig #}
{% extends 'base.html.twig' %}

{%- block block1 -%}
    {{- parent() -}}
    BLOCK1THEME/
{%- endblock -%}

{%- block block2 -%}
    BLOCK2THEME/
{%- endblock -%}

{# base.html.twig #}
{%- block block1 -%}
    BLOCK1BASE/

    {%- block block2 -%}
        BLOCK2BASE/
    {%- endblock -%}
{%- endblock -%}

https://twigfiddle.com/jae42d/3

I'd expect the outputs to be the same.

Looking at the compiled templates, including a block from another template displays the block from the parent.

It appears however that when rendering a block in a template with the block() function, the inheritance is lost when displaying the block.

Is there a reason that the inheritance works differently when including a template vs rendering a specific block from a template?

edit: When debugging the templates, I noticed that when including theme.html.twig the parent is set, but when calling block('block1', 'theme.html.twig') the parent is null.

The parent is set in the doDisplay call, when the extends statement is evaluated. When displaying a block the doDisplay is not called, and the parent is not loaded. It is not possible to call the extends in a block, so it is not possible to set the parent inside a block, which I'm sure has a good reason to not allow.

chapterjason commented 2 years ago

I came across the same issue, I think that is one of the reasons why the symfony form component has it's own renderer.

I have a small workaround, the only limit is that you always have to passthrough the template name. I the way my usage is, I always know the template.

    public function renderMenuBlock(Environment $environment, array $context, string $blockName, ?string $templateName = null): string
    {
        $templateName = $templateName ?? $this->defaultOptions['template'];
        $template = $environment->resolveTemplate($templateName);

        ob_start();

        $template->displayBlock($blockName, $context);

        return ob_get_clean();
    }