mirror of
https://github.com/twigphp/Twig.git
synced 2026-09-12 18:36:53 +00:00
Add the include_only function to render a template without access to the current context
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
# 3.29.0 (2026-XX-XX)
|
# 3.29.0 (2026-XX-XX)
|
||||||
|
|
||||||
* Fix imported macros not resolving their own template-level macro imports
|
* Fix imported macros not resolving their own template-level macro imports
|
||||||
|
* Add the `include_only` function to render a template without giving it access to the current context
|
||||||
* Add the `Twig\Sandbox\SandboxInterface` interface and `Twig\Sandbox\Sandbox` class to render untrusted templates through a dedicated, always-sandboxed environment crafted for it
|
* Add the `Twig\Sandbox\SandboxInterface` interface and `Twig\Sandbox\Sandbox` class to render untrusted templates through a dedicated, always-sandboxed environment crafted for it
|
||||||
* Add the `Twig\Extension\SandboxBridgeExtension` to render sandboxed templates from trusted templates with an explicit output escaping strategy
|
* Add the `Twig\Extension\SandboxBridgeExtension` to render sandboxed templates from trusted templates with an explicit output escaping strategy
|
||||||
* Extract the sandbox runtime enforcement into a new internal `Twig\Sandbox\SecurityChecker` class used by compiled templates and `CoreExtension`
|
* Extract the sandbox runtime enforcement into a new internal `Twig\Sandbox\SecurityChecker` class used by compiled templates and `CoreExtension`
|
||||||
|
|||||||
+13
-48
@@ -8,29 +8,25 @@ The ``include`` function returns the rendered content of a template:
|
|||||||
{{ include('template.html.twig') }}
|
{{ include('template.html.twig') }}
|
||||||
{{ include(some_var) }}
|
{{ include(some_var) }}
|
||||||
|
|
||||||
The returned content is a ``\Twig\Markup`` instance, so it is considered safe
|
|
||||||
and is not escaped again when you store it in a variable and print it later:
|
|
||||||
|
|
||||||
.. code-block:: twig
|
|
||||||
|
|
||||||
{% set body = include('body.html.twig') %}
|
|
||||||
{{ body }} {# rendered as-is, not re-escaped #}
|
|
||||||
|
|
||||||
Beware that, like any safe value, it is not re-escaped for the context it ends
|
|
||||||
up in, so only embed it in the same context it was rendered for (typically
|
|
||||||
HTML).
|
|
||||||
|
|
||||||
Included templates have access to the variables of the active context.
|
Included templates have access to the variables of the active context.
|
||||||
|
|
||||||
If you are using the filesystem loader, the templates are looked for in the
|
.. tip::
|
||||||
paths defined by it.
|
|
||||||
|
|
||||||
The context is passed by default to the template but you can also pass
|
Prefer the :doc:`include_only() function </functions/include_only>` when
|
||||||
|
you can. Sharing the whole context lets a template silently rely on
|
||||||
|
variables defined by the caller, which hides its real inputs and couples it
|
||||||
|
to wherever it is included from. ``include_only`` takes only the variables
|
||||||
|
you pass, making the data flow explicit and partials easier to reuse.
|
||||||
|
|
||||||
|
Its documentation also covers the template loading, ``ignore_missing`` and
|
||||||
|
return-value behavior shared by both functions.
|
||||||
|
|
||||||
|
The current context is passed by default to the template but you can also pass
|
||||||
additional variables:
|
additional variables:
|
||||||
|
|
||||||
.. code-block:: twig
|
.. code-block:: twig
|
||||||
|
|
||||||
{# template.html.twig will have access to the variables from the current context and the additional ones provided #}
|
{# The included template can access "name" and the current context. #}
|
||||||
{{ include('template.html.twig', {name: 'Fabien'}) }}
|
{{ include('template.html.twig', {name: 'Fabien'}) }}
|
||||||
|
|
||||||
You can disable access to the context by setting ``with_context`` to
|
You can disable access to the context by setting ``with_context`` to
|
||||||
@@ -38,40 +34,9 @@ You can disable access to the context by setting ``with_context`` to
|
|||||||
|
|
||||||
.. code-block:: twig
|
.. code-block:: twig
|
||||||
|
|
||||||
{# only the name variable will be accessible #}
|
{# Only the "name" variable will be accessible. #}
|
||||||
{{ include('template.html.twig', {name: 'Fabien'}, with_context: false) }}
|
{{ include('template.html.twig', {name: 'Fabien'}, with_context: false) }}
|
||||||
|
|
||||||
.. code-block:: twig
|
|
||||||
|
|
||||||
{# no variables will be accessible #}
|
|
||||||
{{ include('template.html.twig', with_context: false) }}
|
|
||||||
|
|
||||||
And if the expression evaluates to a ``\Twig\Template`` or a
|
|
||||||
``\Twig\TemplateWrapper`` instance, Twig will use it directly::
|
|
||||||
|
|
||||||
// {{ include(template) }}
|
|
||||||
|
|
||||||
$template = $twig->load('some_template.html.twig');
|
|
||||||
|
|
||||||
$twig->display('template.html.twig', ['template' => $template]);
|
|
||||||
|
|
||||||
When you set the ``ignore_missing`` flag, Twig will return an empty string if
|
|
||||||
the template does not exist:
|
|
||||||
|
|
||||||
.. code-block:: twig
|
|
||||||
|
|
||||||
{{ include('sidebar.html.twig', ignore_missing: true) }}
|
|
||||||
|
|
||||||
You can also provide a list of templates that are checked for existence before
|
|
||||||
inclusion. The first template that exists will be rendered:
|
|
||||||
|
|
||||||
.. code-block:: twig
|
|
||||||
|
|
||||||
{{ include(['page_detailed.html.twig', 'page.html.twig']) }}
|
|
||||||
|
|
||||||
If ``ignore_missing`` is set, it will fall back to rendering nothing if none
|
|
||||||
of the templates exist, otherwise it will throw an exception.
|
|
||||||
|
|
||||||
When including a template created by an end user, you should
|
When including a template created by an end user, you should
|
||||||
:doc:`sandbox<../sandbox>` it.
|
:doc:`sandbox<../sandbox>` it.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
``include_only``
|
||||||
|
================
|
||||||
|
|
||||||
|
.. versionadded:: 3.29
|
||||||
|
|
||||||
|
The ``include_only`` function was added in Twig 3.29.
|
||||||
|
|
||||||
|
The ``include_only`` function returns the rendered content of a template
|
||||||
|
without giving it access to the current context:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ include_only('template.html.twig') }}
|
||||||
|
{{ include_only(some_var) }}
|
||||||
|
|
||||||
|
Variables from the active context are not passed implicitly. This makes the
|
||||||
|
data a template relies on explicit, which is often clearer and easier to
|
||||||
|
reason about.
|
||||||
|
|
||||||
|
Returned Value
|
||||||
|
--------------
|
||||||
|
|
||||||
|
The returned content is a ``\Twig\Markup`` instance, so it is considered safe
|
||||||
|
and is not escaped again when you store it in a variable and print it later:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{% set body = include_only('body.html.twig') %}
|
||||||
|
{{ body }} {# rendered as-is, not re-escaped #}
|
||||||
|
|
||||||
|
Beware that, like any safe value, it is not re-escaped for the context it ends
|
||||||
|
up in, so only embed it in the same context it was rendered for (typically
|
||||||
|
HTML).
|
||||||
|
|
||||||
|
Passing Variables
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
As the context is not passed, variables a template needs must be passed
|
||||||
|
explicitly:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{# template.html.twig will only have access to the "name" variable #}
|
||||||
|
{{ include_only('template.html.twig', {name: 'Fabien'}) }}
|
||||||
|
|
||||||
|
When passing a variable from the current context, you can use the following
|
||||||
|
shortcut:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ include_only('template.html.twig', {name, email}) }}
|
||||||
|
|
||||||
|
{# is equivalent to #}
|
||||||
|
|
||||||
|
{{ include_only('template.html.twig', {name: name, email: email}) }}
|
||||||
|
|
||||||
|
Loading Templates
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
If you are using the filesystem loader, the templates are looked for in the
|
||||||
|
paths defined by it.
|
||||||
|
|
||||||
|
If the expression evaluates to a ``\Twig\TemplateWrapper`` instance, Twig
|
||||||
|
will use it directly::
|
||||||
|
|
||||||
|
// {{ include_only(template) }}
|
||||||
|
|
||||||
|
$template = $twig->load('some_template.html.twig');
|
||||||
|
|
||||||
|
$twig->display('template.html.twig', ['template' => $template]);
|
||||||
|
|
||||||
|
When you set the ``ignore_missing`` flag, Twig will return an empty string if
|
||||||
|
the template does not exist:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ include_only('sidebar.html.twig', ignore_missing: true) }}
|
||||||
|
|
||||||
|
You can also provide a list of templates that are checked for existence before
|
||||||
|
inclusion. The first template that exists will be rendered:
|
||||||
|
|
||||||
|
.. code-block:: twig
|
||||||
|
|
||||||
|
{{ include_only(['page_detailed.html.twig', 'page.html.twig']) }}
|
||||||
|
|
||||||
|
If ``ignore_missing`` is set, it will fall back to rendering nothing if none
|
||||||
|
of the templates exist, otherwise it will throw an exception.
|
||||||
|
|
||||||
|
To render a template created by an end user, use the
|
||||||
|
:doc:`render_sandboxed() function </functions/render_sandboxed>`.
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
---------
|
||||||
|
|
||||||
|
* ``template``: The template to render
|
||||||
|
* ``variables``: The variables to pass to the template
|
||||||
|
* ``ignore_missing``: Whether to ignore missing templates or not
|
||||||
@@ -16,6 +16,7 @@ Functions
|
|||||||
html_classes
|
html_classes
|
||||||
html_cva
|
html_cva
|
||||||
include
|
include
|
||||||
|
include_only
|
||||||
max
|
max
|
||||||
min
|
min
|
||||||
parent
|
parent
|
||||||
|
|||||||
@@ -303,6 +303,7 @@ final class CoreExtension extends AbstractExtension
|
|||||||
new TwigFunction('random', [self::class, 'random'], ['needs_charset' => true]),
|
new TwigFunction('random', [self::class, 'random'], ['needs_charset' => true]),
|
||||||
new TwigFunction('date', [$this, 'convertDate']),
|
new TwigFunction('date', [$this, 'convertDate']),
|
||||||
new TwigFunction('include', [self::class, 'include'], ['needs_environment' => true, 'needs_context' => true, 'is_safe' => ['all']]),
|
new TwigFunction('include', [self::class, 'include'], ['needs_environment' => true, 'needs_context' => true, 'is_safe' => ['all']]),
|
||||||
|
new TwigFunction('include_only', [self::class, 'includeOnly'], ['needs_environment' => true, 'is_safe' => ['all']]),
|
||||||
new TwigFunction('source', [self::class, 'source'], ['needs_environment' => true, 'is_safe' => ['all']]),
|
new TwigFunction('source', [self::class, 'source'], ['needs_environment' => true, 'is_safe' => ['all']]),
|
||||||
new TwigFunction('enum_cases', [self::class, 'enumCases'], ['node_class' => EnumCasesFunction::class]),
|
new TwigFunction('enum_cases', [self::class, 'enumCases'], ['node_class' => EnumCasesFunction::class]),
|
||||||
new TwigFunction('enum', [self::class, 'enum'], ['node_class' => EnumFunction::class]),
|
new TwigFunction('enum', [self::class, 'enum'], ['node_class' => EnumFunction::class]),
|
||||||
@@ -1532,6 +1533,22 @@ final class CoreExtension extends AbstractExtension
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a template without giving it access to the current context.
|
||||||
|
*
|
||||||
|
* @param string|array<string|TemplateWrapper>|TemplateWrapper $template The template to render or an array of templates to try consecutively
|
||||||
|
* @param array<string, mixed> $variables The variables to pass to the template
|
||||||
|
* @param bool $ignoreMissing Whether to ignore missing templates or not
|
||||||
|
*
|
||||||
|
* @return string|Markup
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
public static function includeOnly(Environment $env, $template, array $variables = [], bool $ignoreMissing = false)
|
||||||
|
{
|
||||||
|
return self::include($env, [], $template, $variables, false, $ignoreMissing);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a template content without rendering it.
|
* Returns a template content without rendering it.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
--TEST--
|
||||||
|
"include_only" function returns Markup so an assigned result is not re-escaped
|
||||||
|
--TEMPLATE--
|
||||||
|
{% set assigned = include_only("included.twig") %}[{{ assigned }}]
|
||||||
|
--TEMPLATE(included.twig)--
|
||||||
|
{{- "a & b"|escape -}}
|
||||||
|
--DATA--
|
||||||
|
return []
|
||||||
|
--EXPECT--
|
||||||
|
[a & b]
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
--TEST--
|
||||||
|
"include_only" function
|
||||||
|
--TEMPLATE--
|
||||||
|
FOO
|
||||||
|
{{ include_only("foo.twig") }}
|
||||||
|
|
||||||
|
BAR
|
||||||
|
--TEMPLATE(foo.twig)--
|
||||||
|
FOOBAR
|
||||||
|
--DATA--
|
||||||
|
return []
|
||||||
|
--EXPECT--
|
||||||
|
FOO
|
||||||
|
|
||||||
|
FOOBAR
|
||||||
|
|
||||||
|
BAR
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
--TEST--
|
||||||
|
"include_only" function ignores missing templates
|
||||||
|
--TEMPLATE--
|
||||||
|
{{ include_only(["foo.twig", "bar.twig"], ignore_missing = true) }}
|
||||||
|
{{ include_only("foo.twig", ignore_missing = true) }}
|
||||||
|
{{ include_only("foo.twig", ignore_missing = true, variables = {}) }}
|
||||||
|
--DATA--
|
||||||
|
return []
|
||||||
|
--EXPECT--
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
--TEST--
|
||||||
|
"include_only" function does not give access to the current context
|
||||||
|
--TEMPLATE--
|
||||||
|
{{ include_only("foo.twig") }}
|
||||||
|
--TEMPLATE(foo.twig)--
|
||||||
|
[{{ foo|default('undefined') }}]
|
||||||
|
--DATA--
|
||||||
|
return ['foo' => 'bar']
|
||||||
|
--EXPECT--
|
||||||
|
[undefined]
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
--TEST--
|
||||||
|
"include_only" function accepts variables
|
||||||
|
--TEMPLATE--
|
||||||
|
{{ include_only("foo.twig", {'foo': 'bar'}) }}
|
||||||
|
{{- include_only("foo.twig", vars) }}
|
||||||
|
--TEMPLATE(foo.twig)--
|
||||||
|
{{ foo }}
|
||||||
|
--DATA--
|
||||||
|
return ['vars' => ['foo' => 'bar']]
|
||||||
|
--EXPECT--
|
||||||
|
bar
|
||||||
|
bar
|
||||||
Reference in New Issue
Block a user