Merge branch '3.x' into 4.x

* 3.x:
  Avoid polluting ModuleNode::toString() with embedded templates
  Simplify code
  Add tests
  Tweak Sandbox docs
  Improve docs on creating new tags
  remove not needed code
  Remove obsolete comment
This commit is contained in:
Fabien Potencier
2025-02-28 08:59:42 +01:00
6 changed files with 71 additions and 23 deletions
+20 -2
View File
@@ -478,6 +478,7 @@ Now, let's see the actual code of this class::
public function parse(\Twig\Token $token): Node
{
$parser = $this->parser;
$lineno = $token->getLine();
$stream = $parser->getStream();
$name = $stream->expect(\Twig\Token::NAME_TYPE)->getValue();
@@ -485,7 +486,7 @@ Now, let's see the actual code of this class::
$value = $parser->getExpressionParser()->parseExpression();
$stream->expect(\Twig\Token::BLOCK_END_TYPE);
return new CustomSetNode($name, $value, $token->getLine());
return new CustomSetNode($name, $value, $lineno);
}
public function getTag(): string
@@ -520,6 +521,18 @@ from the token stream (``$this->parser->getStream()``):
Parsing expressions is done by calling the ``parseExpression()`` like we did for
the ``set`` tag.
When encountering a syntax error during parsing, throw an exception::
throw new SyntaxError('Some error message.', $stream->getCurrent()->getLine(), $stream->getSourceContext());
For better error reporting to the user, follow these recommendations:
* Use ``\Twig\Error\SyntaxError``;
* **Always** pass the line number of the node and the source context;
* End the exception message with a dot.
.. tip::
Reading the existing ``TokenParser`` classes is the best way to learn all
@@ -564,7 +577,8 @@ developer generate beautiful and readable PHP code:
``\Twig\Node\ForNode`` for a usage example).
* ``addDebugInfo()``: Adds the line of the original template file related to
the current node as a comment.
the current node as a comment. It's highly recommended to call this method
when implementing custom nodes.
* ``indent()``: Indents the generated code (see ``\Twig\Node\BlockNode`` for a
usage example).
@@ -572,6 +586,10 @@ developer generate beautiful and readable PHP code:
* ``outdent()``: Outdents the generated code (see ``\Twig\Node\BlockNode`` for a
usage example).
For structural nodes, always call ``addDebugInfo()`` early on in the
compilation process to improve error reporting to the user in case the code
would throw an exception.
.. _creating_extensions:
Creating an Extension
+7 -7
View File
@@ -17,7 +17,7 @@ The sandbox security is managed by a policy instance, which must be passed to
the ``SandboxExtension`` constructor.
By default, Twig comes with one policy class: ``\Twig\Sandbox\SecurityPolicy``.
This class allows you to allow-list some tags, filters, functions, but also
This class allows you to allow-list some tags, filters, functions, and
properties and methods on objects::
$tags = ['if'];
@@ -31,11 +31,11 @@ properties and methods on objects::
$functions = ['range'];
$policy = new \Twig\Sandbox\SecurityPolicy($tags, $filters, $methods, $properties, $functions);
With the previous configuration, the security policy will only allow usage of
the ``if`` tag, and the ``upper`` filter. Moreover, the templates will only be
able to call the ``getTitle()`` and ``getBody()`` methods on ``Article``
objects, and the ``title`` and ``body`` public properties. Everything else
won't be allowed and will generate a ``\Twig\Sandbox\SecurityError`` exception.
With the above configuration, the security policy will only allow usage of the
``if`` tag, and the ``upper`` filter. Moreover, the templates will only be able
to call the ``getTitle()`` and ``getBody()`` methods on ``Article`` objects,
and the ``title`` and ``body`` public properties. Everything else won't be
allowed and will generate a ``\Twig\Sandbox\SecurityError`` exception.
.. note::
@@ -60,7 +60,7 @@ function:
You can sandbox all templates by passing ``true`` as the second argument of
the extension constructor::
$sandbox = new \Twig\Extension\SandboxExtension($policy, true);
$twig->addExtension(new \Twig\Extension\SandboxExtension($policy, true));
Accepting Callables Arguments
-----------------------------
+4 -13
View File
@@ -29,10 +29,7 @@ use Twig\Template;
* Whenever possible, you must set these information (original template name
* and line number) yourself by passing them to the constructor. If some or all
* these information are not available from where you throw the exception, then
* this class will guess them automatically (when the line number is set to -1
* and/or the name is set to null). As this is a costly operation, this
* can be disabled by passing false for both the name and the line number
* when creating a new instance of this class.
* this class will guess them automatically.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
@@ -142,18 +139,12 @@ class Error extends \Exception
$this->lineno = 0;
$template = null;
$templateClass = null;
$backtrace = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS | \DEBUG_BACKTRACE_PROVIDE_OBJECT);
foreach ($backtrace as $trace) {
if (isset($trace['object']) && $trace['object'] instanceof Template) {
$currentClass = $trace['object']::class;
$isEmbedContainer = null === $templateClass ? false : str_starts_with($templateClass, $currentClass);
if ($this->source->getName() === $trace['object']->getTemplateName() && !$isEmbedContainer) {
$template = $trace['object'];
$templateClass = $trace['object']::class;
if (isset($trace['object']) && $trace['object'] instanceof Template && $this->source->getName() === $trace['object']->getTemplateName()) {
$template = $trace['object'];
break;
}
break;
}
}
+6
View File
@@ -32,6 +32,12 @@ final class ModuleNode extends Node
{
public function __construct(BodyNode $body, ?AbstractExpression $parent, Node $blocks, Node $macros, Node $traits, $embeddedTemplates, Source $source)
{
if (!$embeddedTemplates instanceof Node) {
trigger_deprecation('twig/twig', '3.21', \sprintf('Not passing a "%s" instance as the "embedded_templates" argument of the "%s" constructor is deprecated.', Node::class, static::class));
$embeddedTemplates = new Nodes($embeddedTemplates);
}
$nodes = [
'body' => $body,
'blocks' => $blocks,
+9 -1
View File
@@ -127,7 +127,15 @@ class Parser
$this->expressionRefs = null;
}
$node = new ModuleNode(new BodyNode([$body]), $this->parent, new Nodes($this->blocks), new Nodes($this->macros), new Nodes($this->traits), $this->embeddedTemplates, $stream->getSourceContext());
$node = new ModuleNode(
new BodyNode([$body]),
$this->parent,
$this->blocks ? new Nodes($this->blocks) : new EmptyNode(),
$this->macros ? new Nodes($this->macros) : new EmptyNode(),
$this->traits ? new Nodes($this->traits) : new EmptyNode(),
$this->embeddedTemplates ? new Nodes($this->embeddedTemplates) : new EmptyNode(),
$stream->getSourceContext(),
);
$traverser = new NodeTraverser($this->env, $this->visitors);
+25
View File
@@ -413,6 +413,31 @@ EOHTML,
],
'index', 3,
],
// error occurs in an embed tag
[
[
'index' => "
{% embed 'base' %}
{% endembed %}",
'base' => '{% block foo %}{{ foo.bar }}{% endblock %}',
],
'base', 1,
],
// error occurs in an overridden block from an embed tag
[
[
'index' => "
{% embed 'base' %}
{% block foo %}
{{ foo.bar }}
{% endblock %}
{% endembed %}",
'base' => '{% block foo %}{% endblock %}',
],
'index', 4,
],
];
}