mirror of
https://github.com/twigphp/Twig.git
synced 2026-08-31 04:27:00 +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)
|
||||
|
||||
* 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\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`
|
||||
|
||||
+13
-48
@@ -8,29 +8,25 @@ The ``include`` function returns the rendered content of a template:
|
||||
{{ include('template.html.twig') }}
|
||||
{{ 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.
|
||||
|
||||
If you are using the filesystem loader, the templates are looked for in the
|
||||
paths defined by it.
|
||||
.. tip::
|
||||
|
||||
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:
|
||||
|
||||
.. 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'}) }}
|
||||
|
||||
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
|
||||
|
||||
{# only the name variable will be accessible #}
|
||||
{# Only the "name" variable will be accessible. #}
|
||||
{{ 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
|
||||
: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_cva
|
||||
include
|
||||
include_only
|
||||
max
|
||||
min
|
||||
parent
|
||||
|
||||
@@ -303,6 +303,7 @@ final class CoreExtension extends AbstractExtension
|
||||
new TwigFunction('random', [self::class, 'random'], ['needs_charset' => true]),
|
||||
new TwigFunction('date', [$this, 'convertDate']),
|
||||
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('enum_cases', [self::class, 'enumCases'], ['node_class' => EnumCasesFunction::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.
|
||||
*
|
||||
|
||||
@@ -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