Update Twig to v2.15.4

This commit is contained in:
slawkens 2023-02-02 10:37:45 +01:00
parent e552bcfe82
commit 130f7ba405
309 changed files with 3802 additions and 4005 deletions

View File

@ -18,7 +18,7 @@ namespace Twig\Cache;
*/
class FilesystemCache implements CacheInterface
{
const FORCE_BYTECODE_INVALIDATION = 1;
public const FORCE_BYTECODE_INVALIDATION = 1;
private $directory;
private $options;
@ -35,7 +35,7 @@ class FilesystemCache implements CacheInterface
public function generateKey($name, $className)
{
$hash = hash('sha256', $className);
$hash = hash(\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', $className);
return $this->directory.$hash[0].$hash[1].'/'.$hash.'.php';
}
@ -67,7 +67,7 @@ class FilesystemCache implements CacheInterface
if (self::FORCE_BYTECODE_INVALIDATION == ($this->options & self::FORCE_BYTECODE_INVALIDATION)) {
// Compile cached file into bytecode cache
if (\function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), FILTER_VALIDATE_BOOLEAN)) {
if (\function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN)) {
@opcache_invalidate($key, true);
} elseif (\function_exists('apc_compile_file')) {
apc_compile_file($key);

View File

@ -14,11 +14,9 @@ namespace Twig\Cache;
/**
* Implements a no-cache strategy.
*
* @final
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class NullCache implements CacheInterface
final class NullCache implements CacheInterface
{
public function generateKey($name, $className)
{

View File

@ -12,23 +12,22 @@
namespace Twig;
use Twig\Node\ModuleNode;
use Twig\Node\Node;
/**
* Compiles a node to PHP code.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Compiler implements \Twig_CompilerInterface
class Compiler
{
protected $lastLine;
protected $source;
protected $indentation;
protected $env;
protected $debugInfo = [];
protected $sourceOffset;
protected $sourceLine;
protected $filename;
private $lastLine;
private $source;
private $indentation;
private $env;
private $debugInfo = [];
private $sourceOffset;
private $sourceLine;
private $varNameSalt = 0;
public function __construct(Environment $env)
@ -36,16 +35,6 @@ class Compiler implements \Twig_CompilerInterface
$this->env = $env;
}
/**
* @deprecated since 1.25 (to be removed in 2.0)
*/
public function getFilename()
{
@trigger_error(sprintf('The %s() method is deprecated since version 1.25 and will be removed in 2.0.', __FUNCTION__), E_USER_DEPRECATED);
return $this->filename;
}
/**
* Returns the environment instance related to this compiler.
*
@ -73,7 +62,7 @@ class Compiler implements \Twig_CompilerInterface
*
* @return $this
*/
public function compile(\Twig_NodeInterface $node, $indentation = 0)
public function compile(Node $node, $indentation = 0)
{
$this->lastLine = null;
$this->source = '';
@ -84,17 +73,12 @@ class Compiler implements \Twig_CompilerInterface
$this->indentation = $indentation;
$this->varNameSalt = 0;
if ($node instanceof ModuleNode) {
// to be removed in 2.0
$this->filename = $node->getTemplateName();
}
$node->compile($this);
return $this;
}
public function subcompile(\Twig_NodeInterface $node, $raw = true)
public function subcompile(Node $node, $raw = true)
{
if (false === $raw) {
$this->source .= str_repeat(' ', $this->indentation * 4);
@ -124,9 +108,8 @@ class Compiler implements \Twig_CompilerInterface
*
* @return $this
*/
public function write()
public function write(...$strings)
{
$strings = \func_get_args();
foreach ($strings as $string) {
$this->source .= str_repeat(' ', $this->indentation * 4).$string;
}
@ -134,22 +117,6 @@ class Compiler implements \Twig_CompilerInterface
return $this;
}
/**
* Appends an indentation to the current PHP code after compilation.
*
* @return $this
*
* @deprecated since 1.27 (to be removed in 2.0).
*/
public function addIndentation()
{
@trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use write(\'\') instead.', E_USER_DEPRECATED);
$this->source .= str_repeat(' ', $this->indentation * 4);
return $this;
}
/**
* Adds a quoted string to the compiled code.
*
@ -174,21 +141,21 @@ class Compiler implements \Twig_CompilerInterface
public function repr($value)
{
if (\is_int($value) || \is_float($value)) {
if (false !== $locale = setlocale(LC_NUMERIC, '0')) {
setlocale(LC_NUMERIC, 'C');
if (false !== $locale = setlocale(\LC_NUMERIC, '0')) {
setlocale(\LC_NUMERIC, 'C');
}
$this->raw(var_export($value, true));
if (false !== $locale) {
setlocale(LC_NUMERIC, $locale);
setlocale(\LC_NUMERIC, $locale);
}
} elseif (null === $value) {
$this->raw('null');
} elseif (\is_bool($value)) {
$this->raw($value ? 'true' : 'false');
} elseif (\is_array($value)) {
$this->raw('[');
$this->raw('array(');
$first = true;
foreach ($value as $key => $v) {
if (!$first) {
@ -199,7 +166,7 @@ class Compiler implements \Twig_CompilerInterface
$this->raw(' => ');
$this->repr($v);
}
$this->raw(']');
$this->raw(')');
} else {
$this->string($value);
}
@ -212,22 +179,12 @@ class Compiler implements \Twig_CompilerInterface
*
* @return $this
*/
public function addDebugInfo(\Twig_NodeInterface $node)
public function addDebugInfo(Node $node)
{
if ($node->getTemplateLine() != $this->lastLine) {
$this->write(sprintf("// line %d\n", $node->getTemplateLine()));
// when mbstring.func_overload is set to 2
// mb_substr_count() replaces substr_count()
// but they have different signatures!
if (((int) ini_get('mbstring.func_overload')) & 2) {
@trigger_error('Support for having "mbstring.func_overload" different from 0 is deprecated version 1.29 and will be removed in 2.0.', E_USER_DEPRECATED);
// this is much slower than the "right" version
$this->sourceLine += mb_substr_count(mb_substr($this->source, $this->sourceOffset), "\n");
} else {
$this->sourceLine += substr_count($this->source, "\n", $this->sourceOffset);
}
$this->sourceLine += substr_count($this->source, "\n", $this->sourceOffset);
$this->sourceOffset = \strlen($this->source);
$this->debugInfo[$this->sourceLine] = $node->getTemplateLine();
@ -281,7 +238,7 @@ class Compiler implements \Twig_CompilerInterface
public function getVarName()
{
return sprintf('__internal_%s', hash('sha256', __METHOD__.$this->varNameSalt++));
return sprintf('__internal_compile_%d', $this->varNameSalt++);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -38,11 +38,9 @@ use Twig\Template;
*/
class Error extends \Exception
{
protected $lineno;
// to be renamed to name in 2.0
protected $filename;
protected $rawMessage;
private $lineno;
private $name;
private $rawMessage;
private $sourcePath;
private $sourceCode;
@ -57,22 +55,23 @@ class Error extends \Exception
* @param Source|string|null $source The source context where the error occurred
* @param \Exception $previous The previous exception
*/
public function __construct($message, $lineno = -1, $source = null, \Exception $previous = null)
public function __construct(string $message, int $lineno = -1, $source = null, \Exception $previous = null)
{
parent::__construct('', 0, $previous);
if (null === $source) {
$name = null;
} elseif (!$source instanceof Source) {
// for compat with the Twig C ext., passing the template name as string is accepted
} elseif (!$source instanceof Source && !$source instanceof \Twig_Source) {
@trigger_error(sprintf('Passing a string as a source to %s is deprecated since Twig 2.6.1; pass a Twig\Source instance instead.', __CLASS__), \E_USER_DEPRECATED);
$name = $source;
} else {
$name = $source->getName();
$this->sourceCode = $source->getCode();
$this->sourcePath = $source->getPath();
}
parent::__construct('', 0, $previous);
$this->lineno = $lineno;
$this->filename = $name;
$this->name = $name;
$this->rawMessage = $message;
$this->updateRepr();
}
@ -87,67 +86,6 @@ class Error extends \Exception
return $this->rawMessage;
}
/**
* Gets the logical name where the error occurred.
*
* @return string The name
*
* @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead.
*/
public function getTemplateFile()
{
@trigger_error(sprintf('The "%s" method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', __METHOD__), E_USER_DEPRECATED);
return $this->filename;
}
/**
* Sets the logical name where the error occurred.
*
* @param string $name The name
*
* @deprecated since 1.27 (to be removed in 2.0). Use setSourceContext() instead.
*/
public function setTemplateFile($name)
{
@trigger_error(sprintf('The "%s" method is deprecated since version 1.27 and will be removed in 2.0. Use setSourceContext() instead.', __METHOD__), E_USER_DEPRECATED);
$this->filename = $name;
$this->updateRepr();
}
/**
* Gets the logical name where the error occurred.
*
* @return string The name
*
* @deprecated since 1.29 (to be removed in 2.0). Use getSourceContext() instead.
*/
public function getTemplateName()
{
@trigger_error(sprintf('The "%s" method is deprecated since version 1.29 and will be removed in 2.0. Use getSourceContext() instead.', __METHOD__), E_USER_DEPRECATED);
return $this->filename;
}
/**
* Sets the logical name where the error occurred.
*
* @param string $name The name
*
* @deprecated since 1.29 (to be removed in 2.0). Use setSourceContext() instead.
*/
public function setTemplateName($name)
{
@trigger_error(sprintf('The "%s" method is deprecated since version 1.29 and will be removed in 2.0. Use setSourceContext() instead.', __METHOD__), E_USER_DEPRECATED);
$this->filename = $name;
$this->sourceCode = $this->sourcePath = null;
$this->updateRepr();
}
/**
* Gets the template line where the error occurred.
*
@ -177,7 +115,7 @@ class Error extends \Exception
*/
public function getSourceContext()
{
return $this->filename ? new Source($this->sourceCode, $this->filename, $this->sourcePath) : null;
return $this->name ? new Source($this->sourceCode, $this->name, $this->sourcePath) : null;
}
/**
@ -186,10 +124,10 @@ class Error extends \Exception
public function setSourceContext(Source $source = null)
{
if (null === $source) {
$this->sourceCode = $this->filename = $this->sourcePath = null;
$this->sourceCode = $this->name = $this->sourcePath = null;
} else {
$this->sourceCode = $source->getCode();
$this->filename = $source->getName();
$this->name = $source->getName();
$this->sourcePath = $source->getPath();
}
@ -208,10 +146,7 @@ class Error extends \Exception
$this->updateRepr();
}
/**
* @internal
*/
protected function updateRepr()
private function updateRepr()
{
$this->message = $this->rawMessage;
@ -234,11 +169,11 @@ class Error extends \Exception
$questionMark = true;
}
if ($this->filename) {
if (\is_string($this->filename) || (\is_object($this->filename) && method_exists($this->filename, '__toString'))) {
$name = sprintf('"%s"', $this->filename);
if ($this->name) {
if (\is_string($this->name) || (\is_object($this->name) && method_exists($this->name, '__toString'))) {
$name = sprintf('"%s"', $this->name);
} else {
$name = json_encode($this->filename);
$name = json_encode($this->name);
}
$this->message .= sprintf(' in %s', $name);
}
@ -256,20 +191,17 @@ class Error extends \Exception
}
}
/**
* @internal
*/
protected function guessTemplateInfo()
private function guessTemplateInfo()
{
$template = null;
$templateClass = null;
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS | DEBUG_BACKTRACE_PROVIDE_OBJECT);
$backtrace = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS | \DEBUG_BACKTRACE_PROVIDE_OBJECT);
foreach ($backtrace as $trace) {
if (isset($trace['object']) && $trace['object'] instanceof Template && 'Twig_Template' !== \get_class($trace['object'])) {
if (isset($trace['object']) && $trace['object'] instanceof Template && 'Twig\Template' !== \get_class($trace['object'])) {
$currentClass = \get_class($trace['object']);
$isEmbedContainer = 0 === strpos($templateClass, $currentClass);
if (null === $this->filename || ($this->filename == $trace['object']->getTemplateName() && !$isEmbedContainer)) {
$isEmbedContainer = null === $templateClass ? false : 0 === strpos($templateClass, $currentClass);
if (null === $this->name || ($this->name == $trace['object']->getTemplateName() && !$isEmbedContainer)) {
$template = $trace['object'];
$templateClass = \get_class($trace['object']);
}
@ -277,8 +209,8 @@ class Error extends \Exception
}
// update template name
if (null !== $template && null === $this->filename) {
$this->filename = $template->getTemplateName();
if (null !== $template && null === $this->name) {
$this->name = $template->getTemplateName();
}
// update template path if any
@ -296,7 +228,7 @@ class Error extends \Exception
$file = $r->getFileName();
$exceptions = [$e = $this];
while ($e instanceof self && $e = $e->getPrevious()) {
while ($e = $e->getPrevious()) {
$exceptions[] = $e;
}

View File

@ -26,20 +26,6 @@ class SyntaxError extends Error
* @param array $items An array of possible items
*/
public function addSuggestions($name, array $items)
{
if (!$alternatives = self::computeAlternatives($name, $items)) {
return;
}
$this->appendMessage(sprintf(' Did you mean "%s"?', implode('", "', $alternatives)));
}
/**
* @internal
*
* To be merged with the addSuggestions() method in 2.0.
*/
public static function computeAlternatives($name, $items)
{
$alternatives = [];
foreach ($items as $item) {
@ -48,9 +34,14 @@ class SyntaxError extends Error
$alternatives[$item] = $lev;
}
}
if (!$alternatives) {
return;
}
asort($alternatives);
return array_keys($alternatives);
$this->appendMessage(sprintf(' Did you mean "%s"?', implode('", "', array_keys($alternatives))));
}
}

View File

@ -13,6 +13,7 @@
namespace Twig;
use Twig\Error\SyntaxError;
use Twig\Node\Expression\AbstractExpression;
use Twig\Node\Expression\ArrayExpression;
use Twig\Node\Expression\ArrowFunctionExpression;
use Twig\Node\Expression\AssignNameExpression;
@ -24,6 +25,7 @@ use Twig\Node\Expression\GetAttrExpression;
use Twig\Node\Expression\MethodCallExpression;
use Twig\Node\Expression\NameExpression;
use Twig\Node\Expression\ParentExpression;
use Twig\Node\Expression\TestExpression;
use Twig\Node\Expression\Unary\NegUnary;
use Twig\Node\Expression\Unary\NotUnary;
use Twig\Node\Expression\Unary\PosUnary;
@ -43,30 +45,20 @@ use Twig\Node\Node;
*/
class ExpressionParser
{
const OPERATOR_LEFT = 1;
const OPERATOR_RIGHT = 2;
protected $parser;
protected $unaryOperators;
protected $binaryOperators;
public const OPERATOR_LEFT = 1;
public const OPERATOR_RIGHT = 2;
private $parser;
private $env;
private $unaryOperators;
private $binaryOperators;
public function __construct(Parser $parser, $env = null)
public function __construct(Parser $parser, Environment $env)
{
$this->parser = $parser;
if ($env instanceof Environment) {
$this->env = $env;
$this->unaryOperators = $env->getUnaryOperators();
$this->binaryOperators = $env->getBinaryOperators();
} else {
@trigger_error('Passing the operators as constructor arguments to '.__METHOD__.' is deprecated since version 1.27. Pass the environment instead.', E_USER_DEPRECATED);
$this->env = $parser->getEnvironment();
$this->unaryOperators = func_get_arg(1);
$this->binaryOperators = func_get_arg(2);
}
$this->env = $env;
$this->unaryOperators = $env->getUnaryOperators();
$this->binaryOperators = $env->getBinaryOperators();
}
public function parseExpression($precedence = 0, $allowArrow = false)
@ -86,7 +78,7 @@ class ExpressionParser
} elseif ('is' === $token->getValue()) {
$expr = $this->parseTestExpression($expr);
} elseif (isset($op['callable'])) {
$expr = \call_user_func($op['callable'], $this->parser, $expr);
$expr = $op['callable']($this->parser, $expr);
} else {
$expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence']);
$class = $op['class'];
@ -111,57 +103,57 @@ class ExpressionParser
$stream = $this->parser->getStream();
// short array syntax (one argument, no parentheses)?
if ($stream->look(1)->test(Token::ARROW_TYPE)) {
if ($stream->look(1)->test(/* Token::ARROW_TYPE */ 12)) {
$line = $stream->getCurrent()->getLine();
$token = $stream->expect(Token::NAME_TYPE);
$token = $stream->expect(/* Token::NAME_TYPE */ 5);
$names = [new AssignNameExpression($token->getValue(), $token->getLine())];
$stream->expect(Token::ARROW_TYPE);
$stream->expect(/* Token::ARROW_TYPE */ 12);
return new ArrowFunctionExpression($this->parseExpression(0), new Node($names), $line);
}
// first, determine if we are parsing an arrow function by finding => (long form)
$i = 0;
if (!$stream->look($i)->test(Token::PUNCTUATION_TYPE, '(')) {
if (!$stream->look($i)->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) {
return null;
}
++$i;
while (true) {
// variable name
++$i;
if (!$stream->look($i)->test(Token::PUNCTUATION_TYPE, ',')) {
if (!$stream->look($i)->test(/* Token::PUNCTUATION_TYPE */ 9, ',')) {
break;
}
++$i;
}
if (!$stream->look($i)->test(Token::PUNCTUATION_TYPE, ')')) {
if (!$stream->look($i)->test(/* Token::PUNCTUATION_TYPE */ 9, ')')) {
return null;
}
++$i;
if (!$stream->look($i)->test(Token::ARROW_TYPE)) {
if (!$stream->look($i)->test(/* Token::ARROW_TYPE */ 12)) {
return null;
}
// yes, let's parse it properly
$token = $stream->expect(Token::PUNCTUATION_TYPE, '(');
$token = $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '(');
$line = $token->getLine();
$names = [];
while (true) {
$token = $stream->expect(Token::NAME_TYPE);
$token = $stream->expect(/* Token::NAME_TYPE */ 5);
$names[] = new AssignNameExpression($token->getValue(), $token->getLine());
if (!$stream->nextIf(Token::PUNCTUATION_TYPE, ',')) {
if (!$stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) {
break;
}
}
$stream->expect(Token::PUNCTUATION_TYPE, ')');
$stream->expect(Token::ARROW_TYPE);
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ')');
$stream->expect(/* Token::ARROW_TYPE */ 12);
return new ArrowFunctionExpression($this->parseExpression(0), new Node($names), $line);
}
protected function getPrimary()
private function getPrimary(): AbstractExpression
{
$token = $this->parser->getCurrentToken();
@ -172,10 +164,10 @@ class ExpressionParser
$class = $operator['class'];
return $this->parsePostfixExpression(new $class($expr, $token->getLine()));
} elseif ($token->test(Token::PUNCTUATION_TYPE, '(')) {
} elseif ($token->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) {
$this->parser->getStream()->next();
$expr = $this->parseExpression();
$this->parser->getStream()->expect(Token::PUNCTUATION_TYPE, ')', 'An opened parenthesis is not properly closed');
$this->parser->getStream()->expect(/* Token::PUNCTUATION_TYPE */ 9, ')', 'An opened parenthesis is not properly closed');
return $this->parsePostfixExpression($expr);
}
@ -183,12 +175,12 @@ class ExpressionParser
return $this->parsePrimaryExpression();
}
protected function parseConditionalExpression($expr)
private function parseConditionalExpression($expr): AbstractExpression
{
while ($this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, '?')) {
if (!$this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, ':')) {
while ($this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, '?')) {
if (!$this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ':')) {
$expr2 = $this->parseExpression();
if ($this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, ':')) {
if ($this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ':')) {
$expr3 = $this->parseExpression();
} else {
$expr3 = new ConstantExpression('', $this->parser->getCurrentToken()->getLine());
@ -204,21 +196,21 @@ class ExpressionParser
return $expr;
}
protected function isUnary(Token $token)
private function isUnary(Token $token): bool
{
return $token->test(Token::OPERATOR_TYPE) && isset($this->unaryOperators[$token->getValue()]);
return $token->test(/* Token::OPERATOR_TYPE */ 8) && isset($this->unaryOperators[$token->getValue()]);
}
protected function isBinary(Token $token)
private function isBinary(Token $token): bool
{
return $token->test(Token::OPERATOR_TYPE) && isset($this->binaryOperators[$token->getValue()]);
return $token->test(/* Token::OPERATOR_TYPE */ 8) && isset($this->binaryOperators[$token->getValue()]);
}
public function parsePrimaryExpression()
{
$token = $this->parser->getCurrentToken();
switch ($token->getType()) {
case Token::NAME_TYPE:
case /* Token::NAME_TYPE */ 5:
$this->parser->getStream()->next();
switch ($token->getValue()) {
case 'true':
@ -247,17 +239,17 @@ class ExpressionParser
}
break;
case Token::NUMBER_TYPE:
case /* Token::NUMBER_TYPE */ 6:
$this->parser->getStream()->next();
$node = new ConstantExpression($token->getValue(), $token->getLine());
break;
case Token::STRING_TYPE:
case Token::INTERPOLATION_START_TYPE:
case /* Token::STRING_TYPE */ 7:
case /* Token::INTERPOLATION_START_TYPE */ 10:
$node = $this->parseStringExpression();
break;
case Token::OPERATOR_TYPE:
case /* Token::OPERATOR_TYPE */ 8:
if (preg_match(Lexer::REGEX_NAME, $token->getValue(), $matches) && $matches[0] == $token->getValue()) {
// in this context, string operators are variable names
$this->parser->getStream()->next();
@ -267,10 +259,8 @@ class ExpressionParser
$class = $this->unaryOperators[$token->getValue()]['class'];
$ref = new \ReflectionClass($class);
$negClass = 'Twig\Node\Expression\Unary\NegUnary';
$posClass = 'Twig\Node\Expression\Unary\PosUnary';
if (!(\in_array($ref->getName(), [$negClass, $posClass, 'Twig_Node_Expression_Unary_Neg', 'Twig_Node_Expression_Unary_Pos'])
|| $ref->isSubclassOf($negClass) || $ref->isSubclassOf($posClass)
if (!(\in_array($ref->getName(), [NegUnary::class, PosUnary::class, 'Twig_Node_Expression_Unary_Neg', 'Twig_Node_Expression_Unary_Pos'])
|| $ref->isSubclassOf(NegUnary::class) || $ref->isSubclassOf(PosUnary::class)
|| $ref->isSubclassOf('Twig_Node_Expression_Unary_Neg') || $ref->isSubclassOf('Twig_Node_Expression_Unary_Pos'))
) {
throw new SyntaxError(sprintf('Unexpected unary operator "%s".', $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext());
@ -285,11 +275,11 @@ class ExpressionParser
// no break
default:
if ($token->test(Token::PUNCTUATION_TYPE, '[')) {
if ($token->test(/* Token::PUNCTUATION_TYPE */ 9, '[')) {
$node = $this->parseArrayExpression();
} elseif ($token->test(Token::PUNCTUATION_TYPE, '{')) {
} elseif ($token->test(/* Token::PUNCTUATION_TYPE */ 9, '{')) {
$node = $this->parseHashExpression();
} elseif ($token->test(Token::OPERATOR_TYPE, '=') && ('==' === $this->parser->getStream()->look(-1)->getValue() || '!=' === $this->parser->getStream()->look(-1)->getValue())) {
} elseif ($token->test(/* Token::OPERATOR_TYPE */ 8, '=') && ('==' === $this->parser->getStream()->look(-1)->getValue() || '!=' === $this->parser->getStream()->look(-1)->getValue())) {
throw new SyntaxError(sprintf('Unexpected operator of value "%s". Did you try to use "===" or "!==" for strict comparison? Use "is same as(value)" instead.', $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext());
} else {
throw new SyntaxError(sprintf('Unexpected token "%s" of value "%s".', Token::typeToEnglish($token->getType()), $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext());
@ -307,12 +297,12 @@ class ExpressionParser
// a string cannot be followed by another string in a single expression
$nextCanBeString = true;
while (true) {
if ($nextCanBeString && $token = $stream->nextIf(Token::STRING_TYPE)) {
if ($nextCanBeString && $token = $stream->nextIf(/* Token::STRING_TYPE */ 7)) {
$nodes[] = new ConstantExpression($token->getValue(), $token->getLine());
$nextCanBeString = false;
} elseif ($stream->nextIf(Token::INTERPOLATION_START_TYPE)) {
} elseif ($stream->nextIf(/* Token::INTERPOLATION_START_TYPE */ 10)) {
$nodes[] = $this->parseExpression();
$stream->expect(Token::INTERPOLATION_END_TYPE);
$stream->expect(/* Token::INTERPOLATION_END_TYPE */ 11);
$nextCanBeString = true;
} else {
break;
@ -330,16 +320,16 @@ class ExpressionParser
public function parseArrayExpression()
{
$stream = $this->parser->getStream();
$stream->expect(Token::PUNCTUATION_TYPE, '[', 'An array element was expected');
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '[', 'An array element was expected');
$node = new ArrayExpression([], $stream->getCurrent()->getLine());
$first = true;
while (!$stream->test(Token::PUNCTUATION_TYPE, ']')) {
while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9, ']')) {
if (!$first) {
$stream->expect(Token::PUNCTUATION_TYPE, ',', 'An array element must be followed by a comma');
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ',', 'An array element must be followed by a comma');
// trailing ,?
if ($stream->test(Token::PUNCTUATION_TYPE, ']')) {
if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ']')) {
break;
}
}
@ -347,7 +337,7 @@ class ExpressionParser
$node->addElement($this->parseExpression());
}
$stream->expect(Token::PUNCTUATION_TYPE, ']', 'An opened array is not properly closed');
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ']', 'An opened array is not properly closed');
return $node;
}
@ -355,16 +345,16 @@ class ExpressionParser
public function parseHashExpression()
{
$stream = $this->parser->getStream();
$stream->expect(Token::PUNCTUATION_TYPE, '{', 'A hash element was expected');
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '{', 'A hash element was expected');
$node = new ArrayExpression([], $stream->getCurrent()->getLine());
$first = true;
while (!$stream->test(Token::PUNCTUATION_TYPE, '}')) {
while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9, '}')) {
if (!$first) {
$stream->expect(Token::PUNCTUATION_TYPE, ',', 'A hash value must be followed by a comma');
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ',', 'A hash value must be followed by a comma');
// trailing ,?
if ($stream->test(Token::PUNCTUATION_TYPE, '}')) {
if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '}')) {
break;
}
}
@ -376,9 +366,18 @@ class ExpressionParser
// * a string -- 'a'
// * a name, which is equivalent to a string -- a
// * an expression, which must be enclosed in parentheses -- (1 + 2)
if (($token = $stream->nextIf(Token::STRING_TYPE)) || ($token = $stream->nextIf(Token::NAME_TYPE)) || $token = $stream->nextIf(Token::NUMBER_TYPE)) {
if ($token = $stream->nextIf(/* Token::NAME_TYPE */ 5)) {
$key = new ConstantExpression($token->getValue(), $token->getLine());
} elseif ($stream->test(Token::PUNCTUATION_TYPE, '(')) {
// {a} is a shortcut for {a:a}
if ($stream->test(Token::PUNCTUATION_TYPE, [',', '}'])) {
$value = new NameExpression($key->getAttribute('value'), $key->getTemplateLine());
$node->addElement($value, $key);
continue;
}
} elseif (($token = $stream->nextIf(/* Token::STRING_TYPE */ 7)) || $token = $stream->nextIf(/* Token::NUMBER_TYPE */ 6)) {
$key = new ConstantExpression($token->getValue(), $token->getLine());
} elseif ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) {
$key = $this->parseExpression();
} else {
$current = $stream->getCurrent();
@ -386,12 +385,12 @@ class ExpressionParser
throw new SyntaxError(sprintf('A hash key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s".', Token::typeToEnglish($current->getType()), $current->getValue()), $current->getLine(), $stream->getSourceContext());
}
$stream->expect(Token::PUNCTUATION_TYPE, ':', 'A hash key must be followed by a colon (:)');
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ':', 'A hash key must be followed by a colon (:)');
$value = $this->parseExpression();
$node->addElement($value, $key);
}
$stream->expect(Token::PUNCTUATION_TYPE, '}', 'An opened hash is not properly closed');
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '}', 'An opened hash is not properly closed');
return $node;
}
@ -400,7 +399,7 @@ class ExpressionParser
{
while (true) {
$token = $this->parser->getCurrentToken();
if (Token::PUNCTUATION_TYPE == $token->getType()) {
if (/* Token::PUNCTUATION_TYPE */ 9 == $token->getType()) {
if ('.' == $token->getValue() || '[' == $token->getValue()) {
$node = $this->parseSubscriptExpression($node);
} elseif ('|' == $token->getValue()) {
@ -474,22 +473,22 @@ class ExpressionParser
if ('.' == $token->getValue()) {
$token = $stream->next();
if (
Token::NAME_TYPE == $token->getType()
/* Token::NAME_TYPE */ 5 == $token->getType()
||
Token::NUMBER_TYPE == $token->getType()
/* Token::NUMBER_TYPE */ 6 == $token->getType()
||
(Token::OPERATOR_TYPE == $token->getType() && preg_match(Lexer::REGEX_NAME, $token->getValue()))
(/* Token::OPERATOR_TYPE */ 8 == $token->getType() && preg_match(Lexer::REGEX_NAME, $token->getValue()))
) {
$arg = new ConstantExpression($token->getValue(), $lineno);
if ($stream->test(Token::PUNCTUATION_TYPE, '(')) {
if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) {
$type = Template::METHOD_CALL;
foreach ($this->parseArguments() as $n) {
$arguments->addElement($n);
}
}
} else {
throw new SyntaxError('Expected name or number.', $lineno, $stream->getSourceContext());
throw new SyntaxError(sprintf('Expected name or number, got value "%s" of type %s.', $token->getValue(), Token::typeToEnglish($token->getType())), $lineno, $stream->getSourceContext());
}
if ($node instanceof NameExpression && null !== $this->parser->getImportedSymbol('template', $node->getAttribute('name'))) {
@ -499,11 +498,7 @@ class ExpressionParser
$name = $arg->getAttribute('value');
if ($this->parser->isReservedMacroName($name)) {
throw new SyntaxError(sprintf('"%s" cannot be called as macro as it is a reserved keyword.', $name), $token->getLine(), $stream->getSourceContext());
}
$node = new MethodCallExpression($node, 'get'.$name, $arguments, $lineno);
$node = new MethodCallExpression($node, 'macro_'.$name, $arguments, $lineno);
$node->setAttribute('safe', true);
return $node;
@ -513,19 +508,19 @@ class ExpressionParser
// slice?
$slice = false;
if ($stream->test(Token::PUNCTUATION_TYPE, ':')) {
if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ':')) {
$slice = true;
$arg = new ConstantExpression(0, $token->getLine());
} else {
$arg = $this->parseExpression();
}
if ($stream->nextIf(Token::PUNCTUATION_TYPE, ':')) {
if ($stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ':')) {
$slice = true;
}
if ($slice) {
if ($stream->test(Token::PUNCTUATION_TYPE, ']')) {
if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ']')) {
$length = new ConstantExpression(null, $token->getLine());
} else {
$length = $this->parseExpression();
@ -535,12 +530,12 @@ class ExpressionParser
$arguments = new Node([$arg, $length]);
$filter = new $class($node, new ConstantExpression('slice', $token->getLine()), $arguments, $token->getLine());
$stream->expect(Token::PUNCTUATION_TYPE, ']');
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ']');
return $filter;
}
$stream->expect(Token::PUNCTUATION_TYPE, ']');
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ']');
}
return new GetAttrExpression($node, $arg, $arguments, $type, $lineno);
@ -556,10 +551,10 @@ class ExpressionParser
public function parseFilterExpressionRaw($node, $tag = null)
{
while (true) {
$token = $this->parser->getStream()->expect(Token::NAME_TYPE);
$token = $this->parser->getStream()->expect(/* Token::NAME_TYPE */ 5);
$name = new ConstantExpression($token->getValue(), $token->getLine());
if (!$this->parser->getStream()->test(Token::PUNCTUATION_TYPE, '(')) {
if (!$this->parser->getStream()->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) {
$arguments = new Node();
} else {
$arguments = $this->parseArguments(true, false, true);
@ -569,7 +564,7 @@ class ExpressionParser
$node = new $class($node, $name, $arguments, $token->getLine(), $tag);
if (!$this->parser->getStream()->test(Token::PUNCTUATION_TYPE, '|')) {
if (!$this->parser->getStream()->test(/* Token::PUNCTUATION_TYPE */ 9, '|')) {
break;
}
@ -594,21 +589,26 @@ class ExpressionParser
$args = [];
$stream = $this->parser->getStream();
$stream->expect(Token::PUNCTUATION_TYPE, '(', 'A list of arguments must begin with an opening parenthesis');
while (!$stream->test(Token::PUNCTUATION_TYPE, ')')) {
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '(', 'A list of arguments must begin with an opening parenthesis');
while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9, ')')) {
if (!empty($args)) {
$stream->expect(Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma');
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ',', 'Arguments must be separated by a comma');
// if the comma above was a trailing comma, early exit the argument parse loop
if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ')')) {
break;
}
}
if ($definition) {
$token = $stream->expect(Token::NAME_TYPE, null, 'An argument must be a name');
$token = $stream->expect(/* Token::NAME_TYPE */ 5, null, 'An argument must be a name');
$value = new NameExpression($token->getValue(), $this->parser->getCurrentToken()->getLine());
} else {
$value = $this->parseExpression(0, $allowArrow);
}
$name = null;
if ($namedArguments && $token = $stream->nextIf(Token::OPERATOR_TYPE, '=')) {
if ($namedArguments && $token = $stream->nextIf(/* Token::OPERATOR_TYPE */ 8, '=')) {
if (!$value instanceof NameExpression) {
throw new SyntaxError(sprintf('A parameter name must be a string, "%s" given.', \get_class($value)), $token->getLine(), $stream->getSourceContext());
}
@ -618,7 +618,7 @@ class ExpressionParser
$value = $this->parsePrimaryExpression();
if (!$this->checkConstantExpression($value)) {
throw new SyntaxError(sprintf('A default value for an argument must be a constant (a boolean, a string, a number, or an array).'), $token->getLine(), $stream->getSourceContext());
throw new SyntaxError('A default value for an argument must be a constant (a boolean, a string, a number, or an array).', $token->getLine(), $stream->getSourceContext());
}
} else {
$value = $this->parseExpression(0, $allowArrow);
@ -639,7 +639,7 @@ class ExpressionParser
}
}
}
$stream->expect(Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis');
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ')', 'A list of arguments must be closed by a parenthesis');
return new Node($args);
}
@ -650,19 +650,19 @@ class ExpressionParser
$targets = [];
while (true) {
$token = $this->parser->getCurrentToken();
if ($stream->test(Token::OPERATOR_TYPE) && preg_match(Lexer::REGEX_NAME, $token->getValue())) {
if ($stream->test(/* Token::OPERATOR_TYPE */ 8) && preg_match(Lexer::REGEX_NAME, $token->getValue())) {
// in this context, string operators are variable names
$this->parser->getStream()->next();
} else {
$stream->expect(Token::NAME_TYPE, null, 'Only variables can be assigned to');
$stream->expect(/* Token::NAME_TYPE */ 5, null, 'Only variables can be assigned to');
}
$value = $token->getValue();
if (\in_array(strtolower($value), ['true', 'false', 'none', 'null'])) {
if (\in_array(strtr($value, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), ['true', 'false', 'none', 'null'])) {
throw new SyntaxError(sprintf('You cannot assign a value to "%s".', $value), $token->getLine(), $stream->getSourceContext());
}
$targets[] = new AssignNameExpression($value, $token->getLine());
if (!$stream->nextIf(Token::PUNCTUATION_TYPE, ',')) {
if (!$stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) {
break;
}
}
@ -675,7 +675,7 @@ class ExpressionParser
$targets = [];
while (true) {
$targets[] = $this->parseExpression();
if (!$this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, ',')) {
if (!$this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) {
break;
}
}
@ -683,35 +683,42 @@ class ExpressionParser
return new Node($targets);
}
private function parseNotTestExpression(\Twig_NodeInterface $node)
private function parseNotTestExpression(Node $node): NotUnary
{
return new NotUnary($this->parseTestExpression($node), $this->parser->getCurrentToken()->getLine());
}
private function parseTestExpression(\Twig_NodeInterface $node)
private function parseTestExpression(Node $node): TestExpression
{
$stream = $this->parser->getStream();
list($name, $test) = $this->getTest($node->getTemplateLine());
$class = $this->getTestNodeClass($test);
$arguments = null;
if ($stream->test(Token::PUNCTUATION_TYPE, '(')) {
if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) {
$arguments = $this->parseArguments(true);
} elseif ($test->hasOneMandatoryArgument()) {
$arguments = new Node([0 => $this->parsePrimaryExpression()]);
}
if ('defined' === $name && $node instanceof NameExpression && null !== $alias = $this->parser->getImportedSymbol('function', $node->getAttribute('name'))) {
$node = new MethodCallExpression($alias['node'], $alias['name'], new ArrayExpression([], $node->getTemplateLine()), $node->getTemplateLine());
$node->setAttribute('safe', true);
}
return new $class($node, $name, $arguments, $this->parser->getCurrentToken()->getLine());
}
private function getTest($line)
private function getTest(int $line): array
{
$stream = $this->parser->getStream();
$name = $stream->expect(Token::NAME_TYPE)->getValue();
$name = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue();
if ($test = $this->env->getTest($name)) {
return [$name, $test];
}
if ($stream->test(Token::NAME_TYPE)) {
if ($stream->test(/* Token::NAME_TYPE */ 5)) {
// try 2-words tests
$name = $name.' '.$this->parser->getCurrentToken()->getValue();
@ -728,11 +735,12 @@ class ExpressionParser
throw $e;
}
private function getTestNodeClass($test)
private function getTestNodeClass(TwigTest $test): string
{
if ($test instanceof TwigTest && $test->isDeprecated()) {
if ($test->isDeprecated()) {
$stream = $this->parser->getStream();
$message = sprintf('Twig Test "%s" is deprecated', $test->getName());
if (!\is_bool($test->getDeprecatedVersion())) {
$message .= sprintf(' since version %s', $test->getDeprecatedVersion());
}
@ -740,19 +748,15 @@ class ExpressionParser
$message .= sprintf('. Use "%s" instead', $test->getAlternative());
}
$src = $stream->getSourceContext();
$message .= sprintf(' in %s at line %d.', $src->getPath() ? $src->getPath() : $src->getName(), $stream->getCurrent()->getLine());
$message .= sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $stream->getCurrent()->getLine());
@trigger_error($message, E_USER_DEPRECATED);
@trigger_error($message, \E_USER_DEPRECATED);
}
if ($test instanceof TwigTest) {
return $test->getNodeClass();
}
return $test instanceof \Twig_Test_Node ? $test->getClass() : 'Twig\Node\Expression\TestExpression';
return $test->getNodeClass();
}
protected function getFunctionNodeClass($name, $line)
private function getFunctionNodeClass(string $name, int $line): string
{
if (false === $function = $this->env->getFunction($name)) {
$e = new SyntaxError(sprintf('Unknown "%s" function.', $name), $line, $this->parser->getStream()->getSourceContext());
@ -761,7 +765,7 @@ class ExpressionParser
throw $e;
}
if ($function instanceof TwigFunction && $function->isDeprecated()) {
if ($function->isDeprecated()) {
$message = sprintf('Twig Function "%s" is deprecated', $function->getName());
if (!\is_bool($function->getDeprecatedVersion())) {
$message .= sprintf(' since version %s', $function->getDeprecatedVersion());
@ -770,19 +774,15 @@ class ExpressionParser
$message .= sprintf('. Use "%s" instead', $function->getAlternative());
}
$src = $this->parser->getStream()->getSourceContext();
$message .= sprintf(' in %s at line %d.', $src->getPath() ? $src->getPath() : $src->getName(), $line);
$message .= sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $line);
@trigger_error($message, E_USER_DEPRECATED);
@trigger_error($message, \E_USER_DEPRECATED);
}
if ($function instanceof TwigFunction) {
return $function->getNodeClass();
}
return $function instanceof \Twig_Function_Node ? $function->getClass() : 'Twig\Node\Expression\FunctionExpression';
return $function->getNodeClass();
}
protected function getFilterNodeClass($name, $line)
private function getFilterNodeClass(string $name, int $line): string
{
if (false === $filter = $this->env->getFilter($name)) {
$e = new SyntaxError(sprintf('Unknown "%s" filter.', $name), $line, $this->parser->getStream()->getSourceContext());
@ -791,7 +791,7 @@ class ExpressionParser
throw $e;
}
if ($filter instanceof TwigFilter && $filter->isDeprecated()) {
if ($filter->isDeprecated()) {
$message = sprintf('Twig Filter "%s" is deprecated', $filter->getName());
if (!\is_bool($filter->getDeprecatedVersion())) {
$message .= sprintf(' since version %s', $filter->getDeprecatedVersion());
@ -800,20 +800,16 @@ class ExpressionParser
$message .= sprintf('. Use "%s" instead', $filter->getAlternative());
}
$src = $this->parser->getStream()->getSourceContext();
$message .= sprintf(' in %s at line %d.', $src->getPath() ? $src->getPath() : $src->getName(), $line);
$message .= sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $line);
@trigger_error($message, E_USER_DEPRECATED);
@trigger_error($message, \E_USER_DEPRECATED);
}
if ($filter instanceof TwigFilter) {
return $filter->getNodeClass();
}
return $filter instanceof \Twig_Filter_Node ? $filter->getClass() : 'Twig\Node\Expression\FilterExpression';
return $filter->getNodeClass();
}
// checks that the node only contains "constant" elements
protected function checkConstantExpression(\Twig_NodeInterface $node)
private function checkConstantExpression(Node $node): bool
{
if (!($node instanceof ConstantExpression || $node instanceof ArrayExpression
|| $node instanceof NegUnary || $node instanceof PosUnary

View File

@ -11,17 +11,8 @@
namespace Twig\Extension;
use Twig\Environment;
abstract class AbstractExtension implements ExtensionInterface
{
/**
* @deprecated since 1.23 (to be removed in 2.0), implement \Twig_Extension_InitRuntimeInterface instead
*/
public function initRuntime(Environment $environment)
{
}
public function getTokenParsers()
{
return [];
@ -51,22 +42,6 @@ abstract class AbstractExtension implements ExtensionInterface
{
return [];
}
/**
* @deprecated since 1.23 (to be removed in 2.0), implement \Twig_Extension_GlobalsInterface instead
*/
public function getGlobals()
{
return [];
}
/**
* @deprecated since 1.26 (to be removed in 2.0), not used anymore internally
*/
public function getName()
{
return \get_class($this);
}
}
class_alias('Twig\Extension\AbstractExtension', 'Twig_Extension');

File diff suppressed because it is too large Load Diff

View File

@ -12,10 +12,7 @@
namespace Twig\Extension {
use Twig\TwigFunction;
/**
* @final
*/
class DebugExtension extends AbstractExtension
final class DebugExtension extends AbstractExtension
{
public function getFunctions()
{
@ -33,11 +30,6 @@ class DebugExtension extends AbstractExtension
new TwigFunction('dump', 'twig_var_dump', ['is_safe' => $isDumpOutputHtmlSafe ? ['html'] : [], 'needs_context' => true, 'needs_environment' => true, 'is_variadic' => true]),
];
}
public function getName()
{
return 'debug';
}
}
class_alias('Twig\Extension\DebugExtension', 'Twig_Extension_Debug');
@ -48,7 +40,7 @@ use Twig\Environment;
use Twig\Template;
use Twig\TemplateWrapper;
function twig_var_dump(Environment $env, $context, array $vars = [])
function twig_var_dump(Environment $env, $context, ...$vars)
{
if (!$env->isDebug()) {
return;
@ -66,9 +58,7 @@ function twig_var_dump(Environment $env, $context, array $vars = [])
var_dump($vars);
} else {
foreach ($vars as $var) {
var_dump($var);
}
var_dump(...$vars);
}
return ob_get_clean();

View File

@ -10,16 +10,21 @@
*/
namespace Twig\Extension {
use Twig\FileExtensionEscapingStrategy;
use Twig\NodeVisitor\EscaperNodeVisitor;
use Twig\TokenParser\AutoEscapeTokenParser;
use Twig\TwigFilter;
/**
* @final
*/
class EscaperExtension extends AbstractExtension
final class EscaperExtension extends AbstractExtension
{
protected $defaultStrategy;
private $defaultStrategy;
private $escapers = [];
/** @internal */
public $safeClasses = [];
/** @internal */
public $safeLookup = [];
/**
* @param string|false|callable $defaultStrategy An escaping strategy
@ -44,6 +49,8 @@ class EscaperExtension extends AbstractExtension
public function getFilters()
{
return [
new TwigFilter('escape', 'twig_escape_filter', ['needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe']),
new TwigFilter('e', 'twig_escape_filter', ['needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe']),
new TwigFilter('raw', 'twig_raw_filter', ['is_safe' => ['all']]),
];
}
@ -58,21 +65,8 @@ class EscaperExtension extends AbstractExtension
*/
public function setDefaultStrategy($defaultStrategy)
{
// for BC
if (true === $defaultStrategy) {
@trigger_error('Using "true" as the default strategy is deprecated since version 1.21. Use "html" instead.', E_USER_DEPRECATED);
$defaultStrategy = 'html';
}
if ('filename' === $defaultStrategy) {
@trigger_error('Using "filename" as the default strategy is deprecated since version 1.27. Use "name" instead.', E_USER_DEPRECATED);
$defaultStrategy = 'name';
}
if ('name' === $defaultStrategy) {
$defaultStrategy = ['\Twig\FileExtensionEscapingStrategy', 'guess'];
$defaultStrategy = [FileExtensionEscapingStrategy::class, 'guess'];
}
$this->defaultStrategy = $defaultStrategy;
@ -96,9 +90,47 @@ class EscaperExtension extends AbstractExtension
return $this->defaultStrategy;
}
public function getName()
/**
* Defines a new escaper to be used via the escape filter.
*
* @param string $strategy The strategy name that should be used as a strategy in the escape call
* @param callable $callable A valid PHP callable
*/
public function setEscaper($strategy, callable $callable)
{
return 'escaper';
$this->escapers[$strategy] = $callable;
}
/**
* Gets all defined escapers.
*
* @return callable[] An array of escapers
*/
public function getEscapers()
{
return $this->escapers;
}
public function setSafeClasses(array $safeClasses = [])
{
$this->safeClasses = [];
$this->safeLookup = [];
foreach ($safeClasses as $class => $strategies) {
$this->addSafeClass($class, $strategies);
}
}
public function addSafeClass(string $class, array $strategies)
{
$class = ltrim($class, '\\');
if (!isset($this->safeClasses[$class])) {
$this->safeClasses[$class] = [];
}
$this->safeClasses[$class] = array_merge($this->safeClasses[$class], $strategies);
foreach ($strategies as $strategy) {
$this->safeLookup[$strategy][$class] = true;
}
}
}
@ -106,6 +138,14 @@ class_alias('Twig\Extension\EscaperExtension', 'Twig_Extension_Escaper');
}
namespace {
use Twig\Environment;
use Twig\Error\RuntimeError;
use Twig\Extension\CoreExtension;
use Twig\Extension\EscaperExtension;
use Twig\Markup;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Node;
/**
* Marks a variable as being safe.
*
@ -117,4 +157,272 @@ function twig_raw_filter($string)
{
return $string;
}
/**
* Escapes a string.
*
* @param mixed $string The value to be escaped
* @param string $strategy The escaping strategy
* @param string $charset The charset
* @param bool $autoescape Whether the function is called by the auto-escaping feature (true) or by the developer (false)
*
* @return string
*/
function twig_escape_filter(Environment $env, $string, $strategy = 'html', $charset = null, $autoescape = false)
{
if ($autoescape && $string instanceof Markup) {
return $string;
}
if (!\is_string($string)) {
if (\is_object($string) && method_exists($string, '__toString')) {
if ($autoescape) {
$c = \get_class($string);
$ext = $env->getExtension(EscaperExtension::class);
if (!isset($ext->safeClasses[$c])) {
$ext->safeClasses[$c] = [];
foreach (class_parents($string) + class_implements($string) as $class) {
if (isset($ext->safeClasses[$class])) {
$ext->safeClasses[$c] = array_unique(array_merge($ext->safeClasses[$c], $ext->safeClasses[$class]));
foreach ($ext->safeClasses[$class] as $s) {
$ext->safeLookup[$s][$c] = true;
}
}
}
}
if (isset($ext->safeLookup[$strategy][$c]) || isset($ext->safeLookup['all'][$c])) {
return (string) $string;
}
}
$string = (string) $string;
} elseif (\in_array($strategy, ['html', 'js', 'css', 'html_attr', 'url'])) {
return $string;
}
}
if ('' === $string) {
return '';
}
if (null === $charset) {
$charset = $env->getCharset();
}
switch ($strategy) {
case 'html':
// see https://www.php.net/htmlspecialchars
// Using a static variable to avoid initializing the array
// each time the function is called. Moving the declaration on the
// top of the function slow downs other escaping strategies.
static $htmlspecialcharsCharsets = [
'ISO-8859-1' => true, 'ISO8859-1' => true,
'ISO-8859-15' => true, 'ISO8859-15' => true,
'utf-8' => true, 'UTF-8' => true,
'CP866' => true, 'IBM866' => true, '866' => true,
'CP1251' => true, 'WINDOWS-1251' => true, 'WIN-1251' => true,
'1251' => true,
'CP1252' => true, 'WINDOWS-1252' => true, '1252' => true,
'KOI8-R' => true, 'KOI8-RU' => true, 'KOI8R' => true,
'BIG5' => true, '950' => true,
'GB2312' => true, '936' => true,
'BIG5-HKSCS' => true,
'SHIFT_JIS' => true, 'SJIS' => true, '932' => true,
'EUC-JP' => true, 'EUCJP' => true,
'ISO8859-5' => true, 'ISO-8859-5' => true, 'MACROMAN' => true,
];
if (isset($htmlspecialcharsCharsets[$charset])) {
return htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, $charset);
}
if (isset($htmlspecialcharsCharsets[strtoupper($charset)])) {
// cache the lowercase variant for future iterations
$htmlspecialcharsCharsets[$charset] = true;
return htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, $charset);
}
$string = twig_convert_encoding($string, 'UTF-8', $charset);
$string = htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, 'UTF-8');
return iconv('UTF-8', $charset, $string);
case 'js':
// escape all non-alphanumeric characters
// into their \x or \uHHHH representations
if ('UTF-8' !== $charset) {
$string = twig_convert_encoding($string, 'UTF-8', $charset);
}
if (!preg_match('//u', $string)) {
throw new RuntimeError('The string to escape is not a valid UTF-8 string.');
}
$string = preg_replace_callback('#[^a-zA-Z0-9,\._]#Su', function ($matches) {
$char = $matches[0];
/*
* A few characters have short escape sequences in JSON and JavaScript.
* Escape sequences supported only by JavaScript, not JSON, are omitted.
* \" is also supported but omitted, because the resulting string is not HTML safe.
*/
static $shortMap = [
'\\' => '\\\\',
'/' => '\\/',
"\x08" => '\b',
"\x0C" => '\f',
"\x0A" => '\n',
"\x0D" => '\r',
"\x09" => '\t',
];
if (isset($shortMap[$char])) {
return $shortMap[$char];
}
$codepoint = mb_ord($char, 'UTF-8');
if (0x10000 > $codepoint) {
return sprintf('\u%04X', $codepoint);
}
// Split characters outside the BMP into surrogate pairs
// https://tools.ietf.org/html/rfc2781.html#section-2.1
$u = $codepoint - 0x10000;
$high = 0xD800 | ($u >> 10);
$low = 0xDC00 | ($u & 0x3FF);
return sprintf('\u%04X\u%04X', $high, $low);
}, $string);
if ('UTF-8' !== $charset) {
$string = iconv('UTF-8', $charset, $string);
}
return $string;
case 'css':
if ('UTF-8' !== $charset) {
$string = twig_convert_encoding($string, 'UTF-8', $charset);
}
if (!preg_match('//u', $string)) {
throw new RuntimeError('The string to escape is not a valid UTF-8 string.');
}
$string = preg_replace_callback('#[^a-zA-Z0-9]#Su', function ($matches) {
$char = $matches[0];
return sprintf('\\%X ', 1 === \strlen($char) ? \ord($char) : mb_ord($char, 'UTF-8'));
}, $string);
if ('UTF-8' !== $charset) {
$string = iconv('UTF-8', $charset, $string);
}
return $string;
case 'html_attr':
if ('UTF-8' !== $charset) {
$string = twig_convert_encoding($string, 'UTF-8', $charset);
}
if (!preg_match('//u', $string)) {
throw new RuntimeError('The string to escape is not a valid UTF-8 string.');
}
$string = preg_replace_callback('#[^a-zA-Z0-9,\.\-_]#Su', function ($matches) {
/**
* This function is adapted from code coming from Zend Framework.
*
* @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (https://www.zend.com)
* @license https://framework.zend.com/license/new-bsd New BSD License
*/
$chr = $matches[0];
$ord = \ord($chr);
/*
* The following replaces characters undefined in HTML with the
* hex entity for the Unicode replacement character.
*/
if (($ord <= 0x1f && "\t" != $chr && "\n" != $chr && "\r" != $chr) || ($ord >= 0x7f && $ord <= 0x9f)) {
return '&#xFFFD;';
}
/*
* Check if the current character to escape has a name entity we should
* replace it with while grabbing the hex value of the character.
*/
if (1 === \strlen($chr)) {
/*
* While HTML supports far more named entities, the lowest common denominator
* has become HTML5's XML Serialisation which is restricted to the those named
* entities that XML supports. Using HTML entities would result in this error:
* XML Parsing Error: undefined entity
*/
static $entityMap = [
34 => '&quot;', /* quotation mark */
38 => '&amp;', /* ampersand */
60 => '&lt;', /* less-than sign */
62 => '&gt;', /* greater-than sign */
];
if (isset($entityMap[$ord])) {
return $entityMap[$ord];
}
return sprintf('&#x%02X;', $ord);
}
/*
* Per OWASP recommendations, we'll use hex entities for any other
* characters where a named entity does not exist.
*/
return sprintf('&#x%04X;', mb_ord($chr, 'UTF-8'));
}, $string);
if ('UTF-8' !== $charset) {
$string = iconv('UTF-8', $charset, $string);
}
return $string;
case 'url':
return rawurlencode($string);
default:
// check the ones set on CoreExtension for BC (to be removed in 3.0)
$legacyEscapers = $env->getExtension(CoreExtension::class)->getEscapers(false);
if (array_key_exists($strategy, $legacyEscapers)) {
return $legacyEscapers[$strategy]($env, $string, $charset);
}
$escapers = $env->getExtension(EscaperExtension::class)->getEscapers();
if (array_key_exists($strategy, $escapers)) {
return $escapers[$strategy]($env, $string, $charset);
}
$escapers = array_merge($legacyEscapers, $escapers);
$validStrategies = implode(', ', array_merge(['html', 'js', 'url', 'css', 'html_attr'], array_keys($escapers)));
throw new RuntimeError(sprintf('Invalid escaping strategy "%s" (valid ones: %s).', $strategy, $validStrategies));
}
}
/**
* @internal
*/
function twig_escape_filter_is_safe(Node $filterArgs)
{
foreach ($filterArgs as $arg) {
if ($arg instanceof ConstantExpression) {
return [$arg->getAttribute('value')];
}
return [];
}
return ['html'];
}
}

View File

@ -11,7 +11,6 @@
namespace Twig\Extension;
use Twig\Environment;
use Twig\NodeVisitor\NodeVisitorInterface;
use Twig\TokenParser\TokenParserInterface;
use Twig\TwigFilter;
@ -25,15 +24,6 @@ use Twig\TwigTest;
*/
interface ExtensionInterface
{
/**
* Initializes the runtime environment.
*
* This is where you can load some file that contains filter functions for instance.
*
* @deprecated since 1.23 (to be removed in 2.0), implement \Twig_Extension_InitRuntimeInterface instead
*/
public function initRuntime(Environment $environment);
/**
* Returns the token parser instances to add to the existing list.
*
@ -75,24 +65,6 @@ interface ExtensionInterface
* @return array<array> First array of unary operators, second array of binary operators
*/
public function getOperators();
/**
* Returns a list of global variables to add to the existing list.
*
* @return array An array of global variables
*
* @deprecated since 1.23 (to be removed in 2.0), implement \Twig_Extension_GlobalsInterface instead
*/
public function getGlobals();
/**
* Returns the name of the extension.
*
* @return string The extension name
*
* @deprecated since 1.26 (to be removed in 2.0), not used anymore internally
*/
public function getName();
}
class_alias('Twig\Extension\ExtensionInterface', 'Twig_ExtensionInterface');

View File

@ -21,6 +21,12 @@ namespace Twig\Extension;
*/
interface GlobalsInterface
{
/**
* Returns a list of global variables to add to the existing list.
*
* @return array An array of global variables
*/
public function getGlobals();
}
class_alias('Twig\Extension\GlobalsInterface', 'Twig_Extension_GlobalsInterface');

View File

@ -11,6 +11,8 @@
namespace Twig\Extension;
use Twig\Environment;
/**
* Enables usage of the deprecated Twig\Extension\AbstractExtension::initRuntime() method.
*
@ -18,9 +20,17 @@ namespace Twig\Extension;
* deprecated initRuntime() method in your extensions.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @deprecated since Twig 2.7, to be removed in 3.0
*/
interface InitRuntimeInterface
{
/**
* Initializes the runtime environment.
*
* This is where you can load some file that contains filter functions for instance.
*/
public function initRuntime(Environment $environment);
}
class_alias('Twig\Extension\InitRuntimeInterface', 'Twig_Extension_InitRuntimeInterface');

View File

@ -13,12 +13,9 @@ namespace Twig\Extension;
use Twig\NodeVisitor\OptimizerNodeVisitor;
/**
* @final
*/
class OptimizerExtension extends AbstractExtension
final class OptimizerExtension extends AbstractExtension
{
protected $optimizers;
private $optimizers;
public function __construct($optimizers = -1)
{
@ -29,11 +26,6 @@ class OptimizerExtension extends AbstractExtension
{
return [new OptimizerNodeVisitor($this->optimizers)];
}
public function getName()
{
return 'optimizer';
}
}
class_alias('Twig\Extension\OptimizerExtension', 'Twig_Extension_Optimizer');

View File

@ -41,12 +41,7 @@ class ProfilerExtension extends AbstractExtension
public function getNodeVisitors()
{
return [new ProfilerNodeVisitor(\get_class($this))];
}
public function getName()
{
return 'profiler';
return [new ProfilerNodeVisitor(static::class)];
}
}

View File

@ -12,17 +12,17 @@
namespace Twig\Extension;
use Twig\NodeVisitor\SandboxNodeVisitor;
use Twig\Sandbox\SecurityNotAllowedMethodError;
use Twig\Sandbox\SecurityNotAllowedPropertyError;
use Twig\Sandbox\SecurityPolicyInterface;
use Twig\Source;
use Twig\TokenParser\SandboxTokenParser;
/**
* @final
*/
class SandboxExtension extends AbstractExtension
final class SandboxExtension extends AbstractExtension
{
protected $sandboxedGlobally;
protected $sandboxed;
protected $policy;
private $sandboxedGlobally;
private $sandboxed;
private $policy;
public function __construct(SecurityPolicyInterface $policy, $sandboxed = false)
{
@ -77,33 +77,49 @@ class SandboxExtension extends AbstractExtension
}
}
public function checkMethodAllowed($obj, $method)
public function checkMethodAllowed($obj, $method, int $lineno = -1, Source $source = null)
{
if ($this->isSandboxed()) {
$this->policy->checkMethodAllowed($obj, $method);
try {
$this->policy->checkMethodAllowed($obj, $method);
} catch (SecurityNotAllowedMethodError $e) {
$e->setSourceContext($source);
$e->setTemplateLine($lineno);
throw $e;
}
}
}
public function checkPropertyAllowed($obj, $method)
public function checkPropertyAllowed($obj, $property, int $lineno = -1, Source $source = null)
{
if ($this->isSandboxed()) {
$this->policy->checkPropertyAllowed($obj, $method);
try {
$this->policy->checkPropertyAllowed($obj, $property);
} catch (SecurityNotAllowedPropertyError $e) {
$e->setSourceContext($source);
$e->setTemplateLine($lineno);
throw $e;
}
}
}
public function ensureToStringAllowed($obj)
public function ensureToStringAllowed($obj, int $lineno = -1, Source $source = null)
{
if ($this->isSandboxed() && \is_object($obj) && method_exists($obj, '__toString')) {
$this->policy->checkMethodAllowed($obj, '__toString');
try {
$this->policy->checkMethodAllowed($obj, '__toString');
} catch (SecurityNotAllowedMethodError $e) {
$e->setSourceContext($source);
$e->setTemplateLine($lineno);
throw $e;
}
}
return $obj;
}
public function getName()
{
return 'sandbox';
}
}
class_alias('Twig\Extension\SandboxExtension', 'Twig_Extension_Sandbox');

View File

@ -13,32 +13,32 @@ namespace Twig\Extension;
use Twig\NodeVisitor\NodeVisitorInterface;
use Twig\TokenParser\TokenParserInterface;
use Twig\TwigFilter;
use Twig\TwigFunction;
use Twig\TwigTest;
/**
* Internal class.
*
* This class is used by \Twig\Environment as a staging area and must not be used directly.
* Used by \Twig\Environment as a staging area.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @internal
*/
class StagingExtension extends AbstractExtension
final class StagingExtension extends AbstractExtension
{
protected $functions = [];
protected $filters = [];
protected $visitors = [];
protected $tokenParsers = [];
protected $globals = [];
protected $tests = [];
private $functions = [];
private $filters = [];
private $visitors = [];
private $tokenParsers = [];
private $tests = [];
public function addFunction($name, $function)
public function addFunction(TwigFunction $function)
{
if (isset($this->functions[$name])) {
@trigger_error(sprintf('Overriding function "%s" that is already registered is deprecated since version 1.30 and won\'t be possible anymore in 2.0.', $name), E_USER_DEPRECATED);
if (isset($this->functions[$function->getName()])) {
throw new \LogicException(sprintf('Function "%s" is already registered.', $function->getName()));
}
$this->functions[$name] = $function;
$this->functions[$function->getName()] = $function;
}
public function getFunctions()
@ -46,13 +46,13 @@ class StagingExtension extends AbstractExtension
return $this->functions;
}
public function addFilter($name, $filter)
public function addFilter(TwigFilter $filter)
{
if (isset($this->filters[$name])) {
@trigger_error(sprintf('Overriding filter "%s" that is already registered is deprecated since version 1.30 and won\'t be possible anymore in 2.0.', $name), E_USER_DEPRECATED);
if (isset($this->filters[$filter->getName()])) {
throw new \LogicException(sprintf('Filter "%s" is already registered.', $filter->getName()));
}
$this->filters[$name] = $filter;
$this->filters[$filter->getName()] = $filter;
}
public function getFilters()
@ -73,7 +73,7 @@ class StagingExtension extends AbstractExtension
public function addTokenParser(TokenParserInterface $parser)
{
if (isset($this->tokenParsers[$parser->getTag()])) {
@trigger_error(sprintf('Overriding tag "%s" that is already registered is deprecated since version 1.30 and won\'t be possible anymore in 2.0.', $parser->getTag()), E_USER_DEPRECATED);
throw new \LogicException(sprintf('Tag "%s" is already registered.', $parser->getTag()));
}
$this->tokenParsers[$parser->getTag()] = $parser;
@ -84,34 +84,19 @@ class StagingExtension extends AbstractExtension
return $this->tokenParsers;
}
public function addGlobal($name, $value)
public function addTest(TwigTest $test)
{
$this->globals[$name] = $value;
}
public function getGlobals()
{
return $this->globals;
}
public function addTest($name, $test)
{
if (isset($this->tests[$name])) {
@trigger_error(sprintf('Overriding test "%s" that is already registered is deprecated since version 1.30 and won\'t be possible anymore in 2.0.', $name), E_USER_DEPRECATED);
if (isset($this->tests[$test->getName()])) {
throw new \LogicException(sprintf('Test "%s" is already registered.', $test->getName()));
}
$this->tests[$name] = $test;
$this->tests[$test->getName()] = $test;
}
public function getTests()
{
return $this->tests;
}
public function getName()
{
return 'staging';
}
}
class_alias('Twig\Extension\StagingExtension', 'Twig_Extension_Staging');

View File

@ -12,10 +12,7 @@
namespace Twig\Extension {
use Twig\TwigFunction;
/**
* @final
*/
class StringLoaderExtension extends AbstractExtension
final class StringLoaderExtension extends AbstractExtension
{
public function getFunctions()
{
@ -23,11 +20,6 @@ class StringLoaderExtension extends AbstractExtension
new TwigFunction('template_from_string', 'twig_template_from_string', ['needs_environment' => true]),
];
}
public function getName()
{
return 'string_loader';
}
}
class_alias('Twig\Extension\StringLoaderExtension', 'Twig_Extension_StringLoader');
@ -47,7 +39,7 @@ use Twig\TemplateWrapper;
*
* @return TemplateWrapper
*/
function twig_template_from_string(Environment $env, $template, $name = null)
function twig_template_from_string(Environment $env, $template, string $name = null)
{
return $env->createTemplate((string) $template, $name);
}

View File

@ -0,0 +1,475 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig;
use Twig\Error\RuntimeError;
use Twig\Extension\ExtensionInterface;
use Twig\Extension\GlobalsInterface;
use Twig\Extension\InitRuntimeInterface;
use Twig\Extension\StagingExtension;
use Twig\NodeVisitor\NodeVisitorInterface;
use Twig\TokenParser\TokenParserInterface;
/**
* @author Fabien Potencier <fabien@symfony.com>
*
* @internal
*/
final class ExtensionSet
{
private $extensions;
private $initialized = false;
private $runtimeInitialized = false;
private $staging;
private $parsers;
private $visitors;
private $filters;
private $tests;
private $functions;
private $unaryOperators;
private $binaryOperators;
private $globals;
private $functionCallbacks = [];
private $filterCallbacks = [];
private $lastModified = 0;
public function __construct()
{
$this->staging = new StagingExtension();
}
/**
* Initializes the runtime environment.
*
* @deprecated since Twig 2.7
*/
public function initRuntime(Environment $env)
{
if ($this->runtimeInitialized) {
return;
}
$this->runtimeInitialized = true;
foreach ($this->extensions as $extension) {
if ($extension instanceof InitRuntimeInterface) {
$extension->initRuntime($env);
}
}
}
public function hasExtension(string $class): bool
{
$class = ltrim($class, '\\');
if (!isset($this->extensions[$class]) && class_exists($class, false)) {
// For BC/FC with namespaced aliases
$class = (new \ReflectionClass($class))->name;
}
return isset($this->extensions[$class]);
}
public function getExtension(string $class): ExtensionInterface
{
$class = ltrim($class, '\\');
if (!isset($this->extensions[$class]) && class_exists($class, false)) {
// For BC/FC with namespaced aliases
$class = (new \ReflectionClass($class))->name;
}
if (!isset($this->extensions[$class])) {
throw new RuntimeError(sprintf('The "%s" extension is not enabled.', $class));
}
return $this->extensions[$class];
}
/**
* @param ExtensionInterface[] $extensions
*/
public function setExtensions(array $extensions)
{
foreach ($extensions as $extension) {
$this->addExtension($extension);
}
}
/**
* @return ExtensionInterface[]
*/
public function getExtensions(): array
{
return $this->extensions;
}
public function getSignature(): string
{
return json_encode(array_keys($this->extensions));
}
public function isInitialized(): bool
{
return $this->initialized || $this->runtimeInitialized;
}
public function getLastModified(): int
{
if (0 !== $this->lastModified) {
return $this->lastModified;
}
foreach ($this->extensions as $extension) {
$r = new \ReflectionObject($extension);
if (file_exists($r->getFileName()) && ($extensionTime = filemtime($r->getFileName())) > $this->lastModified) {
$this->lastModified = $extensionTime;
}
}
return $this->lastModified;
}
public function addExtension(ExtensionInterface $extension)
{
$class = \get_class($extension);
if ($this->initialized) {
throw new \LogicException(sprintf('Unable to register extension "%s" as extensions have already been initialized.', $class));
}
if (isset($this->extensions[$class])) {
throw new \LogicException(sprintf('Unable to register extension "%s" as it is already registered.', $class));
}
$this->extensions[$class] = $extension;
}
public function addFunction(TwigFunction $function)
{
if ($this->initialized) {
throw new \LogicException(sprintf('Unable to add function "%s" as extensions have already been initialized.', $function->getName()));
}
$this->staging->addFunction($function);
}
/**
* @return TwigFunction[]
*/
public function getFunctions(): array
{
if (!$this->initialized) {
$this->initExtensions();
}
return $this->functions;
}
/**
* @return TwigFunction|false
*/
public function getFunction(string $name)
{
if (!$this->initialized) {
$this->initExtensions();
}
if (isset($this->functions[$name])) {
return $this->functions[$name];
}
foreach ($this->functions as $pattern => $function) {
$pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count);
if ($count && preg_match('#^'.$pattern.'$#', $name, $matches)) {
array_shift($matches);
$function->setArguments($matches);
return $function;
}
}
foreach ($this->functionCallbacks as $callback) {
if (false !== $function = $callback($name)) {
return $function;
}
}
return false;
}
public function registerUndefinedFunctionCallback(callable $callable)
{
$this->functionCallbacks[] = $callable;
}
public function addFilter(TwigFilter $filter)
{
if ($this->initialized) {
throw new \LogicException(sprintf('Unable to add filter "%s" as extensions have already been initialized.', $filter->getName()));
}
$this->staging->addFilter($filter);
}
/**
* @return TwigFilter[]
*/
public function getFilters(): array
{
if (!$this->initialized) {
$this->initExtensions();
}
return $this->filters;
}
/**
* @return TwigFilter|false
*/
public function getFilter(string $name)
{
if (!$this->initialized) {
$this->initExtensions();
}
if (isset($this->filters[$name])) {
return $this->filters[$name];
}
foreach ($this->filters as $pattern => $filter) {
$pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count);
if ($count && preg_match('#^'.$pattern.'$#', $name, $matches)) {
array_shift($matches);
$filter->setArguments($matches);
return $filter;
}
}
foreach ($this->filterCallbacks as $callback) {
if (false !== $filter = $callback($name)) {
return $filter;
}
}
return false;
}
public function registerUndefinedFilterCallback(callable $callable)
{
$this->filterCallbacks[] = $callable;
}
public function addNodeVisitor(NodeVisitorInterface $visitor)
{
if ($this->initialized) {
throw new \LogicException('Unable to add a node visitor as extensions have already been initialized.');
}
$this->staging->addNodeVisitor($visitor);
}
/**
* @return NodeVisitorInterface[]
*/
public function getNodeVisitors(): array
{
if (!$this->initialized) {
$this->initExtensions();
}
return $this->visitors;
}
public function addTokenParser(TokenParserInterface $parser)
{
if ($this->initialized) {
throw new \LogicException('Unable to add a token parser as extensions have already been initialized.');
}
$this->staging->addTokenParser($parser);
}
/**
* @return TokenParserInterface[]
*/
public function getTokenParsers(): array
{
if (!$this->initialized) {
$this->initExtensions();
}
return $this->parsers;
}
public function getGlobals(): array
{
if (null !== $this->globals) {
return $this->globals;
}
$globals = [];
foreach ($this->extensions as $extension) {
if (!$extension instanceof GlobalsInterface) {
continue;
}
$extGlobals = $extension->getGlobals();
if (!\is_array($extGlobals)) {
throw new \UnexpectedValueException(sprintf('"%s::getGlobals()" must return an array of globals.', \get_class($extension)));
}
$globals = array_merge($globals, $extGlobals);
}
if ($this->initialized) {
$this->globals = $globals;
}
return $globals;
}
public function addTest(TwigTest $test)
{
if ($this->initialized) {
throw new \LogicException(sprintf('Unable to add test "%s" as extensions have already been initialized.', $test->getName()));
}
$this->staging->addTest($test);
}
/**
* @return TwigTest[]
*/
public function getTests(): array
{
if (!$this->initialized) {
$this->initExtensions();
}
return $this->tests;
}
/**
* @return TwigTest|false
*/
public function getTest(string $name)
{
if (!$this->initialized) {
$this->initExtensions();
}
if (isset($this->tests[$name])) {
return $this->tests[$name];
}
foreach ($this->tests as $pattern => $test) {
$pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count);
if ($count) {
if (preg_match('#^'.$pattern.'$#', $name, $matches)) {
array_shift($matches);
$test->setArguments($matches);
return $test;
}
}
}
return false;
}
public function getUnaryOperators(): array
{
if (!$this->initialized) {
$this->initExtensions();
}
return $this->unaryOperators;
}
public function getBinaryOperators(): array
{
if (!$this->initialized) {
$this->initExtensions();
}
return $this->binaryOperators;
}
private function initExtensions()
{
$this->parsers = [];
$this->filters = [];
$this->functions = [];
$this->tests = [];
$this->visitors = [];
$this->unaryOperators = [];
$this->binaryOperators = [];
foreach ($this->extensions as $extension) {
$this->initExtension($extension);
}
$this->initExtension($this->staging);
// Done at the end only, so that an exception during initialization does not mark the environment as initialized when catching the exception
$this->initialized = true;
}
private function initExtension(ExtensionInterface $extension)
{
// filters
foreach ($extension->getFilters() as $filter) {
$this->filters[$filter->getName()] = $filter;
}
// functions
foreach ($extension->getFunctions() as $function) {
$this->functions[$function->getName()] = $function;
}
// tests
foreach ($extension->getTests() as $test) {
$this->tests[$test->getName()] = $test;
}
// token parsers
foreach ($extension->getTokenParsers() as $parser) {
if (!$parser instanceof TokenParserInterface) {
throw new \LogicException('getTokenParsers() must return an array of \Twig\TokenParser\TokenParserInterface.');
}
$this->parsers[] = $parser;
}
// node visitors
foreach ($extension->getNodeVisitors() as $visitor) {
$this->visitors[] = $visitor;
}
// operators
if ($operators = $extension->getOperators()) {
if (!\is_array($operators)) {
throw new \InvalidArgumentException(sprintf('"%s::getOperators()" must return an array with operators, got "%s".', \get_class($extension), \is_object($operators) ? \get_class($operators) : \gettype($operators).(\is_resource($operators) ? '' : '#'.$operators)));
}
if (2 !== \count($operators)) {
throw new \InvalidArgumentException(sprintf('"%s::getOperators()" must return an array of 2 elements, got %d.', \get_class($extension), \count($operators)));
}
$this->unaryOperators = array_merge($this->unaryOperators, $operators[0]);
$this->binaryOperators = array_merge($this->binaryOperators, $operators[1]);
}
}
}
class_alias('Twig\ExtensionSet', 'Twig_ExtensionSet');

View File

@ -41,7 +41,7 @@ class FileExtensionEscapingStrategy
$name = substr($name, 0, -5);
}
$extension = pathinfo($name, PATHINFO_EXTENSION);
$extension = pathinfo($name, \PATHINFO_EXTENSION);
switch ($extension) {
case 'js':

View File

@ -19,39 +19,36 @@ use Twig\Error\SyntaxError;
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Lexer implements \Twig_LexerInterface
class Lexer
{
protected $tokens;
protected $code;
protected $cursor;
protected $lineno;
protected $end;
protected $state;
protected $states;
protected $brackets;
protected $env;
// to be renamed to $name in 2.0 (where it is private)
protected $filename;
protected $options;
protected $regexes;
protected $position;
protected $positions;
protected $currentVarBlockLine;
private $tokens;
private $code;
private $cursor;
private $lineno;
private $end;
private $state;
private $states;
private $brackets;
private $env;
private $source;
private $options;
private $regexes;
private $position;
private $positions;
private $currentVarBlockLine;
const STATE_DATA = 0;
const STATE_BLOCK = 1;
const STATE_VAR = 2;
const STATE_STRING = 3;
const STATE_INTERPOLATION = 4;
public const STATE_DATA = 0;
public const STATE_BLOCK = 1;
public const STATE_VAR = 2;
public const STATE_STRING = 3;
public const STATE_INTERPOLATION = 4;
const REGEX_NAME = '/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A';
const REGEX_NUMBER = '/[0-9]+(?:\.[0-9]+)?([Ee][\+\-][0-9]+)?/A';
const REGEX_STRING = '/"([^#"\\\\]*(?:\\\\.[^#"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As';
const REGEX_DQ_STRING_DELIM = '/"/A';
const REGEX_DQ_STRING_PART = '/[^#"\\\\]*(?:(?:\\\\.|#(?!\{))[^#"\\\\]*)*/As';
const PUNCTUATION = '()[]{}?:.,|';
public const REGEX_NAME = '/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A';
public const REGEX_NUMBER = '/[0-9]+(?:\.[0-9]+)?([Ee][\+\-][0-9]+)?/A';
public const REGEX_STRING = '/"([^#"\\\\]*(?:\\\\.[^#"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As';
public const REGEX_DQ_STRING_DELIM = '/"/A';
public const REGEX_DQ_STRING_PART = '/[^#"\\\\]*(?:(?:\\\\.|#(?!\{))[^#"\\\\]*)*/As';
public const PUNCTUATION = '()[]{}?:.,|';
public function __construct(Environment $env, array $options = [])
{
@ -100,9 +97,7 @@ class Lexer implements \Twig_LexerInterface
$this->options['whitespace_trim']. // -
'|'.
$this->options['whitespace_line_trim']. // ~
')?\s*'.
'(?:end%s)'. // endraw or endverbatim
'\s*'.
')?\s*endverbatim\s*'.
'(?:'.
preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '#').'\s*'. // -%}
'|'.
@ -117,7 +112,7 @@ class Lexer implements \Twig_LexerInterface
// #}
'lex_comment' => '{
(?:'.
preg_quote($this->options['whitespace_trim']).preg_quote($this->options['tag_comment'][1], '#').'\s*\n?'. // -#}\s*\n?
preg_quote($this->options['whitespace_trim'].$this->options['tag_comment'][1], '#').'\s*\n?'. // -#}\s*\n?
'|'.
preg_quote($this->options['whitespace_line_trim'].$this->options['tag_comment'][1], '#').'['.$this->options['whitespace_line_chars'].']*'. // ~#}[ \t\0\x0B]*
'|'.
@ -127,9 +122,7 @@ class Lexer implements \Twig_LexerInterface
// verbatim %}
'lex_block_raw' => '{
\s*
(raw|verbatim)
\s*
\s*verbatim\s*
(?:'.
preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '#').'\s*'. // -%}\s*
'|'.
@ -160,28 +153,10 @@ class Lexer implements \Twig_LexerInterface
];
}
public function tokenize($code, $name = null)
public function tokenize(Source $source)
{
if (!$code instanceof Source) {
@trigger_error(sprintf('Passing a string as the $code argument of %s() is deprecated since version 1.27 and will be removed in 2.0. Pass a \Twig\Source instance instead.', __METHOD__), E_USER_DEPRECATED);
$this->source = new Source($code, $name);
} else {
$this->source = $code;
}
if (((int) ini_get('mbstring.func_overload')) & 2) {
@trigger_error('Support for having "mbstring.func_overload" different from 0 is deprecated version 1.29 and will be removed in 2.0.', E_USER_DEPRECATED);
}
if (\function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) {
$mbEncoding = mb_internal_encoding();
mb_internal_encoding('ASCII');
} else {
$mbEncoding = null;
}
$this->code = str_replace(["\r\n", "\r"], "\n", $this->source->getCode());
$this->filename = $this->source->getName();
$this->source = $source;
$this->code = str_replace(["\r\n", "\r"], "\n", $source->getCode());
$this->cursor = 0;
$this->lineno = 1;
$this->end = \strlen($this->code);
@ -192,7 +167,7 @@ class Lexer implements \Twig_LexerInterface
$this->position = -1;
// find all token starts in one go
preg_match_all($this->regexes['lex_tokens_start'], $this->code, $matches, PREG_OFFSET_CAPTURE);
preg_match_all($this->regexes['lex_tokens_start'], $this->code, $matches, \PREG_OFFSET_CAPTURE);
$this->positions = $matches;
while ($this->cursor < $this->end) {
@ -221,25 +196,21 @@ class Lexer implements \Twig_LexerInterface
}
}
$this->pushToken(Token::EOF_TYPE);
$this->pushToken(/* Token::EOF_TYPE */ -1);
if (!empty($this->brackets)) {
list($expect, $lineno) = array_pop($this->brackets);
throw new SyntaxError(sprintf('Unclosed "%s".', $expect), $lineno, $this->source);
}
if ($mbEncoding) {
mb_internal_encoding($mbEncoding);
}
return new TokenStream($this->tokens, $this->source);
}
protected function lexData()
private function lexData()
{
// if no matches are left we return the rest of the template as simple text token
if ($this->position == \count($this->positions[0]) - 1) {
$this->pushToken(Token::TEXT_TYPE, substr($this->code, $this->cursor));
$this->pushToken(/* Token::TEXT_TYPE */ 0, substr($this->code, $this->cursor));
$this->cursor = $this->end;
return;
@ -268,7 +239,7 @@ class Lexer implements \Twig_LexerInterface
$text = rtrim($text, " \t\0\x0B");
}
}
$this->pushToken(Token::TEXT_TYPE, $text);
$this->pushToken(/* Token::TEXT_TYPE */ 0, $text);
$this->moveCursor($textContent.$position[0]);
switch ($this->positions[1][$this->position][0]) {
@ -280,30 +251,30 @@ class Lexer implements \Twig_LexerInterface
// raw data?
if (preg_match($this->regexes['lex_block_raw'], $this->code, $match, 0, $this->cursor)) {
$this->moveCursor($match[0]);
$this->lexRawData($match[1]);
$this->lexRawData();
// {% line \d+ %}
} elseif (preg_match($this->regexes['lex_block_line'], $this->code, $match, 0, $this->cursor)) {
$this->moveCursor($match[0]);
$this->lineno = (int) $match[1];
} else {
$this->pushToken(Token::BLOCK_START_TYPE);
$this->pushToken(/* Token::BLOCK_START_TYPE */ 1);
$this->pushState(self::STATE_BLOCK);
$this->currentVarBlockLine = $this->lineno;
}
break;
case $this->options['tag_variable'][0]:
$this->pushToken(Token::VAR_START_TYPE);
$this->pushToken(/* Token::VAR_START_TYPE */ 2);
$this->pushState(self::STATE_VAR);
$this->currentVarBlockLine = $this->lineno;
break;
}
}
protected function lexBlock()
private function lexBlock()
{
if (empty($this->brackets) && preg_match($this->regexes['lex_block'], $this->code, $match, 0, $this->cursor)) {
$this->pushToken(Token::BLOCK_END_TYPE);
$this->pushToken(/* Token::BLOCK_END_TYPE */ 3);
$this->moveCursor($match[0]);
$this->popState();
} else {
@ -311,10 +282,10 @@ class Lexer implements \Twig_LexerInterface
}
}
protected function lexVar()
private function lexVar()
{
if (empty($this->brackets) && preg_match($this->regexes['lex_var'], $this->code, $match, 0, $this->cursor)) {
$this->pushToken(Token::VAR_END_TYPE);
$this->pushToken(/* Token::VAR_END_TYPE */ 4);
$this->moveCursor($match[0]);
$this->popState();
} else {
@ -322,7 +293,7 @@ class Lexer implements \Twig_LexerInterface
}
}
protected function lexExpression()
private function lexExpression()
{
// whitespace
if (preg_match('/\s+/A', $this->code, $match, 0, $this->cursor)) {
@ -340,21 +311,21 @@ class Lexer implements \Twig_LexerInterface
}
// operators
elseif (preg_match($this->regexes['operator'], $this->code, $match, 0, $this->cursor)) {
$this->pushToken(Token::OPERATOR_TYPE, preg_replace('/\s+/', ' ', $match[0]));
$this->pushToken(/* Token::OPERATOR_TYPE */ 8, preg_replace('/\s+/', ' ', $match[0]));
$this->moveCursor($match[0]);
}
// names
elseif (preg_match(self::REGEX_NAME, $this->code, $match, 0, $this->cursor)) {
$this->pushToken(Token::NAME_TYPE, $match[0]);
$this->pushToken(/* Token::NAME_TYPE */ 5, $match[0]);
$this->moveCursor($match[0]);
}
// numbers
elseif (preg_match(self::REGEX_NUMBER, $this->code, $match, 0, $this->cursor)) {
$number = (float) $match[0]; // floats
if (ctype_digit($match[0]) && $number <= PHP_INT_MAX) {
if (ctype_digit($match[0]) && $number <= \PHP_INT_MAX) {
$number = (int) $match[0]; // integers lower than the maximum
}
$this->pushToken(Token::NUMBER_TYPE, $number);
$this->pushToken(/* Token::NUMBER_TYPE */ 6, $number);
$this->moveCursor($match[0]);
}
// punctuation
@ -375,12 +346,12 @@ class Lexer implements \Twig_LexerInterface
}
}
$this->pushToken(Token::PUNCTUATION_TYPE, $this->code[$this->cursor]);
$this->pushToken(/* Token::PUNCTUATION_TYPE */ 9, $this->code[$this->cursor]);
++$this->cursor;
}
// strings
elseif (preg_match(self::REGEX_STRING, $this->code, $match, 0, $this->cursor)) {
$this->pushToken(Token::STRING_TYPE, stripcslashes(substr($match[0], 1, -1)));
$this->pushToken(/* Token::STRING_TYPE */ 7, stripcslashes(substr($match[0], 1, -1)));
$this->moveCursor($match[0]);
}
// opening double quoted string
@ -395,14 +366,10 @@ class Lexer implements \Twig_LexerInterface
}
}
protected function lexRawData($tag)
private function lexRawData()
{
if ('raw' === $tag) {
@trigger_error(sprintf('Twig Tag "raw" is deprecated since version 1.21. Use "verbatim" instead in %s at line %d.', $this->filename, $this->lineno), E_USER_DEPRECATED);
}
if (!preg_match(str_replace('%s', $tag, $this->regexes['lex_raw_data']), $this->code, $match, PREG_OFFSET_CAPTURE, $this->cursor)) {
throw new SyntaxError(sprintf('Unexpected end of file: Unclosed "%s" block.', $tag), $this->lineno, $this->source);
if (!preg_match($this->regexes['lex_raw_data'], $this->code, $match, \PREG_OFFSET_CAPTURE, $this->cursor)) {
throw new SyntaxError('Unexpected end of file: Unclosed "verbatim" block.', $this->lineno, $this->source);
}
$text = substr($this->code, $this->cursor, $match[0][1] - $this->cursor);
@ -420,27 +387,27 @@ class Lexer implements \Twig_LexerInterface
}
}
$this->pushToken(Token::TEXT_TYPE, $text);
$this->pushToken(/* Token::TEXT_TYPE */ 0, $text);
}
protected function lexComment()
private function lexComment()
{
if (!preg_match($this->regexes['lex_comment'], $this->code, $match, PREG_OFFSET_CAPTURE, $this->cursor)) {
if (!preg_match($this->regexes['lex_comment'], $this->code, $match, \PREG_OFFSET_CAPTURE, $this->cursor)) {
throw new SyntaxError('Unclosed comment.', $this->lineno, $this->source);
}
$this->moveCursor(substr($this->code, $this->cursor, $match[0][1] - $this->cursor).$match[0][0]);
}
protected function lexString()
private function lexString()
{
if (preg_match($this->regexes['interpolation_start'], $this->code, $match, 0, $this->cursor)) {
$this->brackets[] = [$this->options['interpolation'][0], $this->lineno];
$this->pushToken(Token::INTERPOLATION_START_TYPE);
$this->pushToken(/* Token::INTERPOLATION_START_TYPE */ 10);
$this->moveCursor($match[0]);
$this->pushState(self::STATE_INTERPOLATION);
} elseif (preg_match(self::REGEX_DQ_STRING_PART, $this->code, $match, 0, $this->cursor) && \strlen($match[0]) > 0) {
$this->pushToken(Token::STRING_TYPE, stripcslashes($match[0]));
$this->pushToken(/* Token::STRING_TYPE */ 7, stripcslashes($match[0]));
$this->moveCursor($match[0]);
} elseif (preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, 0, $this->cursor)) {
list($expect, $lineno) = array_pop($this->brackets);
@ -456,12 +423,12 @@ class Lexer implements \Twig_LexerInterface
}
}
protected function lexInterpolation()
private function lexInterpolation()
{
$bracket = end($this->brackets);
if ($this->options['interpolation'][0] === $bracket[0] && preg_match($this->regexes['interpolation_end'], $this->code, $match, 0, $this->cursor)) {
array_pop($this->brackets);
$this->pushToken(Token::INTERPOLATION_END_TYPE);
$this->pushToken(/* Token::INTERPOLATION_END_TYPE */ 11);
$this->moveCursor($match[0]);
$this->popState();
} else {
@ -469,23 +436,23 @@ class Lexer implements \Twig_LexerInterface
}
}
protected function pushToken($type, $value = '')
private function pushToken($type, $value = '')
{
// do not push empty text tokens
if (Token::TEXT_TYPE === $type && '' === $value) {
if (/* Token::TEXT_TYPE */ 0 === $type && '' === $value) {
return;
}
$this->tokens[] = new Token($type, $value, $this->lineno);
}
protected function moveCursor($text)
private function moveCursor($text)
{
$this->cursor += \strlen($text);
$this->lineno += substr_count($text, "\n");
}
protected function getOperatorRegex()
private function getOperatorRegex()
{
$operators = array_merge(
['='],
@ -499,11 +466,15 @@ class Lexer implements \Twig_LexerInterface
$regex = [];
foreach ($operators as $operator => $length) {
// an operator that ends with a character must be followed by
// a whitespace or a parenthesis
// a whitespace, a parenthesis, an opening map [ or sequence {
$r = preg_quote($operator, '/');
if (ctype_alpha($operator[$length - 1])) {
$r = preg_quote($operator, '/').'(?=[\s()])';
} else {
$r = preg_quote($operator, '/');
$r .= '(?=[\s()\[{])';
}
// an operator that begins with a character must not have a dot or pipe before
if (ctype_alpha($operator[0])) {
$r = '(?<![\.\|])'.$r;
}
// an operator with a space can be any amount of whitespaces
@ -515,13 +486,13 @@ class Lexer implements \Twig_LexerInterface
return '/'.implode('|', $regex).'/A';
}
protected function pushState($state)
private function pushState($state)
{
$this->states[] = $this->state;
$this->state = $state;
}
protected function popState()
private function popState()
{
if (0 === \count($this->states)) {
throw new \LogicException('Cannot pop state without a previous state.');

View File

@ -24,13 +24,11 @@ use Twig\Source;
*
* This loader should only be used for unit testing.
*
* @final
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ArrayLoader implements LoaderInterface, ExistsLoaderInterface, SourceContextLoaderInterface
final class ArrayLoader implements LoaderInterface, ExistsLoaderInterface, SourceContextLoaderInterface
{
protected $templates = [];
private $templates = [];
/**
* @param array $templates An array of templates (keys are the names, and values are the source code)
@ -48,19 +46,7 @@ class ArrayLoader implements LoaderInterface, ExistsLoaderInterface, SourceConte
*/
public function setTemplate($name, $template)
{
$this->templates[(string) $name] = $template;
}
public function getSource($name)
{
@trigger_error(sprintf('Calling "getSource" on "%s" is deprecated since 1.27. Use getSourceContext() instead.', \get_class($this)), E_USER_DEPRECATED);
$name = (string) $name;
if (!isset($this->templates[$name])) {
throw new LoaderError(sprintf('Template "%s" is not defined.', $name));
}
return $this->templates[$name];
$this->templates[$name] = $template;
}
public function getSourceContext($name)
@ -75,12 +61,11 @@ class ArrayLoader implements LoaderInterface, ExistsLoaderInterface, SourceConte
public function exists($name)
{
return isset($this->templates[(string) $name]);
return isset($this->templates[$name]);
}
public function getCacheKey($name)
{
$name = (string) $name;
if (!isset($this->templates[$name])) {
throw new LoaderError(sprintf('Template "%s" is not defined.', $name));
}
@ -90,7 +75,6 @@ class ArrayLoader implements LoaderInterface, ExistsLoaderInterface, SourceConte
public function isFresh($name, $time)
{
$name = (string) $name;
if (!isset($this->templates[$name])) {
throw new LoaderError(sprintf('Template "%s" is not defined.', $name));
}

View File

@ -12,19 +12,16 @@
namespace Twig\Loader;
use Twig\Error\LoaderError;
use Twig\Source;
/**
* Loads templates from other loaders.
*
* @final
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ChainLoader implements LoaderInterface, ExistsLoaderInterface, SourceContextLoaderInterface
final class ChainLoader implements LoaderInterface, ExistsLoaderInterface, SourceContextLoaderInterface
{
private $hasSourceCache = [];
protected $loaders = [];
private $loaders = [];
/**
* @param LoaderInterface[] $loaders
@ -50,40 +47,16 @@ class ChainLoader implements LoaderInterface, ExistsLoaderInterface, SourceConte
return $this->loaders;
}
public function getSource($name)
{
@trigger_error(sprintf('Calling "getSource" on "%s" is deprecated since 1.27. Use getSourceContext() instead.', \get_class($this)), E_USER_DEPRECATED);
$exceptions = [];
foreach ($this->loaders as $loader) {
if ($loader instanceof ExistsLoaderInterface && !$loader->exists($name)) {
continue;
}
try {
return $loader->getSource($name);
} catch (LoaderError $e) {
$exceptions[] = $e->getMessage();
}
}
throw new LoaderError(sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : ''));
}
public function getSourceContext($name)
{
$exceptions = [];
foreach ($this->loaders as $loader) {
if ($loader instanceof ExistsLoaderInterface && !$loader->exists($name)) {
if (!$loader->exists($name)) {
continue;
}
try {
if ($loader instanceof SourceContextLoaderInterface) {
return $loader->getSourceContext($name);
}
return new Source($loader->getSource($name), $name);
return $loader->getSourceContext($name);
} catch (LoaderError $e) {
$exceptions[] = $e->getMessage();
}
@ -94,30 +67,13 @@ class ChainLoader implements LoaderInterface, ExistsLoaderInterface, SourceConte
public function exists($name)
{
$name = (string) $name;
if (isset($this->hasSourceCache[$name])) {
return $this->hasSourceCache[$name];
}
foreach ($this->loaders as $loader) {
if ($loader instanceof ExistsLoaderInterface) {
if ($loader->exists($name)) {
return $this->hasSourceCache[$name] = true;
}
continue;
}
try {
if ($loader instanceof SourceContextLoaderInterface) {
$loader->getSourceContext($name);
} else {
$loader->getSource($name);
}
if ($loader->exists($name)) {
return $this->hasSourceCache[$name] = true;
} catch (LoaderError $e) {
}
}
@ -128,7 +84,7 @@ class ChainLoader implements LoaderInterface, ExistsLoaderInterface, SourceConte
{
$exceptions = [];
foreach ($this->loaders as $loader) {
if ($loader instanceof ExistsLoaderInterface && !$loader->exists($name)) {
if (!$loader->exists($name)) {
continue;
}
@ -146,7 +102,7 @@ class ChainLoader implements LoaderInterface, ExistsLoaderInterface, SourceConte
{
$exceptions = [];
foreach ($this->loaders as $loader) {
if ($loader instanceof ExistsLoaderInterface && !$loader->exists($name)) {
if (!$loader->exists($name)) {
continue;
}

View File

@ -12,22 +12,12 @@
namespace Twig\Loader;
/**
* Adds an exists() method for loaders.
* Empty interface for Twig 1.x compatibility.
*
* @author Florin Patan <florinpatan@gmail.com>
*
* @deprecated since 1.12 (to be removed in 3.0)
* @deprecated since Twig 2.7, to be removed in 3.0
*/
interface ExistsLoaderInterface
interface ExistsLoaderInterface extends LoaderInterface
{
/**
* Check if we have the source code of a template, given its name.
*
* @param string $name The name of the template to check if we can load
*
* @return bool If the template source code is handled by this loader or not
*/
public function exists($name);
}
class_alias('Twig\Loader\ExistsLoaderInterface', 'Twig_ExistsLoaderInterface');

View File

@ -22,7 +22,7 @@ use Twig\Source;
class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, SourceContextLoaderInterface
{
/** Identifier of the main namespace. */
const MAIN_NAMESPACE = '__main__';
public const MAIN_NAMESPACE = '__main__';
protected $paths = [];
protected $cache = [];
@ -34,10 +34,10 @@ class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, Source
* @param string|array $paths A path or an array of paths where to look for templates
* @param string|null $rootPath The root path common to all relative paths (null for getcwd())
*/
public function __construct($paths = [], $rootPath = null)
public function __construct($paths = [], string $rootPath = null)
{
$this->rootPath = (null === $rootPath ? getcwd() : $rootPath).\DIRECTORY_SEPARATOR;
if (false !== $realPath = realpath($rootPath)) {
if (null !== $rootPath && false !== ($realPath = realpath($rootPath))) {
$this->rootPath = $realPath.\DIRECTORY_SEPARATOR;
}
@ -136,17 +136,6 @@ class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, Source
}
}
public function getSource($name)
{
@trigger_error(sprintf('Calling "getSource" on "%s" is deprecated since 1.27. Use getSourceContext() instead.', \get_class($this)), E_USER_DEPRECATED);
if (null === ($path = $this->findTemplate($name)) || false === $path) {
return '';
}
return file_get_contents($path);
}
public function getSourceContext($name)
{
if (null === ($path = $this->findTemplate($name)) || false === $path) {
@ -177,13 +166,7 @@ class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, Source
return true;
}
try {
return null !== ($path = $this->findTemplate($name, false)) && false !== $path;
} catch (LoaderError $e) {
@trigger_error(sprintf('In %s::findTemplate(), you must accept a second argument that when set to "false" returns "false" instead of throwing an exception. Not supporting this argument is deprecated since version 1.27.', \get_class($this)), E_USER_DEPRECATED);
return false;
}
return null !== ($path = $this->findTemplate($name, false)) && false !== $path;
}
public function isFresh($name, $time)
@ -199,13 +182,15 @@ class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, Source
/**
* Checks if the template can be found.
*
* @param string $name The template name
* In Twig 3.0, findTemplate must return a string or null (returning false won't work anymore).
*
* @param string $name The template name
* @param bool $throw Whether to throw an exception when an error occurs
*
* @return string|false|null The template name or false/null
*/
protected function findTemplate($name)
protected function findTemplate($name, $throw = true)
{
$throw = \func_num_args() > 1 ? func_get_arg(1) : true;
$name = $this->normalizeName($name);
if (isset($this->cache[$name])) {
@ -221,9 +206,9 @@ class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, Source
}
try {
$this->validateName($name);
list($namespace, $shortname) = $this->parseName($name);
$this->validateName($shortname);
} catch (LoaderError $e) {
if (!$throw) {
return false;
@ -265,7 +250,12 @@ class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, Source
throw new LoaderError($this->errorCache[$name]);
}
protected function parseName($name, $default = self::MAIN_NAMESPACE)
private function normalizeName($name)
{
return preg_replace('#/{2,}#', '/', str_replace('\\', '/', (string) $name));
}
private function parseName($name, $default = self::MAIN_NAMESPACE)
{
if (isset($name[0]) && '@' == $name[0]) {
if (false === $pos = strpos($name, '/')) {
@ -281,12 +271,7 @@ class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, Source
return [$default, $name];
}
protected function normalizeName($name)
{
return preg_replace('#/{2,}#', '/', str_replace('\\', '/', (string) $name));
}
protected function validateName($name)
private function validateName($name)
{
if (false !== strpos($name, "\0")) {
throw new LoaderError('A template name cannot contain NUL bytes.');
@ -312,10 +297,10 @@ class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, Source
{
return strspn($file, '/\\', 0, 1)
|| (\strlen($file) > 3 && ctype_alpha($file[0])
&& ':' === substr($file, 1, 1)
&& ':' === $file[1]
&& strspn($file, '/\\', 2, 1)
)
|| null !== parse_url($file, PHP_URL_SCHEME)
|| null !== parse_url($file, \PHP_URL_SCHEME)
;
}
}

View File

@ -12,6 +12,7 @@
namespace Twig\Loader;
use Twig\Error\LoaderError;
use Twig\Source;
/**
* Interface all loaders must implement.
@ -21,17 +22,15 @@ use Twig\Error\LoaderError;
interface LoaderInterface
{
/**
* Gets the source code of a template, given its name.
* Returns the source context for a given template logical name.
*
* @param string $name The name of the template to load
* @param string $name The template logical name
*
* @return string The template source code
* @return Source
*
* @throws LoaderError When $name is not found
*
* @deprecated since 1.27 (to be removed in 2.0), implement Twig\Loader\SourceContextLoaderInterface
*/
public function getSource($name);
public function getSourceContext($name);
/**
* Gets the cache key to use for the cache for a given template name.
@ -56,6 +55,15 @@ interface LoaderInterface
* @throws LoaderError When $name is not found
*/
public function isFresh($name, $time);
/**
* Check if we have the source code of a template, given its name.
*
* @param string $name The name of the template to check if we can load
*
* @return bool If the template source code is handled by this loader or not
*/
public function exists($name);
}
class_alias('Twig\Loader\LoaderInterface', 'Twig_LoaderInterface');

View File

@ -11,28 +11,11 @@
namespace Twig\Loader;
use Twig\Error\LoaderError;
use Twig\Source;
/**
* Adds a getSourceContext() method for loaders.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @deprecated since 1.27 (to be removed in 3.0)
* Empty interface for Twig 1.x compatibility.
*/
interface SourceContextLoaderInterface
interface SourceContextLoaderInterface extends LoaderInterface
{
/**
* Returns the source context for a given template logical name.
*
* @param string $name The template logical name
*
* @return Source
*
* @throws LoaderError When $name is not found
*/
public function getSourceContext($name);
}
class_alias('Twig\Loader\SourceContextLoaderInterface', 'Twig_SourceContextLoaderInterface');

View File

@ -16,10 +16,10 @@ namespace Twig;
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Markup implements \Countable
class Markup implements \Countable, \JsonSerializable
{
protected $content;
protected $charset;
private $content;
private $charset;
public function __construct($content, $charset)
{
@ -32,9 +32,22 @@ class Markup implements \Countable
return $this->content;
}
/**
* @return int
*/
#[\ReturnTypeWillChange]
public function count()
{
return \function_exists('mb_get_info') ? mb_strlen($this->content, $this->charset) : \strlen($this->content);
return mb_strlen($this->content, $this->charset);
}
/**
* @return mixed
*/
#[\ReturnTypeWillChange]
public function jsonSerialize()
{
return $this->content;
}
}

View File

@ -26,7 +26,7 @@ use Twig\Compiler;
*/
class AutoEscapeNode extends Node
{
public function __construct($value, \Twig_NodeInterface $body, $lineno, $tag = 'autoescape')
public function __construct($value, Node $body, int $lineno, string $tag = 'autoescape')
{
parent::__construct(['body' => $body], ['value' => $value], $lineno, $tag);
}

View File

@ -21,7 +21,7 @@ use Twig\Compiler;
*/
class BlockNode extends Node
{
public function __construct($name, \Twig_NodeInterface $body, $lineno, $tag = null)
public function __construct(string $name, Node $body, int $lineno, string $tag = null)
{
parent::__construct(['body' => $body], ['name' => $name], $lineno, $tag);
}
@ -32,6 +32,7 @@ class BlockNode extends Node
->addDebugInfo($this)
->write(sprintf("public function block_%s(\$context, array \$blocks = [])\n", $this->getAttribute('name')), "{\n")
->indent()
->write("\$macros = \$this->macros;\n")
;
$compiler

View File

@ -21,7 +21,7 @@ use Twig\Compiler;
*/
class BlockReferenceNode extends Node implements NodeOutputInterface
{
public function __construct($name, $lineno, $tag = null)
public function __construct(string $name, int $lineno, string $tag = null)
{
parent::__construct([], ['name' => $name], $lineno, $tag);
}

View File

@ -0,0 +1,28 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node;
use Twig\Compiler;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
class CheckSecurityCallNode extends Node
{
public function compile(Compiler $compiler)
{
$compiler
->write("\$this->sandbox = \$this->env->getExtension('\Twig\Extension\SandboxExtension');\n")
->write("\$this->checkSecurity();\n")
;
}
}

View File

@ -18,9 +18,9 @@ use Twig\Compiler;
*/
class CheckSecurityNode extends Node
{
protected $usedFilters;
protected $usedTags;
protected $usedFunctions;
private $usedFilters;
private $usedTags;
private $usedFunctions;
public function __construct(array $usedFilters, array $usedTags, array $usedFunctions)
{
@ -45,10 +45,13 @@ class CheckSecurityNode extends Node
}
$compiler
->write("\$this->sandbox = \$this->env->getExtension('\Twig\Extension\SandboxExtension');\n")
->write('$tags = ')->repr(array_filter($tags))->raw(";\n")
->write('$filters = ')->repr(array_filter($filters))->raw(";\n")
->write('$functions = ')->repr(array_filter($functions))->raw(";\n\n")
->write("\n")
->write("public function checkSecurity()\n")
->write("{\n")
->indent()
->write('static $tags = ')->repr(array_filter($tags))->raw(";\n")
->write('static $filters = ')->repr(array_filter($filters))->raw(";\n")
->write('static $functions = ')->repr(array_filter($functions))->raw(";\n\n")
->write("try {\n")
->indent()
->write("\$this->sandbox->checkSecurity(\n")
@ -61,7 +64,7 @@ class CheckSecurityNode extends Node
->outdent()
->write("} catch (SecurityError \$e) {\n")
->indent()
->write("\$e->setSourceContext(\$this->getSourceContext());\n\n")
->write("\$e->setSourceContext(\$this->source);\n\n")
->write("if (\$e instanceof SecurityNotAllowedTagError && isset(\$tags[\$e->getTagName()])) {\n")
->indent()
->write("\$e->setTemplateLine(\$tags[\$e->getTagName()]);\n")
@ -78,6 +81,8 @@ class CheckSecurityNode extends Node
->write("throw \$e;\n")
->outdent()
->write("}\n\n")
->outdent()
->write("}\n")
;
}
}

View File

@ -33,10 +33,13 @@ class CheckToStringNode extends AbstractExpression
public function compile(Compiler $compiler)
{
$expr = $this->getNode('expr');
$compiler
->raw('$this->sandbox->ensureToStringAllowed(')
->subcompile($this->getNode('expr'))
->raw(')')
->subcompile($expr)
->raw(', ')
->repr($expr->getTemplateLine())
->raw(', $this->source)')
;
}
}

View File

@ -22,7 +22,7 @@ use Twig\Node\Expression\ConstantExpression;
*/
class DeprecatedNode extends Node
{
public function __construct(AbstractExpression $expr, $lineno, $tag = null)
public function __construct(AbstractExpression $expr, int $lineno, string $tag = null)
{
parent::__construct(['expr' => $expr], [], $lineno, $tag);
}

View File

@ -21,7 +21,7 @@ use Twig\Node\Expression\AbstractExpression;
*/
class DoNode extends Node
{
public function __construct(AbstractExpression $expr, $lineno, $tag = null)
public function __construct(AbstractExpression $expr, int $lineno, string $tag = null)
{
parent::__construct(['expr' => $expr], [], $lineno, $tag);
}

View File

@ -23,13 +23,11 @@ use Twig\Node\Expression\ConstantExpression;
class EmbedNode extends IncludeNode
{
// we don't inject the module to avoid node visitors to traverse it twice (as it will be already visited in the main module)
public function __construct($name, $index, AbstractExpression $variables = null, $only = false, $ignoreMissing = false, $lineno, $tag = null)
public function __construct(string $name, int $index, ?AbstractExpression $variables, bool $only, bool $ignoreMissing, int $lineno, string $tag = null)
{
parent::__construct(new ConstantExpression('not_used', $lineno), $variables, $only, $ignoreMissing, $lineno, $tag);
$this->setAttribute('name', $name);
// to be removed in 2.0, used name instead
$this->setAttribute('filename', $name);
$this->setAttribute('index', $index);
}

View File

@ -15,9 +15,9 @@ use Twig\Compiler;
class ArrayExpression extends AbstractExpression
{
protected $index;
private $index;
public function __construct(array $elements, $lineno)
public function __construct(array $elements, int $lineno)
{
parent::__construct($elements, [], $lineno);

View File

@ -44,7 +44,7 @@ class ArrowFunctionExpression extends AbstractExpression
;
}
$compiler
->raw(') use ($context) { ')
->raw(') use ($context, $macros) { ')
;
foreach ($this->getNode('names') as $name) {
$compiler

View File

@ -14,10 +14,11 @@ namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
use Twig\Node\Expression\AbstractExpression;
use Twig\Node\Node;
abstract class AbstractBinary extends AbstractExpression
{
public function __construct(\Twig_NodeInterface $left, \Twig_NodeInterface $right, $lineno)
public function __construct(Node $left, Node $right, int $lineno)
{
parent::__construct(['left' => $left, 'right' => $right], [], $lineno);
}

View File

@ -15,21 +15,6 @@ use Twig\Compiler;
class PowerBinary extends AbstractBinary
{
public function compile(Compiler $compiler)
{
if (\PHP_VERSION_ID >= 50600) {
return parent::compile($compiler);
}
$compiler
->raw('pow(')
->subcompile($this->getNode('left'))
->raw(', ')
->subcompile($this->getNode('right'))
->raw(')')
;
}
public function operator(Compiler $compiler)
{
return $compiler->raw('**');

View File

@ -0,0 +1,22 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
class SpaceshipBinary extends AbstractBinary
{
public function operator(Compiler $compiler)
{
return $compiler->raw('<=>');
}
}

View File

@ -22,17 +22,8 @@ use Twig\Node\Node;
*/
class BlockReferenceExpression extends AbstractExpression
{
/**
* @param Node|null $template
*/
public function __construct(\Twig_NodeInterface $name, $template = null, $lineno, $tag = null)
public function __construct(Node $name, ?Node $template, int $lineno, string $tag = null)
{
if (\is_bool($template)) {
@trigger_error(sprintf('The %s method "$asString" argument is deprecated since version 1.28 and will be removed in 2.0.', __METHOD__), E_USER_DEPRECATED);
$template = null;
}
$nodes = ['name' => $name];
if (null !== $template) {
$nodes['template'] = $template;
@ -58,7 +49,7 @@ class BlockReferenceExpression extends AbstractExpression
}
}
private function compileTemplateCall(Compiler $compiler, $method)
private function compileTemplateCall(Compiler $compiler, string $method): Compiler
{
if (!$this->hasNode('template')) {
$compiler->write('$this');
@ -75,12 +66,11 @@ class BlockReferenceExpression extends AbstractExpression
}
$compiler->raw(sprintf('->%s', $method));
$this->compileBlockArguments($compiler);
return $compiler;
return $this->compileBlockArguments($compiler);
}
private function compileBlockArguments(Compiler $compiler)
private function compileBlockArguments(Compiler $compiler): Compiler
{
$compiler
->raw('(')

View File

@ -22,37 +22,37 @@ abstract class CallExpression extends AbstractExpression
protected function compileCallable(Compiler $compiler)
{
$closingParenthesis = false;
$isArray = false;
if ($this->hasAttribute('callable') && $callable = $this->getAttribute('callable')) {
if (\is_string($callable) && false === strpos($callable, '::')) {
$compiler->raw($callable);
} else {
list($r, $callable) = $this->reflectCallable($callable);
if ($r instanceof \ReflectionMethod && \is_string($callable[0])) {
if ($r->isStatic()) {
$compiler->raw(sprintf('%s::%s', $callable[0], $callable[1]));
} else {
$compiler->raw(sprintf('$this->env->getRuntime(\'%s\')->%s', $callable[0], $callable[1]));
}
} elseif ($r instanceof \ReflectionMethod && $callable[0] instanceof ExtensionInterface) {
$compiler->raw(sprintf('$this->env->getExtension(\'%s\')->%s', \get_class($callable[0]), $callable[1]));
} else {
$type = ucfirst($this->getAttribute('type'));
$compiler->raw(sprintf('call_user_func_array($this->env->get%s(\'%s\')->getCallable(), ', $type, $this->getAttribute('name')));
$closingParenthesis = true;
$isArray = true;
}
}
$callable = $this->getAttribute('callable');
if (\is_string($callable) && false === strpos($callable, '::')) {
$compiler->raw($callable);
} else {
$compiler->raw($this->getAttribute('thing')->compile());
[$r, $callable] = $this->reflectCallable($callable);
if (\is_string($callable)) {
$compiler->raw($callable);
} elseif (\is_array($callable) && \is_string($callable[0])) {
if (!$r instanceof \ReflectionMethod || $r->isStatic()) {
$compiler->raw(sprintf('%s::%s', $callable[0], $callable[1]));
} else {
$compiler->raw(sprintf('$this->env->getRuntime(\'%s\')->%s', $callable[0], $callable[1]));
}
} elseif (\is_array($callable) && $callable[0] instanceof ExtensionInterface) {
$class = \get_class($callable[0]);
if (!$compiler->getEnvironment()->hasExtension($class)) {
// Compile a non-optimized call to trigger a \Twig\Error\RuntimeError, which cannot be a compile-time error
$compiler->raw(sprintf('$this->env->getExtension(\'%s\')', $class));
} else {
$compiler->raw(sprintf('$this->extensions[\'%s\']', ltrim($class, '\\')));
}
$compiler->raw(sprintf('->%s', $callable[1]));
} else {
$compiler->raw(sprintf('$this->env->get%s(\'%s\')->getCallable()', ucfirst($this->getAttribute('type')), $this->getAttribute('name')));
}
}
$this->compileArguments($compiler, $isArray);
if ($closingParenthesis) {
$compiler->raw(')');
}
$this->compileArguments($compiler);
}
protected function compileArguments(Compiler $compiler, $isArray = false)
@ -93,10 +93,8 @@ abstract class CallExpression extends AbstractExpression
}
if ($this->hasNode('arguments')) {
$callable = $this->hasAttribute('callable') ? $this->getAttribute('callable') : null;
$callable = $this->getAttribute('callable');
$arguments = $this->getArguments($callable, $this->getNode('arguments'));
foreach ($arguments as $node) {
if (!$first) {
$compiler->raw(', ');
@ -142,14 +140,23 @@ abstract class CallExpression extends AbstractExpression
throw new \LogicException($message);
}
$callableParameters = $this->getCallableParameters($callable, $isVariadic);
list($callableParameters, $isPhpVariadic) = $this->getCallableParameters($callable, $isVariadic);
$arguments = [];
$names = [];
$missingArguments = [];
$optionalArguments = [];
$pos = 0;
foreach ($callableParameters as $callableParameter) {
$names[] = $name = $this->normalizeName($callableParameter->name);
$name = $this->normalizeName($callableParameter->name);
if (\PHP_VERSION_ID >= 80000 && 'range' === $callable) {
if ('start' === $name) {
$name = 'low';
} elseif ('end' === $name) {
$name = 'high';
}
}
$names[] = $name;
if (\array_key_exists($name, $parameters)) {
if (\array_key_exists($pos, $parameters)) {
@ -187,7 +194,7 @@ abstract class CallExpression extends AbstractExpression
}
if ($isVariadic) {
$arbitraryArguments = new ArrayExpression([], -1);
$arbitraryArguments = $isPhpVariadic ? new VariadicExpression([], -1) : new ArrayExpression([], -1);
foreach ($parameters as $key => $value) {
if (\is_int($key)) {
$arbitraryArguments->addElement($value);
@ -230,12 +237,9 @@ abstract class CallExpression extends AbstractExpression
return strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], ['\\1_\\2', '\\1_\\2'], $name));
}
private function getCallableParameters($callable, $isVariadic)
private function getCallableParameters($callable, bool $isVariadic): array
{
list($r) = $this->reflectCallable($callable);
if (null === $r) {
return [];
}
[$r, , $callableName] = $this->reflectCallable($callable);
$parameters = $r->getParameters();
if ($this->hasNode('node')) {
@ -252,21 +256,21 @@ abstract class CallExpression extends AbstractExpression
array_shift($parameters);
}
}
$isPhpVariadic = false;
if ($isVariadic) {
$argument = end($parameters);
if ($argument && $argument->isArray() && $argument->isDefaultValueAvailable() && [] === $argument->getDefaultValue()) {
$isArray = $argument && $argument->hasType() && 'array' === $argument->getType()->getName();
if ($isArray && $argument->isDefaultValueAvailable() && [] === $argument->getDefaultValue()) {
array_pop($parameters);
} elseif ($argument && $argument->isVariadic()) {
array_pop($parameters);
$isPhpVariadic = true;
} else {
$callableName = $r->name;
if ($r instanceof \ReflectionMethod) {
$callableName = $r->getDeclaringClass()->name.'::'.$callableName;
}
throw new \LogicException(sprintf('The last parameter of "%s" for %s "%s" must be an array with default value, eg. "array $arg = []".', $callableName, $this->getAttribute('type'), $this->getAttribute('name')));
}
}
return $parameters;
return [$parameters, $isPhpVariadic];
}
private function reflectCallable($callable)
@ -275,30 +279,44 @@ abstract class CallExpression extends AbstractExpression
return $this->reflector;
}
if (\is_array($callable)) {
if (!method_exists($callable[0], $callable[1])) {
// __call()
return [null, []];
}
$r = new \ReflectionMethod($callable[0], $callable[1]);
} elseif (\is_object($callable) && !$callable instanceof \Closure) {
$r = new \ReflectionObject($callable);
$r = $r->getMethod('__invoke');
$callable = [$callable, '__invoke'];
} elseif (\is_string($callable) && false !== $pos = strpos($callable, '::')) {
$class = substr($callable, 0, $pos);
$method = substr($callable, $pos + 2);
if (!method_exists($class, $method)) {
// __staticCall()
return [null, []];
}
$r = new \ReflectionMethod($callable);
$callable = [$class, $method];
} else {
$r = new \ReflectionFunction($callable);
if (\is_string($callable) && false !== $pos = strpos($callable, '::')) {
$callable = [substr($callable, 0, $pos), substr($callable, 2 + $pos)];
}
return $this->reflector = [$r, $callable];
if (\is_array($callable) && method_exists($callable[0], $callable[1])) {
$r = new \ReflectionMethod($callable[0], $callable[1]);
return $this->reflector = [$r, $callable, $r->class.'::'.$r->name];
}
$checkVisibility = $callable instanceof \Closure;
try {
$closure = \Closure::fromCallable($callable);
} catch (\TypeError $e) {
throw new \LogicException(sprintf('Callback for %s "%s" is not callable in the current scope.', $this->getAttribute('type'), $this->getAttribute('name')), 0, $e);
}
$r = new \ReflectionFunction($closure);
if (false !== strpos($r->name, '{closure}')) {
return $this->reflector = [$r, $callable, 'Closure'];
}
if ($object = $r->getClosureThis()) {
$callable = [$object, $r->name];
$callableName = (\function_exists('get_debug_type') ? get_debug_type($object) : \get_class($object)).'::'.$r->name;
} elseif (\PHP_VERSION_ID >= 80111 && $class = $r->getClosureCalledClass()) {
$callableName = $class->name.'::'.$r->name;
} elseif (\PHP_VERSION_ID < 80111 && $class = $r->getClosureScopeClass()) {
$callableName = (\is_array($callable) ? $callable[0] : $class->name).'::'.$r->name;
} else {
$callable = $callableName = $r->name;
}
if ($checkVisibility && \is_array($callable) && method_exists(...$callable) && !(new \ReflectionMethod(...$callable))->isPublic()) {
$callable = $r->getClosure();
}
return $this->reflector = [$r, $callable, $callableName];
}
}

View File

@ -16,7 +16,7 @@ use Twig\Compiler;
class ConditionalExpression extends AbstractExpression
{
public function __construct(AbstractExpression $expr1, AbstractExpression $expr2, AbstractExpression $expr3, $lineno)
public function __construct(AbstractExpression $expr1, AbstractExpression $expr2, AbstractExpression $expr3, int $lineno)
{
parent::__construct(['expr1' => $expr1, 'expr2' => $expr2, 'expr3' => $expr3], [], $lineno);
}

View File

@ -16,7 +16,7 @@ use Twig\Compiler;
class ConstantExpression extends AbstractExpression
{
public function __construct($value, $lineno)
public function __construct($value, int $lineno)
{
parent::__construct([], ['value' => $value], $lineno);
}

View File

@ -29,7 +29,7 @@ use Twig\Node\Node;
*/
class DefaultFilter extends FilterExpression
{
public function __construct(\Twig_NodeInterface $node, ConstantExpression $filterName, \Twig_NodeInterface $arguments, $lineno, $tag = null)
public function __construct(Node $node, ConstantExpression $filterName, Node $arguments, int $lineno, string $tag = null)
{
$default = new FilterExpression($node, new ConstantExpression('default', $node->getTemplateLine()), $arguments, $node->getTemplateLine());

View File

@ -13,11 +13,11 @@
namespace Twig\Node\Expression;
use Twig\Compiler;
use Twig\TwigFilter;
use Twig\Node\Node;
class FilterExpression extends CallExpression
{
public function __construct(\Twig_NodeInterface $node, ConstantExpression $filterName, \Twig_NodeInterface $arguments, $lineno, $tag = null)
public function __construct(Node $node, ConstantExpression $filterName, Node $arguments, int $lineno, string $tag = null)
{
parent::__construct(['node' => $node, 'filter' => $filterName, 'arguments' => $arguments], [], $lineno, $tag);
}
@ -29,16 +29,11 @@ class FilterExpression extends CallExpression
$this->setAttribute('name', $name);
$this->setAttribute('type', 'filter');
$this->setAttribute('thing', $filter);
$this->setAttribute('needs_environment', $filter->needsEnvironment());
$this->setAttribute('needs_context', $filter->needsContext());
$this->setAttribute('arguments', $filter->getArguments());
if ($filter instanceof \Twig_FilterCallableInterface || $filter instanceof TwigFilter) {
$this->setAttribute('callable', $filter->getCallable());
}
if ($filter instanceof TwigFilter) {
$this->setAttribute('is_variadic', $filter->isVariadic());
}
$this->setAttribute('callable', $filter->getCallable());
$this->setAttribute('is_variadic', $filter->isVariadic());
$this->compileCallable($compiler);
}

View File

@ -12,11 +12,11 @@
namespace Twig\Node\Expression;
use Twig\Compiler;
use Twig\TwigFunction;
use Twig\Node\Node;
class FunctionExpression extends CallExpression
{
public function __construct($name, \Twig_NodeInterface $arguments, $lineno)
public function __construct(string $name, Node $arguments, int $lineno)
{
parent::__construct(['arguments' => $arguments], ['name' => $name, 'is_defined_test' => false], $lineno);
}
@ -28,21 +28,15 @@ class FunctionExpression extends CallExpression
$this->setAttribute('name', $name);
$this->setAttribute('type', 'function');
$this->setAttribute('thing', $function);
$this->setAttribute('needs_environment', $function->needsEnvironment());
$this->setAttribute('needs_context', $function->needsContext());
$this->setAttribute('arguments', $function->getArguments());
if ($function instanceof \Twig_FunctionCallableInterface || $function instanceof TwigFunction) {
$callable = $function->getCallable();
if ('constant' === $name && $this->getAttribute('is_defined_test')) {
$callable = 'twig_constant_is_defined';
}
$this->setAttribute('callable', $callable);
}
if ($function instanceof TwigFunction) {
$this->setAttribute('is_variadic', $function->isVariadic());
$callable = $function->getCallable();
if ('constant' === $name && $this->getAttribute('is_defined_test')) {
$callable = 'twig_constant_is_defined';
}
$this->setAttribute('callable', $callable);
$this->setAttribute('is_variadic', $function->isVariadic());
$this->compileCallable($compiler);
}

View File

@ -13,67 +13,76 @@
namespace Twig\Node\Expression;
use Twig\Compiler;
use Twig\Extension\SandboxExtension;
use Twig\Template;
class GetAttrExpression extends AbstractExpression
{
public function __construct(AbstractExpression $node, AbstractExpression $attribute, AbstractExpression $arguments = null, $type, $lineno)
public function __construct(AbstractExpression $node, AbstractExpression $attribute, ?AbstractExpression $arguments, string $type, int $lineno)
{
$nodes = ['node' => $node, 'attribute' => $attribute];
if (null !== $arguments) {
$nodes['arguments'] = $arguments;
}
parent::__construct($nodes, ['type' => $type, 'is_defined_test' => false, 'ignore_strict_check' => false, 'disable_c_ext' => false], $lineno);
parent::__construct($nodes, ['type' => $type, 'is_defined_test' => false, 'ignore_strict_check' => false, 'optimizable' => true], $lineno);
}
public function compile(Compiler $compiler)
{
if ($this->getAttribute('disable_c_ext')) {
@trigger_error(sprintf('Using the "disable_c_ext" attribute on %s is deprecated since version 1.30 and will be removed in 2.0.', __CLASS__), E_USER_DEPRECATED);
$env = $compiler->getEnvironment();
// optimize array calls
if (
$this->getAttribute('optimizable')
&& (!$env->isStrictVariables() || $this->getAttribute('ignore_strict_check'))
&& !$this->getAttribute('is_defined_test')
&& Template::ARRAY_CALL === $this->getAttribute('type')
) {
$var = '$'.$compiler->getVarName();
$compiler
->raw('(('.$var.' = ')
->subcompile($this->getNode('node'))
->raw(') && is_array(')
->raw($var)
->raw(') || ')
->raw($var)
->raw(' instanceof ArrayAccess ? (')
->raw($var)
->raw('[')
->subcompile($this->getNode('attribute'))
->raw('] ?? null) : null)')
;
return;
}
if (\function_exists('twig_template_get_attributes') && !$this->getAttribute('disable_c_ext')) {
$compiler->raw('twig_template_get_attributes($this, ');
} else {
$compiler->raw('$this->getAttribute(');
}
$compiler->raw('twig_get_attribute($this->env, $this->source, ');
if ($this->getAttribute('ignore_strict_check')) {
$this->getNode('node')->setAttribute('ignore_strict_check', true);
}
$compiler->subcompile($this->getNode('node'));
$compiler
->subcompile($this->getNode('node'))
->raw(', ')
->subcompile($this->getNode('attribute'))
;
$compiler->raw(', ')->subcompile($this->getNode('attribute'));
// only generate optional arguments when needed (to make generated code more readable)
$needFourth = $this->getAttribute('ignore_strict_check');
$needThird = $needFourth || $this->getAttribute('is_defined_test');
$needSecond = $needThird || Template::ANY_CALL !== $this->getAttribute('type');
$needFirst = $needSecond || $this->hasNode('arguments');
if ($needFirst) {
if ($this->hasNode('arguments')) {
$compiler->raw(', ')->subcompile($this->getNode('arguments'));
} else {
$compiler->raw(', []');
}
if ($this->hasNode('arguments')) {
$compiler->raw(', ')->subcompile($this->getNode('arguments'));
} else {
$compiler->raw(', []');
}
if ($needSecond) {
$compiler->raw(', ')->repr($this->getAttribute('type'));
}
if ($needThird) {
$compiler->raw(', ')->repr($this->getAttribute('is_defined_test'));
}
if ($needFourth) {
$compiler->raw(', ')->repr($this->getAttribute('ignore_strict_check'));
}
$compiler->raw(')');
$compiler->raw(', ')
->repr($this->getAttribute('type'))
->raw(', ')->repr($this->getAttribute('is_defined_test'))
->raw(', ')->repr($this->getAttribute('ignore_strict_check'))
->raw(', ')->repr($env->hasExtension(SandboxExtension::class))
->raw(', ')->repr($this->getNode('node')->getTemplateLine())
->raw(')')
;
}
}

View File

@ -15,9 +15,9 @@ use Twig\Compiler;
class MethodCallExpression extends AbstractExpression
{
public function __construct(AbstractExpression $node, $method, ArrayExpression $arguments, $lineno)
public function __construct(AbstractExpression $node, string $method, ArrayExpression $arguments, int $lineno)
{
parent::__construct(['node' => $node, 'arguments' => $arguments], ['method' => $method, 'safe' => false], $lineno);
parent::__construct(['node' => $node, 'arguments' => $arguments], ['method' => $method, 'safe' => false, 'is_defined_test' => false], $lineno);
if ($node instanceof NameExpression) {
$node->setAttribute('always_defined', true);
@ -26,11 +26,24 @@ class MethodCallExpression extends AbstractExpression
public function compile(Compiler $compiler)
{
if ($this->getAttribute('is_defined_test')) {
$compiler
->raw('method_exists($macros[')
->repr($this->getNode('node')->getAttribute('name'))
->raw('], ')
->repr($this->getAttribute('method'))
->raw(')')
;
return;
}
$compiler
->subcompile($this->getNode('node'))
->raw('->')
->raw($this->getAttribute('method'))
->raw('(')
->raw('twig_call_macro($macros[')
->repr($this->getNode('node')->getAttribute('name'))
->raw('], ')
->repr($this->getAttribute('method'))
->raw(', [')
;
$first = true;
foreach ($this->getNode('arguments')->getKeyValuePairs() as $pair) {
@ -41,7 +54,10 @@ class MethodCallExpression extends AbstractExpression
$compiler->subcompile($pair['value']);
}
$compiler->raw(')');
$compiler
->raw('], ')
->repr($this->getTemplateLine())
->raw(', $context, $this->getSourceContext())');
}
}

View File

@ -16,13 +16,13 @@ use Twig\Compiler;
class NameExpression extends AbstractExpression
{
protected $specialVars = [
'_self' => '$this',
private $specialVars = [
'_self' => '$this->getTemplateName()',
'_context' => '$context',
'_charset' => '$this->env->getCharset()',
];
public function __construct($name, $lineno)
public function __construct(string $name, int $lineno)
{
parent::__construct([], ['name' => $name, 'is_defined_test' => false, 'ignore_strict_check' => false, 'always_defined' => false], $lineno);
}
@ -36,7 +36,7 @@ class NameExpression extends AbstractExpression
if ($this->getAttribute('is_defined_test')) {
if ($this->isSpecial()) {
$compiler->repr(true);
} elseif (\PHP_VERSION_ID >= 700400) {
} elseif (\PHP_VERSION_ID >= 70400) {
$compiler
->raw('array_key_exists(')
->string($name)
@ -60,45 +60,25 @@ class NameExpression extends AbstractExpression
->raw(']')
;
} else {
if (\PHP_VERSION_ID >= 70000) {
// use PHP 7 null coalescing operator
if ($this->getAttribute('ignore_strict_check') || !$compiler->getEnvironment()->isStrictVariables()) {
$compiler
->raw('($context[')
->string($name)
->raw('] ?? ')
->raw('] ?? null)')
;
if ($this->getAttribute('ignore_strict_check') || !$compiler->getEnvironment()->isStrictVariables()) {
$compiler->raw('null)');
} else {
$compiler->raw('$this->getContext($context, ')->string($name)->raw('))');
}
} elseif (\PHP_VERSION_ID >= 50400) {
// PHP 5.4 ternary operator performance was optimized
} else {
$compiler
->raw('(isset($context[')
->string($name)
->raw(']) ? $context[')
->raw(']) || array_key_exists(')
->string($name)
->raw('] : ')
;
if ($this->getAttribute('ignore_strict_check') || !$compiler->getEnvironment()->isStrictVariables()) {
$compiler->raw('null)');
} else {
$compiler->raw('$this->getContext($context, ')->string($name)->raw('))');
}
} else {
$compiler
->raw('$this->getContext($context, ')
->raw(', $context) ? $context[')
->string($name)
;
if ($this->getAttribute('ignore_strict_check')) {
$compiler->raw(', true');
}
$compiler
->raw('] : (function () { throw new RuntimeError(\'Variable ')
->string($name)
->raw(' does not exist.\', ')
->repr($this->lineno)
->raw(', $this->source); })()')
->raw(')')
;
}

View File

@ -20,7 +20,7 @@ use Twig\Node\Node;
class NullCoalesceExpression extends ConditionalExpression
{
public function __construct(\Twig_NodeInterface $left, \Twig_NodeInterface $right, $lineno)
public function __construct(Node $left, Node $right, int $lineno)
{
$test = new DefinedTest(clone $left, 'defined', new Node(), $left->getTemplateLine());
// for "block()", we don't need the null test as the return value is always a string
@ -44,7 +44,7 @@ class NullCoalesceExpression extends ConditionalExpression
* cases might be implemented as an optimizer node visitor, but has not been done
* as benefits are probably not worth the added complexity.
*/
if (\PHP_VERSION_ID >= 70000 && $this->getNode('expr2') instanceof NameExpression) {
if ($this->getNode('expr2') instanceof NameExpression) {
$this->getNode('expr2')->setAttribute('always_defined', true);
$compiler
->raw('((')

View File

@ -21,7 +21,7 @@ use Twig\Compiler;
*/
class ParentExpression extends AbstractExpression
{
public function __construct($name, $lineno, $tag = null)
public function __construct(string $name, int $lineno, string $tag = null)
{
parent::__construct([], ['output' => false, 'name' => $name], $lineno, $tag);
}

View File

@ -15,7 +15,7 @@ use Twig\Compiler;
class TempNameExpression extends AbstractExpression
{
public function __construct($name, $lineno)
public function __construct(string $name, int $lineno)
{
parent::__construct([], ['name' => $name], $lineno);
}

View File

@ -18,8 +18,10 @@ use Twig\Node\Expression\BlockReferenceExpression;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Expression\FunctionExpression;
use Twig\Node\Expression\GetAttrExpression;
use Twig\Node\Expression\MethodCallExpression;
use Twig\Node\Expression\NameExpression;
use Twig\Node\Expression\TestExpression;
use Twig\Node\Node;
/**
* Checks if a variable is defined in the current context.
@ -33,7 +35,7 @@ use Twig\Node\Expression\TestExpression;
*/
class DefinedTest extends TestExpression
{
public function __construct(\Twig_NodeInterface $node, $name, \Twig_NodeInterface $arguments = null, $lineno)
public function __construct(Node $node, string $name, ?Node $arguments, int $lineno)
{
if ($node instanceof NameExpression) {
$node->setAttribute('is_defined_test', true);
@ -46,6 +48,8 @@ class DefinedTest extends TestExpression
$node->setAttribute('is_defined_test', true);
} elseif ($node instanceof ConstantExpression || $node instanceof ArrayExpression) {
$node = new ConstantExpression(true, $node->getTemplateLine());
} elseif ($node instanceof MethodCallExpression) {
$node->setAttribute('is_defined_test', true);
} else {
throw new SyntaxError('The "defined" test only works with simple variables.', $lineno);
}
@ -53,8 +57,9 @@ class DefinedTest extends TestExpression
parent::__construct($node, $name, $arguments, $lineno);
}
protected function changeIgnoreStrictCheck(GetAttrExpression $node)
private function changeIgnoreStrictCheck(GetAttrExpression $node)
{
$node->setAttribute('optimizable', false);
$node->setAttribute('ignore_strict_check', true);
if ($node->getNode('node') instanceof GetAttrExpression) {

View File

@ -28,7 +28,7 @@ class OddTest extends TestExpression
$compiler
->raw('(')
->subcompile($this->getNode('node'))
->raw(' % 2 == 1')
->raw(' % 2 != 0')
->raw(')')
;
}

View File

@ -12,11 +12,11 @@
namespace Twig\Node\Expression;
use Twig\Compiler;
use Twig\TwigTest;
use Twig\Node\Node;
class TestExpression extends CallExpression
{
public function __construct(\Twig_NodeInterface $node, $name, \Twig_NodeInterface $arguments = null, $lineno)
public function __construct(Node $node, string $name, ?Node $arguments, int $lineno)
{
$nodes = ['node' => $node];
if (null !== $arguments) {
@ -33,16 +33,9 @@ class TestExpression extends CallExpression
$this->setAttribute('name', $name);
$this->setAttribute('type', 'test');
$this->setAttribute('thing', $test);
if ($test instanceof TwigTest) {
$this->setAttribute('arguments', $test->getArguments());
}
if ($test instanceof \Twig_TestCallableInterface || $test instanceof TwigTest) {
$this->setAttribute('callable', $test->getCallable());
}
if ($test instanceof TwigTest) {
$this->setAttribute('is_variadic', $test->isVariadic());
}
$this->setAttribute('arguments', $test->getArguments());
$this->setAttribute('callable', $test->getCallable());
$this->setAttribute('is_variadic', $test->isVariadic());
$this->compileCallable($compiler);
}

View File

@ -14,10 +14,11 @@ namespace Twig\Node\Expression\Unary;
use Twig\Compiler;
use Twig\Node\Expression\AbstractExpression;
use Twig\Node\Node;
abstract class AbstractUnary extends AbstractExpression
{
public function __construct(\Twig_NodeInterface $node, $lineno)
public function __construct(Node $node, int $lineno)
{
parent::__construct(['node' => $node], [], $lineno);
}

View File

@ -0,0 +1,24 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression;
use Twig\Compiler;
class VariadicExpression extends ArrayExpression
{
public function compile(Compiler $compiler)
{
$compiler->raw('...');
parent::compile($compiler);
}
}

View File

@ -20,7 +20,7 @@ use Twig\Compiler;
*/
class FlushNode extends Node
{
public function __construct($lineno, $tag)
public function __construct(int $lineno, string $tag)
{
parent::__construct([], [], $lineno, $tag);
}

View File

@ -20,7 +20,7 @@ use Twig\Compiler;
*/
class ForLoopNode extends Node
{
public function __construct($lineno, $tag = null)
public function __construct(int $lineno, string $tag = null)
{
parent::__construct([], ['with_loop' => false, 'ifexpr' => false, 'else' => false], $lineno, $tag);
}

View File

@ -23,9 +23,9 @@ use Twig\Node\Expression\AssignNameExpression;
*/
class ForNode extends Node
{
protected $loop;
private $loop;
public function __construct(AssignNameExpression $keyTarget, AssignNameExpression $valueTarget, AbstractExpression $seq, AbstractExpression $ifexpr = null, \Twig_NodeInterface $body, \Twig_NodeInterface $else = null, $lineno, $tag = null)
public function __construct(AssignNameExpression $keyTarget, AssignNameExpression $valueTarget, AbstractExpression $seq, ?AbstractExpression $ifexpr, Node $body, ?Node $else, int $lineno, string $tag = null)
{
$body = new Node([$body, $this->loop = new ForLoopNode($lineno, $tag)]);

View File

@ -21,7 +21,7 @@ use Twig\Compiler;
*/
class IfNode extends Node
{
public function __construct(\Twig_NodeInterface $tests, \Twig_NodeInterface $else = null, $lineno, $tag = null)
public function __construct(Node $tests, ?Node $else, int $lineno, string $tag = null)
{
$nodes = ['tests' => $tests];
if (null !== $else) {
@ -50,8 +50,11 @@ class IfNode extends Node
->subcompile($this->getNode('tests')->getNode($i))
->raw(") {\n")
->indent()
->subcompile($this->getNode('tests')->getNode($i + 1))
;
// The node might not exists if the content is empty
if ($this->getNode('tests')->hasNode($i + 1)) {
$compiler->subcompile($this->getNode('tests')->getNode($i + 1));
}
}
if ($this->hasNode('else')) {

View File

@ -22,20 +22,28 @@ use Twig\Node\Expression\NameExpression;
*/
class ImportNode extends Node
{
public function __construct(AbstractExpression $expr, AbstractExpression $var, $lineno, $tag = null)
public function __construct(AbstractExpression $expr, AbstractExpression $var, int $lineno, string $tag = null, bool $global = true)
{
parent::__construct(['expr' => $expr, 'var' => $var], [], $lineno, $tag);
parent::__construct(['expr' => $expr, 'var' => $var], ['global' => $global], $lineno, $tag);
}
public function compile(Compiler $compiler)
{
$compiler
->addDebugInfo($this)
->write('')
->subcompile($this->getNode('var'))
->raw(' = ')
->write('$macros[')
->repr($this->getNode('var')->getAttribute('name'))
->raw('] = ')
;
if ($this->getAttribute('global')) {
$compiler
->raw('$this->macros[')
->repr($this->getNode('var')->getAttribute('name'))
->raw('] = ')
;
}
if ($this->getNode('expr') instanceof NameExpression && '_self' === $this->getNode('expr')->getAttribute('name')) {
$compiler->raw('$this');
} else {

View File

@ -22,7 +22,7 @@ use Twig\Node\Expression\AbstractExpression;
*/
class IncludeNode extends Node implements NodeOutputInterface
{
public function __construct(AbstractExpression $expr, AbstractExpression $variables = null, $only = false, $ignoreMissing = false, $lineno, $tag = null)
public function __construct(AbstractExpression $expr, ?AbstractExpression $variables, bool $only, bool $ignoreMissing, int $lineno, string $tag = null)
{
$nodes = ['expr' => $expr];
if (null !== $variables) {

View File

@ -21,9 +21,9 @@ use Twig\Error\SyntaxError;
*/
class MacroNode extends Node
{
const VARARGS_NAME = 'varargs';
public const VARARGS_NAME = 'varargs';
public function __construct($name, \Twig_NodeInterface $body, \Twig_NodeInterface $arguments, $lineno, $tag = null)
public function __construct(string $name, Node $body, Node $arguments, int $lineno, string $tag = null)
{
foreach ($arguments as $argumentName => $argument) {
if (self::VARARGS_NAME === $argumentName) {
@ -38,7 +38,7 @@ class MacroNode extends Node
{
$compiler
->addDebugInfo($this)
->write(sprintf('public function get%s(', $this->getAttribute('name')))
->write(sprintf('public function macro_%s(', $this->getAttribute('name')))
;
$count = \count($this->getNode('arguments'));
@ -54,21 +54,16 @@ class MacroNode extends Node
}
}
if (\PHP_VERSION_ID >= 50600) {
if ($count) {
$compiler->raw(', ');
}
$compiler->raw('...$__varargs__');
if ($count) {
$compiler->raw(', ');
}
$compiler
->raw('...$__varargs__')
->raw(")\n")
->write("{\n")
->indent()
;
$compiler
->write("\$macros = \$this->macros;\n")
->write("\$context = \$this->env->mergeGlobals([\n")
->indent()
;
@ -88,19 +83,8 @@ class MacroNode extends Node
->raw(' => ')
;
if (\PHP_VERSION_ID >= 50600) {
$compiler->raw("\$__varargs__,\n");
} else {
$compiler
->raw('func_num_args() > ')
->repr($count)
->raw(' ? array_slice(func_get_args(), ')
->repr($count)
->raw(") : [],\n")
;
}
$compiler
->raw("\$__varargs__,\n")
->outdent()
->write("]);\n\n")
->write("\$blocks = [];\n\n")
@ -114,19 +98,14 @@ class MacroNode extends Node
->write("try {\n")
->indent()
->subcompile($this->getNode('body'))
->raw("\n")
->write("return ('' === \$tmp = ob_get_contents()) ? '' : new Markup(\$tmp, \$this->env->getCharset());\n")
->outdent()
->write("} catch (\Exception \$e) {\n")
->write("} finally {\n")
->indent()
->write("ob_end_clean();\n\n")
->write("throw \$e;\n")
->write("ob_end_clean();\n")
->outdent()
->write("} catch (\Throwable \$e) {\n")
->indent()
->write("ob_end_clean();\n\n")
->write("throw \$e;\n")
->outdent()
->write("}\n\n")
->write("return ('' === \$tmp = ob_get_clean()) ? '' : new Markup(\$tmp, \$this->env->getCharset());\n")
->write("}\n")
->outdent()
->write("}\n\n")
;

View File

@ -25,16 +25,15 @@ use Twig\Source;
* display_end, constructor_start, constructor_end, and class_end.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final since Twig 2.4.0
*/
class ModuleNode extends Node
{
public function __construct(\Twig_NodeInterface $body, AbstractExpression $parent = null, \Twig_NodeInterface $blocks, \Twig_NodeInterface $macros, \Twig_NodeInterface $traits, $embeddedTemplates, $name, $source = '')
public function __construct(Node $body, ?AbstractExpression $parent, Node $blocks, Node $macros, Node $traits, $embeddedTemplates, Source $source)
{
if (!$name instanceof Source) {
@trigger_error(sprintf('Passing a string as the $name argument of %s() is deprecated since version 1.27. Pass a \Twig\Source instance instead.', __METHOD__), E_USER_DEPRECATED);
$source = new Source($source, $name);
} else {
$source = $name;
if (__CLASS__ !== static::class) {
@trigger_error('Overriding '.__CLASS__.' is deprecated since Twig 2.4.0 and the class will be final in 3.0.', \E_USER_DEPRECATED);
}
$nodes = [
@ -54,16 +53,11 @@ class ModuleNode extends Node
// embedded templates are set as attributes so that they are only visited once by the visitors
parent::__construct($nodes, [
// source to be remove in 2.0
'source' => $source->getCode(),
// filename to be remove in 2.0 (use getTemplateName() instead)
'filename' => $source->getName(),
'index' => null,
'embedded_templates' => $embeddedTemplates,
], 1);
// populate the template name of all node children
$this->setTemplateName($source->getName());
$this->setSourceContext($source);
}
@ -89,16 +83,7 @@ class ModuleNode extends Node
$this->compileClassHeader($compiler);
if (
\count($this->getNode('blocks'))
|| \count($this->getNode('traits'))
|| !$this->hasNode('parent')
|| $this->getNode('parent') instanceof ConstantExpression
|| \count($this->getNode('constructor_start'))
|| \count($this->getNode('constructor_end'))
) {
$this->compileConstructor($compiler);
}
$this->compileConstructor($compiler);
$this->compileGetParent($compiler);
@ -114,8 +99,6 @@ class ModuleNode extends Node
$this->compileDebugInfo($compiler);
$this->compileGetSource($compiler);
$this->compileGetSourceContext($compiler);
$this->compileClassFooter($compiler);
@ -166,6 +149,7 @@ class ModuleNode extends Node
->write("use Twig\Environment;\n")
->write("use Twig\Error\LoaderError;\n")
->write("use Twig\Error\RuntimeError;\n")
->write("use Twig\Extension\SandboxExtension;\n")
->write("use Twig\Markup;\n")
->write("use Twig\Sandbox\SecurityError;\n")
->write("use Twig\Sandbox\SecurityNotAllowedTagError;\n")
@ -179,9 +163,11 @@ class ModuleNode extends Node
// if the template name contains */, add a blank to avoid a PHP parse error
->write('/* '.str_replace('*/', '* /', $this->getSourceContext()->getName())." */\n")
->write('class '.$compiler->getEnvironment()->getTemplateClass($this->getSourceContext()->getName(), $this->getAttribute('index')))
->raw(sprintf(" extends %s\n", $compiler->getEnvironment()->getBaseTemplateClass()))
->raw(sprintf(" extends %s\n", $compiler->getEnvironment()->getBaseTemplateClass(false)))
->write("{\n")
->indent()
->write("private \$source;\n")
->write("private \$macros = [];\n\n")
;
}
@ -192,6 +178,7 @@ class ModuleNode extends Node
->indent()
->subcompile($this->getNode('constructor_start'))
->write("parent::__construct(\$env);\n\n")
->write("\$this->source = \$this->getSourceContext();\n\n")
;
// parent
@ -203,18 +190,24 @@ class ModuleNode extends Node
if ($countTraits) {
// traits
foreach ($this->getNode('traits') as $i => $trait) {
$this->compileLoadTemplate($compiler, $trait->getNode('template'), sprintf('$_trait_%s', $i));
$node = $trait->getNode('template');
$compiler
->addDebugInfo($node)
->write(sprintf('$_trait_%s = $this->loadTemplate(', $i))
->subcompile($node)
->raw(', ')
->repr($node->getTemplateName())
->raw(', ')
->repr($node->getTemplateLine())
->raw(");\n")
->write(sprintf("if (!\$_trait_%s->isTraitable()) {\n", $i))
->indent()
->write("throw new RuntimeError('Template \"'.")
->subcompile($trait->getNode('template'))
->raw(".'\" cannot be used as a trait.', ")
->repr($node->getTemplateLine())
->raw(", \$this->getSourceContext());\n")
->raw(", \$this->source);\n")
->outdent()
->write("}\n")
->write(sprintf("\$_trait_%s_blocks = \$_trait_%s->getBlocks();\n\n", $i, $i))
@ -226,13 +219,13 @@ class ModuleNode extends Node
->string($key)
->raw("])) {\n")
->indent()
->write("throw new RuntimeError(sprintf('Block ")
->write("throw new RuntimeError('Block ")
->string($key)
->raw(' is not defined in trait ')
->subcompile($trait->getNode('template'))
->raw(".'), ")
->raw(".', ")
->repr($node->getTemplateLine())
->raw(", \$this->getSourceContext());\n")
->raw(", \$this->source);\n")
->outdent()
->write("}\n\n")
@ -318,6 +311,7 @@ class ModuleNode extends Node
$compiler
->write("protected function doDisplay(array \$context, array \$blocks = [])\n", "{\n")
->indent()
->write("\$macros = \$this->macros;\n")
->subcompile($this->getNode('display_start'))
->subcompile($this->getNode('body'))
;
@ -440,20 +434,6 @@ class ModuleNode extends Node
;
}
protected function compileGetSource(Compiler $compiler)
{
$compiler
->write("/** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */\n")
->write("public function getSource()\n", "{\n")
->indent()
->write("@trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED);\n\n")
->write('return $this->getSourceContext()->getCode();')
->raw("\n")
->outdent()
->write("}\n\n")
;
}
protected function compileGetSourceContext(Compiler $compiler)
{
$compiler

View File

@ -20,7 +20,7 @@ use Twig\Source;
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Node implements \Twig_NodeInterface
class Node implements \Countable, \IteratorAggregate
{
protected $nodes;
protected $attributes;
@ -36,11 +36,11 @@ class Node implements \Twig_NodeInterface
* @param int $lineno The line number
* @param string $tag The tag name associated with the Node
*/
public function __construct(array $nodes = [], array $attributes = [], $lineno = 0, $tag = null)
public function __construct(array $nodes = [], array $attributes = [], int $lineno = 0, string $tag = null)
{
foreach ($nodes as $name => $node) {
if (!$node instanceof \Twig_NodeInterface) {
@trigger_error(sprintf('Using "%s" for the value of node "%s" of "%s" is deprecated since version 1.25 and will be removed in 2.0.', \is_object($node) ? \get_class($node) : (null === $node ? 'null' : \gettype($node)), $name, \get_class($this)), E_USER_DEPRECATED);
if (!$node instanceof self) {
throw new \InvalidArgumentException(sprintf('Using "%s" for the value of node "%s" of "%s" is not supported. You must pass a \Twig\Node\Node instance.', \is_object($node) ? \get_class($node) : (null === $node ? 'null' : \gettype($node)), $name, static::class));
}
}
$this->nodes = $nodes;
@ -56,7 +56,7 @@ class Node implements \Twig_NodeInterface
$attributes[] = sprintf('%s: %s', $name, str_replace("\n", '', var_export($value, true)));
}
$repr = [\get_class($this).'('.implode(', ', $attributes)];
$repr = [static::class.'('.implode(', ', $attributes)];
if (\count($this->nodes)) {
foreach ($this->nodes as $name => $node) {
@ -77,41 +77,6 @@ class Node implements \Twig_NodeInterface
return implode("\n", $repr);
}
/**
* @deprecated since 1.16.1 (to be removed in 2.0)
*/
public function toXml($asDom = false)
{
@trigger_error(sprintf('%s is deprecated since version 1.16.1 and will be removed in 2.0.', __METHOD__), E_USER_DEPRECATED);
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->formatOutput = true;
$dom->appendChild($xml = $dom->createElement('twig'));
$xml->appendChild($node = $dom->createElement('node'));
$node->setAttribute('class', \get_class($this));
foreach ($this->attributes as $name => $value) {
$node->appendChild($attribute = $dom->createElement('attribute'));
$attribute->setAttribute('name', $name);
$attribute->appendChild($dom->createTextNode($value));
}
foreach ($this->nodes as $name => $n) {
if (null === $n) {
continue;
}
$child = $n->toXml(true)->getElementsByTagName('node')->item(0);
$child = $dom->importNode($child, true);
$child->setAttribute('name', $name);
$node->appendChild($child);
}
return $asDom ? $dom : $dom->saveXML();
}
public function compile(Compiler $compiler)
{
foreach ($this->nodes as $node) {
@ -124,16 +89,6 @@ class Node implements \Twig_NodeInterface
return $this->lineno;
}
/**
* @deprecated since 1.27 (to be removed in 2.0)
*/
public function getLine()
{
@trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getTemplateLine() instead.', E_USER_DEPRECATED);
return $this->lineno;
}
public function getNodeTag()
{
return $this->tag;
@ -153,7 +108,7 @@ class Node implements \Twig_NodeInterface
public function getAttribute($name)
{
if (!\array_key_exists($name, $this->attributes)) {
throw new \LogicException(sprintf('Attribute "%s" does not exist for Node "%s".', $name, \get_class($this)));
throw new \LogicException(sprintf('Attribute "%s" does not exist for Node "%s".', $name, static::class));
}
return $this->attributes[$name];
@ -178,7 +133,7 @@ class Node implements \Twig_NodeInterface
*/
public function hasNode($name)
{
return \array_key_exists($name, $this->nodes);
return isset($this->nodes[$name]);
}
/**
@ -186,19 +141,15 @@ class Node implements \Twig_NodeInterface
*/
public function getNode($name)
{
if (!\array_key_exists($name, $this->nodes)) {
throw new \LogicException(sprintf('Node "%s" does not exist for Node "%s".', $name, \get_class($this)));
if (!isset($this->nodes[$name])) {
throw new \LogicException(sprintf('Node "%s" does not exist for Node "%s".', $name, static::class));
}
return $this->nodes[$name];
}
public function setNode($name, $node = null)
public function setNode($name, self $node)
{
if (!$node instanceof \Twig_NodeInterface) {
@trigger_error(sprintf('Using "%s" for the value of node "%s" of "%s" is deprecated since version 1.25 and will be removed in 2.0.', \is_object($node) ? \get_class($node) : (null === $node ? 'null' : \gettype($node)), $name, \get_class($this)), E_USER_DEPRECATED);
}
$this->nodes[$name] = $node;
}
@ -207,65 +158,59 @@ class Node implements \Twig_NodeInterface
unset($this->nodes[$name]);
}
/**
* @return int
*/
#[\ReturnTypeWillChange]
public function count()
{
return \count($this->nodes);
}
/**
* @return \Traversable
*/
#[\ReturnTypeWillChange]
public function getIterator()
{
return new \ArrayIterator($this->nodes);
}
public function setTemplateName($name)
/**
* @deprecated since 2.8 (to be removed in 3.0)
*/
public function setTemplateName($name/*, $triggerDeprecation = true */)
{
$triggerDeprecation = 2 > \func_num_args() || \func_get_arg(1);
if ($triggerDeprecation) {
@trigger_error('The '.__METHOD__.' method is deprecated since version 2.8 and will be removed in 3.0. Use setSourceContext() instead.', \E_USER_DEPRECATED);
}
$this->name = $name;
foreach ($this->nodes as $node) {
if (null !== $node) {
$node->setTemplateName($name);
}
$node->setTemplateName($name, $triggerDeprecation);
}
}
public function getTemplateName()
{
return $this->name;
return $this->sourceContext ? $this->sourceContext->getName() : null;
}
public function setSourceContext(Source $source)
{
$this->sourceContext = $source;
foreach ($this->nodes as $node) {
if ($node instanceof Node) {
$node->setSourceContext($source);
}
$node->setSourceContext($source);
}
$this->setTemplateName($source->getName(), false);
}
public function getSourceContext()
{
return $this->sourceContext;
}
/**
* @deprecated since 1.27 (to be removed in 2.0)
*/
public function setFilename($name)
{
@trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use setTemplateName() instead.', E_USER_DEPRECATED);
$this->setTemplateName($name);
}
/**
* @deprecated since 1.27 (to be removed in 2.0)
*/
public function getFilename()
{
@trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getTemplateName() instead.', E_USER_DEPRECATED);
return $this->name;
}
}
class_alias('Twig\Node\Node', 'Twig_Node');

View File

@ -22,7 +22,7 @@ use Twig\Node\Expression\AbstractExpression;
*/
class PrintNode extends Node implements NodeOutputInterface
{
public function __construct(AbstractExpression $expr, $lineno, $tag = null)
public function __construct(AbstractExpression $expr, int $lineno, string $tag = null)
{
parent::__construct(['expr' => $expr], [], $lineno, $tag);
}

View File

@ -20,7 +20,7 @@ use Twig\Compiler;
*/
class SandboxNode extends Node
{
public function __construct(\Twig_NodeInterface $body, $lineno, $tag = null)
public function __construct(Node $body, int $lineno, string $tag = null)
{
parent::__construct(['body' => $body], [], $lineno, $tag);
}
@ -34,12 +34,19 @@ class SandboxNode extends Node
->write("\$this->sandbox->enableSandbox();\n")
->outdent()
->write("}\n")
->write("try {\n")
->indent()
->subcompile($this->getNode('body'))
->outdent()
->write("} finally {\n")
->indent()
->write("if (!\$alreadySandboxed) {\n")
->indent()
->write("\$this->sandbox->disableSandbox();\n")
->outdent()
->write("}\n")
->outdent()
->write("}\n")
;
}
}

View File

@ -13,7 +13,6 @@ namespace Twig\Node;
use Twig\Compiler;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Expression\FilterExpression;
/**
* Adds a check for the __toString() method when the variable is an object and the sandbox is activated.
@ -42,28 +41,14 @@ class SandboxedPrintNode extends PrintNode
;
} else {
$compiler
->write('$this->env->getExtension(\'\Twig\Extension\SandboxExtension\')->ensureToStringAllowed(')
->write('$this->extensions[SandboxExtension::class]->ensureToStringAllowed(')
->subcompile($expr)
->raw(");\n")
->raw(', ')
->repr($expr->getTemplateLine())
->raw(", \$this->source);\n")
;
}
}
/**
* Removes node filters.
*
* This is mostly needed when another visitor adds filters (like the escaper one).
*
* @return Node
*/
protected function removeNodeFilter(Node $node)
{
if ($node instanceof FilterExpression) {
return $this->removeNodeFilter($node->getNode('node'));
}
return $node;
}
}
class_alias('Twig\Node\SandboxedPrintNode', 'Twig_Node_SandboxedPrint');

View File

@ -21,7 +21,7 @@ use Twig\Node\Expression\ConstantExpression;
*/
class SetNode extends Node implements NodeCaptureInterface
{
public function __construct($capture, \Twig_NodeInterface $names, \Twig_NodeInterface $values, $lineno, $tag = null)
public function __construct(bool $capture, Node $names, Node $values, int $lineno, string $tag = null)
{
parent::__construct(['names' => $names, 'values' => $values], ['capture' => $capture, 'safe' => false], $lineno, $tag);

View File

@ -18,11 +18,13 @@ use Twig\Compiler;
*
* It removes spaces between HTML tags.
*
* @deprecated since Twig 2.7, to be removed in 3.0
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class SpacelessNode extends Node
class SpacelessNode extends Node implements NodeOutputInterface
{
public function __construct(\Twig_NodeInterface $body, $lineno, $tag = 'spaceless')
public function __construct(Node $body, int $lineno, string $tag = 'spaceless')
{
parent::__construct(['body' => $body], [], $lineno, $tag);
}

View File

@ -21,7 +21,7 @@ use Twig\Compiler;
*/
class TextNode extends Node implements NodeOutputInterface
{
public function __construct($data, $lineno)
public function __construct(string $data, int $lineno)
{
parent::__construct([], ['data' => $data], $lineno);
}

View File

@ -20,14 +20,14 @@ use Twig\Compiler;
*/
class WithNode extends Node
{
public function __construct(Node $body, Node $variables = null, $only = false, $lineno, $tag = null)
public function __construct(Node $body, ?Node $variables, bool $only, int $lineno, string $tag = null)
{
$nodes = ['body' => $body];
if (null !== $variables) {
$nodes['variables'] = $variables;
}
parent::__construct($nodes, ['only' => (bool) $only], $lineno, $tag);
parent::__construct($nodes, ['only' => $only], $lineno, $tag);
}
public function compile(Compiler $compiler)

View File

@ -11,6 +11,7 @@
namespace Twig;
use Twig\Node\Node;
use Twig\NodeVisitor\NodeVisitorInterface;
/**
@ -18,14 +19,12 @@ use Twig\NodeVisitor\NodeVisitorInterface;
*
* It visits all nodes and their children and calls the given visitor for each.
*
* @final
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class NodeTraverser
final class NodeTraverser
{
protected $env;
protected $visitors = [];
private $env;
private $visitors = [];
/**
* @param NodeVisitorInterface[] $visitors
@ -45,10 +44,8 @@ class NodeTraverser
/**
* Traverses a node and calls the registered visitors.
*
* @return \Twig_NodeInterface
*/
public function traverse(\Twig_NodeInterface $node)
public function traverse(Node $node): Node
{
ksort($this->visitors);
foreach ($this->visitors as $visitors) {
@ -60,24 +57,23 @@ class NodeTraverser
return $node;
}
protected function traverseForVisitor(NodeVisitorInterface $visitor, \Twig_NodeInterface $node = null)
/**
* @return Node|null
*/
private function traverseForVisitor(NodeVisitorInterface $visitor, Node $node)
{
if (null === $node) {
return;
}
$node = $visitor->enterNode($node, $this->env);
foreach ($node as $k => $n) {
if (null === $n) {
continue;
}
if (false !== ($m = $this->traverseForVisitor($visitor, $n)) && null !== $m) {
if ($m !== $n) {
$node->setNode($k, $m);
}
} else {
if (false === $m) {
@trigger_error('Returning "false" to remove a Node from NodeVisitorInterface::leaveNode() is deprecated since Twig version 2.9; return "null" instead.', \E_USER_DEPRECATED);
}
$node->removeNode($k);
}
}

View File

@ -23,21 +23,13 @@ use Twig\Node\Node;
*/
abstract class AbstractNodeVisitor implements NodeVisitorInterface
{
final public function enterNode(\Twig_NodeInterface $node, Environment $env)
final public function enterNode(Node $node, Environment $env)
{
if (!$node instanceof Node) {
throw new \LogicException(sprintf('%s only supports \Twig\Node\Node instances.', __CLASS__));
}
return $this->doEnterNode($node, $env);
}
final public function leaveNode(\Twig_NodeInterface $node, Environment $env)
final public function leaveNode(Node $node, Environment $env)
{
if (!$node instanceof Node) {
throw new \LogicException(sprintf('%s only supports \Twig\Node\Node instances.', __CLASS__));
}
return $this->doLeaveNode($node, $env);
}
@ -51,7 +43,7 @@ abstract class AbstractNodeVisitor implements NodeVisitorInterface
/**
* Called after child nodes are visited.
*
* @return Node|false|null The modified node or null if the node must be removed
* @return Node|null The modified node or null if the node must be removed
*/
abstract protected function doLeaveNode(Node $node, Environment $env);
}

View File

@ -12,6 +12,7 @@
namespace Twig\NodeVisitor;
use Twig\Environment;
use Twig\Extension\EscaperExtension;
use Twig\Node\AutoEscapeNode;
use Twig\Node\BlockNode;
use Twig\Node\BlockReferenceNode;
@ -27,18 +28,16 @@ use Twig\Node\PrintNode;
use Twig\NodeTraverser;
/**
* @final
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class EscaperNodeVisitor extends AbstractNodeVisitor
final class EscaperNodeVisitor extends AbstractNodeVisitor
{
protected $statusStack = [];
protected $blocks = [];
protected $safeAnalysis;
protected $traverser;
protected $defaultStrategy = false;
protected $safeVars = [];
private $statusStack = [];
private $blocks = [];
private $safeAnalysis;
private $traverser;
private $defaultStrategy = false;
private $safeVars = [];
public function __construct()
{
@ -48,7 +47,7 @@ class EscaperNodeVisitor extends AbstractNodeVisitor
protected function doEnterNode(Node $node, Environment $env)
{
if ($node instanceof ModuleNode) {
if ($env->hasExtension('\Twig\Extension\EscaperExtension') && $defaultStrategy = $env->getExtension('\Twig\Extension\EscaperExtension')->getDefaultStrategy($node->getTemplateName())) {
if ($env->hasExtension(EscaperExtension::class) && $defaultStrategy = $env->getExtension(EscaperExtension::class)->getDefaultStrategy($node->getTemplateName())) {
$this->defaultStrategy = $defaultStrategy;
}
$this->safeVars = [];
@ -128,7 +127,7 @@ class EscaperNodeVisitor extends AbstractNodeVisitor
return new InlinePrint($this->getEscaperFilter($type, $expression), $node->getTemplateLine());
}
protected function escapePrintNode(PrintNode $node, Environment $env, $type)
private function escapePrintNode(PrintNode $node, Environment $env, $type)
{
if (false === $type) {
return $node;
@ -145,7 +144,7 @@ class EscaperNodeVisitor extends AbstractNodeVisitor
return new $class($this->getEscaperFilter($type, $expression), $node->getTemplateLine());
}
protected function preEscapeFilterNode(FilterExpression $filter, Environment $env)
private function preEscapeFilterNode(FilterExpression $filter, Environment $env)
{
$name = $filter->getNode('filter')->getAttribute('value');
@ -164,7 +163,7 @@ class EscaperNodeVisitor extends AbstractNodeVisitor
return $filter;
}
protected function isSafeFor($type, \Twig_NodeInterface $expression, $env)
private function isSafeFor($type, Node $expression, $env)
{
$safe = $this->safeAnalysis->getSafe($expression);
@ -182,7 +181,7 @@ class EscaperNodeVisitor extends AbstractNodeVisitor
return \in_array($type, $safe) || \in_array('all', $safe);
}
protected function needEscaping(Environment $env)
private function needEscaping(Environment $env)
{
if (\count($this->statusStack)) {
return $this->statusStack[\count($this->statusStack) - 1];
@ -191,7 +190,7 @@ class EscaperNodeVisitor extends AbstractNodeVisitor
return $this->defaultStrategy ? $this->defaultStrategy : false;
}
protected function getEscaperFilter($type, \Twig_NodeInterface $node)
private function getEscaperFilter(string $type, Node $node): FilterExpression
{
$line = $node->getTemplateLine();
$name = new ConstantExpression('escape', $line);

View File

@ -0,0 +1,72 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\NodeVisitor;
use Twig\Environment;
use Twig\Node\Expression\AssignNameExpression;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Expression\GetAttrExpression;
use Twig\Node\Expression\MethodCallExpression;
use Twig\Node\Expression\NameExpression;
use Twig\Node\ImportNode;
use Twig\Node\ModuleNode;
use Twig\Node\Node;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
final class MacroAutoImportNodeVisitor implements NodeVisitorInterface
{
private $inAModule = false;
private $hasMacroCalls = false;
public function enterNode(Node $node, Environment $env)
{
if ($node instanceof ModuleNode) {
$this->inAModule = true;
$this->hasMacroCalls = false;
}
return $node;
}
public function leaveNode(Node $node, Environment $env)
{
if ($node instanceof ModuleNode) {
$this->inAModule = false;
if ($this->hasMacroCalls) {
$node->getNode('constructor_end')->setNode('_auto_macro_import', new ImportNode(new NameExpression('_self', 0), new AssignNameExpression('_self', 0), 0, 'import', true));
}
} elseif ($this->inAModule) {
if (
$node instanceof GetAttrExpression &&
$node->getNode('node') instanceof NameExpression &&
'_self' === $node->getNode('node')->getAttribute('name') &&
$node->getNode('attribute') instanceof ConstantExpression
) {
$this->hasMacroCalls = true;
$name = $node->getNode('attribute')->getAttribute('value');
$node = new MethodCallExpression($node->getNode('node'), 'macro_'.$name, $node->getNode('arguments'), $node->getTemplateLine());
$node->setAttribute('safe', true);
}
}
return $node;
}
public function getPriority()
{
// we must be ran before auto-escaping
return -10;
}
}

View File

@ -12,6 +12,7 @@
namespace Twig\NodeVisitor;
use Twig\Environment;
use Twig\Node\Node;
/**
* Interface for node visitor classes.
@ -23,16 +24,16 @@ interface NodeVisitorInterface
/**
* Called before child nodes are visited.
*
* @return \Twig_NodeInterface The modified node
* @return Node The modified node
*/
public function enterNode(\Twig_NodeInterface $node, Environment $env);
public function enterNode(Node $node, Environment $env);
/**
* Called after child nodes are visited.
*
* @return \Twig_NodeInterface|false|null The modified node or null if the node must be removed
* @return Node|null The modified node or null if the node must be removed
*/
public function leaveNode(\Twig_NodeInterface $node, Environment $env);
public function leaveNode(Node $node, Environment $env);
/**
* Returns the priority for this visitor.

View File

@ -13,8 +13,6 @@ namespace Twig\NodeVisitor;
use Twig\Environment;
use Twig\Node\BlockReferenceNode;
use Twig\Node\BodyNode;
use Twig\Node\Expression\AbstractExpression;
use Twig\Node\Expression\BlockReferenceExpression;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Expression\FilterExpression;
@ -22,12 +20,10 @@ use Twig\Node\Expression\FunctionExpression;
use Twig\Node\Expression\GetAttrExpression;
use Twig\Node\Expression\NameExpression;
use Twig\Node\Expression\ParentExpression;
use Twig\Node\Expression\TempNameExpression;
use Twig\Node\ForNode;
use Twig\Node\IncludeNode;
use Twig\Node\Node;
use Twig\Node\PrintNode;
use Twig\Node\SetTempNode;
/**
* Tries to optimize the AST.
@ -37,28 +33,25 @@ use Twig\Node\SetTempNode;
* You can configure which optimizations you want to activate via the
* optimizer mode.
*
* @final
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class OptimizerNodeVisitor extends AbstractNodeVisitor
final class OptimizerNodeVisitor extends AbstractNodeVisitor
{
const OPTIMIZE_ALL = -1;
const OPTIMIZE_NONE = 0;
const OPTIMIZE_FOR = 2;
const OPTIMIZE_RAW_FILTER = 4;
const OPTIMIZE_VAR_ACCESS = 8;
public const OPTIMIZE_ALL = -1;
public const OPTIMIZE_NONE = 0;
public const OPTIMIZE_FOR = 2;
public const OPTIMIZE_RAW_FILTER = 4;
// obsolete, does not do anything
public const OPTIMIZE_VAR_ACCESS = 8;
protected $loops = [];
protected $loopsTargets = [];
protected $optimizers;
protected $prependedNodes = [];
protected $inABody = false;
private $loops = [];
private $loopsTargets = [];
private $optimizers;
/**
* @param int $optimizers The optimizer mode
*/
public function __construct($optimizers = -1)
public function __construct(int $optimizers = -1)
{
if (!\is_int($optimizers) || $optimizers > (self::OPTIMIZE_FOR | self::OPTIMIZE_RAW_FILTER | self::OPTIMIZE_VAR_ACCESS)) {
throw new \InvalidArgumentException(sprintf('Optimizer mode "%s" is not valid.', $optimizers));
@ -73,27 +66,11 @@ class OptimizerNodeVisitor extends AbstractNodeVisitor
$this->enterOptimizeFor($node, $env);
}
if (\PHP_VERSION_ID < 50400 && self::OPTIMIZE_VAR_ACCESS === (self::OPTIMIZE_VAR_ACCESS & $this->optimizers) && !$env->isStrictVariables() && !$env->hasExtension('\Twig\Extension\SandboxExtension')) {
if ($this->inABody) {
if (!$node instanceof AbstractExpression) {
if ('Twig_Node' !== \get_class($node)) {
array_unshift($this->prependedNodes, []);
}
} else {
$node = $this->optimizeVariables($node, $env);
}
} elseif ($node instanceof BodyNode) {
$this->inABody = true;
}
}
return $node;
}
protected function doLeaveNode(Node $node, Environment $env)
{
$expression = $node instanceof AbstractExpression;
if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) {
$this->leaveOptimizeFor($node, $env);
}
@ -104,33 +81,6 @@ class OptimizerNodeVisitor extends AbstractNodeVisitor
$node = $this->optimizePrintNode($node, $env);
if (self::OPTIMIZE_VAR_ACCESS === (self::OPTIMIZE_VAR_ACCESS & $this->optimizers) && !$env->isStrictVariables() && !$env->hasExtension('\Twig\Extension\SandboxExtension')) {
if ($node instanceof BodyNode) {
$this->inABody = false;
} elseif ($this->inABody) {
if (!$expression && 'Twig_Node' !== \get_class($node) && $prependedNodes = array_shift($this->prependedNodes)) {
$nodes = [];
foreach (array_unique($prependedNodes) as $name) {
$nodes[] = new SetTempNode($name, $node->getTemplateLine());
}
$nodes[] = $node;
$node = new Node($nodes);
}
}
}
return $node;
}
protected function optimizeVariables(\Twig_NodeInterface $node, Environment $env)
{
if ('Twig_Node_Expression_Name' === \get_class($node) && $node->isSimple()) {
$this->prependedNodes[0][] = $node->getAttribute('name');
return new TempNameExpression($node->getAttribute('name'), $node->getTemplateLine());
}
return $node;
}
@ -140,10 +90,8 @@ class OptimizerNodeVisitor extends AbstractNodeVisitor
* It replaces:
*
* * "echo $this->render(Parent)Block()" with "$this->display(Parent)Block()"
*
* @return \Twig_NodeInterface
*/
protected function optimizePrintNode(\Twig_NodeInterface $node, Environment $env)
private function optimizePrintNode(Node $node, Environment $env): Node
{
if (!$node instanceof PrintNode) {
return $node;
@ -164,10 +112,8 @@ class OptimizerNodeVisitor extends AbstractNodeVisitor
/**
* Removes "raw" filters.
*
* @return \Twig_NodeInterface
*/
protected function optimizeRawFilter(\Twig_NodeInterface $node, Environment $env)
private function optimizeRawFilter(Node $node, Environment $env): Node
{
if ($node instanceof FilterExpression && 'raw' == $node->getNode('filter')->getAttribute('value')) {
return $node->getNode('node');
@ -179,7 +125,7 @@ class OptimizerNodeVisitor extends AbstractNodeVisitor
/**
* Optimizes "for" tag by removing the "loop" variable creation whenever possible.
*/
protected function enterOptimizeFor(\Twig_NodeInterface $node, Environment $env)
private function enterOptimizeFor(Node $node, Environment $env)
{
if ($node instanceof ForNode) {
// disable the loop variable by default
@ -243,7 +189,7 @@ class OptimizerNodeVisitor extends AbstractNodeVisitor
/**
* Optimizes "for" tag by removing the "loop" variable creation whenever possible.
*/
protected function leaveOptimizeFor(\Twig_NodeInterface $node, Environment $env)
private function leaveOptimizeFor(Node $node, Environment $env)
{
if ($node instanceof ForNode) {
array_shift($this->loops);
@ -252,12 +198,12 @@ class OptimizerNodeVisitor extends AbstractNodeVisitor
}
}
protected function addLoopToCurrent()
private function addLoopToCurrent()
{
$this->loops[0]->setAttribute('with_loop', true);
}
protected function addLoopToAll()
private function addLoopToAll()
{
foreach ($this->loops as $loop) {
$loop->setAttribute('with_loop', true);

View File

@ -23,20 +23,17 @@ use Twig\Node\Expression\NameExpression;
use Twig\Node\Expression\ParentExpression;
use Twig\Node\Node;
/**
* @final
*/
class SafeAnalysisNodeVisitor extends AbstractNodeVisitor
final class SafeAnalysisNodeVisitor extends AbstractNodeVisitor
{
protected $data = [];
protected $safeVars = [];
private $data = [];
private $safeVars = [];
public function setSafeVars($safeVars)
{
$this->safeVars = $safeVars;
}
public function getSafe(\Twig_NodeInterface $node)
public function getSafe(Node $node)
{
$hash = spl_object_hash($node);
if (!isset($this->data[$hash])) {
@ -56,7 +53,7 @@ class SafeAnalysisNodeVisitor extends AbstractNodeVisitor
}
}
protected function setSafe(\Twig_NodeInterface $node, array $safe)
private function setSafe(Node $node, array $safe)
{
$hash = spl_object_hash($node);
if (isset($this->data[$hash])) {
@ -125,8 +122,7 @@ class SafeAnalysisNodeVisitor extends AbstractNodeVisitor
}
} elseif ($node instanceof GetAttrExpression && $node->getNode('node') instanceof NameExpression) {
$name = $node->getNode('node')->getAttribute('name');
// attributes on template instances are safe
if ('_self' == $name || \in_array($name, $this->safeVars)) {
if (\in_array($name, $this->safeVars)) {
$this->setSafe($node, ['all']);
} else {
$this->setSafe($node, []);
@ -138,7 +134,7 @@ class SafeAnalysisNodeVisitor extends AbstractNodeVisitor
return $node;
}
protected function intersectSafe(array $a = null, array $b = null)
private function intersectSafe(array $a = null, array $b = null): array
{
if (null === $a || null === $b) {
return [];

View File

@ -12,6 +12,7 @@
namespace Twig\NodeVisitor;
use Twig\Environment;
use Twig\Node\CheckSecurityCallNode;
use Twig\Node\CheckSecurityNode;
use Twig\Node\CheckToStringNode;
use Twig\Node\Expression\Binary\ConcatBinary;
@ -26,16 +27,14 @@ use Twig\Node\PrintNode;
use Twig\Node\SetNode;
/**
* @final
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class SandboxNodeVisitor extends AbstractNodeVisitor
final class SandboxNodeVisitor extends AbstractNodeVisitor
{
protected $inAModule = false;
protected $tags;
protected $filters;
protected $functions;
private $inAModule = false;
private $tags;
private $filters;
private $functions;
private $needsToStringWrap = false;
@ -102,7 +101,8 @@ class SandboxNodeVisitor extends AbstractNodeVisitor
if ($node instanceof ModuleNode) {
$this->inAModule = false;
$node->getNode('constructor_end')->setNode('_security_check', new Node([new CheckSecurityNode($this->filters, $this->tags, $this->functions), $node->getNode('display_start')]));
$node->setNode('constructor_end', new Node([new CheckSecurityCallNode(), $node->getNode('constructor_end')]));
$node->setNode('class_end', new Node([new CheckSecurityNode($this->filters, $this->tags, $this->functions), $node->getNode('class_end')]));
} elseif ($this->inAModule) {
if ($node instanceof PrintNode || $node instanceof SetNode) {
$this->needsToStringWrap = false;
@ -112,7 +112,7 @@ class SandboxNodeVisitor extends AbstractNodeVisitor
return $node;
}
private function wrapNode(Node $node, $name)
private function wrapNode(Node $node, string $name)
{
$expr = $node->getNode($name);
if ($expr instanceof NameExpression || $expr instanceof GetAttrExpression) {
@ -120,7 +120,7 @@ class SandboxNodeVisitor extends AbstractNodeVisitor
}
}
private function wrapArrayNode(Node $node, $name)
private function wrapArrayNode(Node $node, string $name)
{
$args = $node->getNode($name);
foreach ($args as $name => $_) {

View File

@ -23,8 +23,8 @@ use Twig\Node\Node;
use Twig\Node\NodeCaptureInterface;
use Twig\Node\NodeOutputInterface;
use Twig\Node\PrintNode;
use Twig\Node\SpacelessNode;
use Twig\Node\TextNode;
use Twig\NodeVisitor\NodeVisitorInterface;
use Twig\TokenParser\TokenParserInterface;
/**
@ -32,22 +32,21 @@ use Twig\TokenParser\TokenParserInterface;
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Parser implements \Twig_ParserInterface
class Parser
{
protected $stack = [];
protected $stream;
protected $parent;
protected $handlers;
protected $visitors;
protected $expressionParser;
protected $blocks;
protected $blockStack;
protected $macros;
protected $env;
protected $reservedMacroNames;
protected $importedSymbols;
protected $traits;
protected $embeddedTemplates = [];
private $stack = [];
private $stream;
private $parent;
private $handlers;
private $visitors;
private $expressionParser;
private $blocks;
private $blockStack;
private $macros;
private $env;
private $importedSymbols;
private $traits;
private $embeddedTemplates = [];
private $varNameSalt = 0;
public function __construct(Environment $env)
@ -55,48 +54,25 @@ class Parser implements \Twig_ParserInterface
$this->env = $env;
}
/**
* @deprecated since 1.27 (to be removed in 2.0)
*/
public function getEnvironment()
{
@trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0.', E_USER_DEPRECATED);
return $this->env;
}
public function getVarName()
{
return sprintf('__internal_%s', hash('sha256', __METHOD__.$this->stream->getSourceContext()->getCode().$this->varNameSalt++));
}
/**
* @deprecated since 1.27 (to be removed in 2.0). Use $parser->getStream()->getSourceContext()->getPath() instead.
*/
public function getFilename()
{
@trigger_error(sprintf('The "%s" method is deprecated since version 1.27 and will be removed in 2.0. Use $parser->getStream()->getSourceContext()->getPath() instead.', __METHOD__), E_USER_DEPRECATED);
return $this->stream->getSourceContext()->getName();
return sprintf('__internal_parse_%d', $this->varNameSalt++);
}
public function parse(TokenStream $stream, $test = null, $dropNeedle = false)
{
// push all variables into the stack to keep the current state of the parser
// using get_object_vars() instead of foreach would lead to https://bugs.php.net/71336
// This hack can be removed when min version if PHP 7.0
$vars = [];
foreach ($this as $k => $v) {
$vars[$k] = $v;
}
unset($vars['stack'], $vars['env'], $vars['handlers'], $vars['visitors'], $vars['expressionParser'], $vars['reservedMacroNames']);
$vars = get_object_vars($this);
unset($vars['stack'], $vars['env'], $vars['handlers'], $vars['visitors'], $vars['expressionParser'], $vars['reservedMacroNames'], $vars['varNameSalt']);
$this->stack[] = $vars;
// tag handlers
if (null === $this->handlers) {
$this->handlers = $this->env->getTokenParsers();
$this->handlers->setParser($this);
$this->handlers = [];
foreach ($this->env->getTokenParsers() as $handler) {
$handler->setParser($this);
$this->handlers[$handler->getTag()] = $handler;
}
}
// node visitors
@ -116,7 +92,6 @@ class Parser implements \Twig_ParserInterface
$this->blockStack = [];
$this->importedSymbols = [[]];
$this->embeddedTemplates = [];
$this->varNameSalt = 0;
try {
$body = $this->subparse($test, $dropNeedle);
@ -156,27 +131,27 @@ class Parser implements \Twig_ParserInterface
$rv = [];
while (!$this->stream->isEOF()) {
switch ($this->getCurrentToken()->getType()) {
case Token::TEXT_TYPE:
case /* Token::TEXT_TYPE */ 0:
$token = $this->stream->next();
$rv[] = new TextNode($token->getValue(), $token->getLine());
break;
case Token::VAR_START_TYPE:
case /* Token::VAR_START_TYPE */ 2:
$token = $this->stream->next();
$expr = $this->expressionParser->parseExpression();
$this->stream->expect(Token::VAR_END_TYPE);
$this->stream->expect(/* Token::VAR_END_TYPE */ 4);
$rv[] = new PrintNode($expr, $token->getLine());
break;
case Token::BLOCK_START_TYPE:
case /* Token::BLOCK_START_TYPE */ 1:
$this->stream->next();
$token = $this->getCurrentToken();
if (Token::NAME_TYPE !== $token->getType()) {
if (/* Token::NAME_TYPE */ 5 !== $token->getType()) {
throw new SyntaxError('A block must start with a tag name.', $token->getLine(), $this->stream->getSourceContext());
}
if (null !== $test && \call_user_func($test, $token)) {
if (null !== $test && $test($token)) {
if ($dropNeedle) {
$this->stream->next();
}
@ -188,8 +163,7 @@ class Parser implements \Twig_ParserInterface
return new Node($rv, [], $lineno);
}
$subparser = $this->handlers->getTokenParser($token->getValue());
if (null === $subparser) {
if (!isset($this->handlers[$token->getValue()])) {
if (null !== $test) {
$e = new SyntaxError(sprintf('Unexpected "%s" tag', $token->getValue()), $token->getLine(), $this->stream->getSourceContext());
@ -206,6 +180,7 @@ class Parser implements \Twig_ParserInterface
$this->stream->next();
$subparser = $this->handlers[$token->getValue()];
$node = $subparser->parse($token);
if (null !== $node) {
$rv[] = $node;
@ -224,26 +199,6 @@ class Parser implements \Twig_ParserInterface
return new Node($rv, [], $lineno);
}
/**
* @deprecated since 1.27 (to be removed in 2.0)
*/
public function addHandler($name, $class)
{
@trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0.', E_USER_DEPRECATED);
$this->handlers[$name] = $class;
}
/**
* @deprecated since 1.27 (to be removed in 2.0)
*/
public function addNodeVisitor(NodeVisitorInterface $visitor)
{
@trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0.', E_USER_DEPRECATED);
$this->visitors[] = $visitor;
}
public function getBlockStack()
{
return $this->blockStack;
@ -286,28 +241,17 @@ class Parser implements \Twig_ParserInterface
public function setMacro($name, MacroNode $node)
{
if ($this->isReservedMacroName($name)) {
throw new SyntaxError(sprintf('"%s" cannot be used as a macro name as it is a reserved keyword.', $name), $node->getTemplateLine(), $this->stream->getSourceContext());
}
$this->macros[$name] = $node;
}
/**
* @deprecated since Twig 2.7 as there are no reserved macro names anymore, will be removed in 3.0.
*/
public function isReservedMacroName($name)
{
if (null === $this->reservedMacroNames) {
$this->reservedMacroNames = [];
$r = new \ReflectionClass($this->env->getBaseTemplateClass());
foreach ($r->getMethods() as $method) {
$methodName = strtolower($method->getName());
@trigger_error(sprintf('The "%s" method is deprecated since Twig 2.7 and will be removed in 3.0.', __METHOD__), \E_USER_DEPRECATED);
if ('get' === substr($methodName, 0, 3) && isset($methodName[3])) {
$this->reservedMacroNames[] = substr($methodName, 3);
}
}
}
return \in_array(strtolower($name), $this->reservedMacroNames);
return false;
}
public function addTrait($trait)
@ -334,19 +278,8 @@ class Parser implements \Twig_ParserInterface
public function getImportedSymbol($type, $alias)
{
if (null !== $this->peekBlockStack()) {
foreach ($this->importedSymbols as $functions) {
if (isset($functions[$type][$alias])) {
if (\count($this->blockStack) > 1) {
return null;
}
return $functions[$type][$alias];
}
}
} else {
return isset($this->importedSymbols[0][$type][$alias]) ? $this->importedSymbols[0][$type][$alias] : null;
}
// if the symbol does not exist in the current scope (0), try in the main/global scope (last index)
return $this->importedSymbols[0][$type][$alias] ?? ($this->importedSymbols[\count($this->importedSymbols) - 1][$type][$alias] ?? null);
}
public function isMainScope()
@ -398,13 +331,14 @@ class Parser implements \Twig_ParserInterface
return $this->stream->getCurrent();
}
protected function filterBodyNodes(\Twig_NodeInterface $node)
private function filterBodyNodes(Node $node, bool $nested = false)
{
// check that the body does not contain non-empty output nodes
if (
($node instanceof TextNode && !ctype_space($node->getAttribute('data')))
||
(!$node instanceof TextNode && !$node instanceof BlockReferenceNode && $node instanceof NodeOutputInterface)
// the "&& !$node instanceof SpacelessNode" part of the condition must be removed in 3.0
(!$node instanceof TextNode && !$node instanceof BlockReferenceNode && ($node instanceof NodeOutputInterface && !$node instanceof SpacelessNode))
) {
if (false !== strpos((string) $node, \chr(0xEF).\chr(0xBB).\chr(0xBF))) {
$t = substr($node->getAttribute('data'), 3);
@ -417,17 +351,37 @@ class Parser implements \Twig_ParserInterface
throw new SyntaxError('A template that extends another one cannot include content outside Twig blocks. Did you forget to put the content inside a {% block %} tag?', $node->getTemplateLine(), $this->stream->getSourceContext());
}
// bypass nodes that will "capture" the output
// bypass nodes that "capture" the output
if ($node instanceof NodeCaptureInterface) {
// a "block" tag in such a node will serve as a block definition AND be displayed in place as well
return $node;
}
if ($node instanceof NodeOutputInterface) {
// to be removed completely in Twig 3.0
if (!$nested && $node instanceof SpacelessNode) {
@trigger_error(sprintf('Using the spaceless tag at the root level of a child template in "%s" at line %d is deprecated since Twig 2.5.0 and will become a syntax error in 3.0.', $this->stream->getSourceContext()->getName(), $node->getTemplateLine()), \E_USER_DEPRECATED);
}
// "block" tags that are not captured (see above) are only used for defining
// the content of the block. In such a case, nesting it does not work as
// expected as the definition is not part of the default template code flow.
if ($nested && ($node instanceof BlockReferenceNode || $node instanceof \Twig_Node_BlockReference)) {
//throw new SyntaxError('A block definition cannot be nested under non-capturing nodes.', $node->getTemplateLine(), $this->stream->getSourceContext());
@trigger_error(sprintf('Nesting a block definition under a non-capturing node in "%s" at line %d is deprecated since Twig 2.5.0 and will become a syntax error in 3.0.', $this->stream->getSourceContext()->getName(), $node->getTemplateLine()), \E_USER_DEPRECATED);
return;
}
// the "&& !$node instanceof SpacelessNode" part of the condition must be removed in 3.0
if ($node instanceof NodeOutputInterface && !$node instanceof SpacelessNode) {
return;
}
// here, $nested means "being at the root level of a child template"
// we need to discard the wrapping "Twig_Node" for the "body" node
$nested = $nested || ('Twig_Node' !== \get_class($node) && Node::class !== \get_class($node));
foreach ($node as $k => $n) {
if (null !== $n && null === $this->filterBodyNodes($n)) {
if (null !== $n && null === $this->filterBodyNodes($n, $nested)) {
$node->removeNode($k);
}
}

View File

@ -31,7 +31,7 @@ abstract class BaseDumper
abstract protected function formatTime(Profile $profile, $percent);
private function dumpProfile(Profile $profile, $prefix = '', $sibling = false)
private function dumpProfile(Profile $profile, $prefix = '', $sibling = false): string
{
if ($profile->isRoot()) {
$this->root = $profile->getDuration();

View File

@ -15,10 +15,8 @@ use Twig\Profiler\Profile;
/**
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class BlackfireDumper
final class BlackfireDumper
{
public function dump(Profile $profile)
{
@ -42,7 +40,7 @@ EOF;
return $str;
}
private function dumpChildren($parent, Profile $profile, &$data)
private function dumpChildren(string $parent, Profile $profile, &$data)
{
foreach ($profile as $p) {
if ($p->isTemplate()) {
@ -55,7 +53,7 @@ EOF;
}
}
private function dumpProfile($edge, Profile $profile, &$data)
private function dumpProfile(string $edge, Profile $profile, &$data)
{
if (isset($data[$edge])) {
++$data[$edge]['ct'];

View File

@ -15,10 +15,8 @@ use Twig\Profiler\Profile;
/**
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class HtmlDumper extends BaseDumper
final class HtmlDumper extends BaseDumper
{
private static $colors = [
'block' => '#dfd',

View File

@ -15,10 +15,8 @@ use Twig\Profiler\Profile;
/**
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class TextDumper extends BaseDumper
final class TextDumper extends BaseDumper
{
protected function formatTemplate(Profile $profile, $prefix)
{

View File

@ -21,7 +21,7 @@ use Twig\Node\Node;
*/
class EnterProfileNode extends Node
{
public function __construct($extensionName, $type, $name, $varName)
public function __construct(string $extensionName, string $type, string $name, string $varName)
{
parent::__construct([], ['extension_name' => $extensionName, 'name' => $name, 'type' => $type, 'var_name' => $varName]);
}
@ -29,9 +29,9 @@ class EnterProfileNode extends Node
public function compile(Compiler $compiler)
{
$compiler
->write(sprintf('$%s = $this->env->getExtension(', $this->getAttribute('var_name')))
->write(sprintf('$%s = $this->extensions[', $this->getAttribute('var_name')))
->repr($this->getAttribute('extension_name'))
->raw(");\n")
->raw("];\n")
->write(sprintf('$%s->enter($%s = new \Twig\Profiler\Profile($this->getTemplateName(), ', $this->getAttribute('var_name'), $this->getAttribute('var_name').'_prof'))
->repr($this->getAttribute('type'))
->raw(', ')

View File

@ -21,7 +21,7 @@ use Twig\Node\Node;
*/
class LeaveProfileNode extends Node
{
public function __construct($varName)
public function __construct(string $varName)
{
parent::__construct([], ['var_name' => $varName]);
}

View File

@ -24,16 +24,16 @@ use Twig\Profiler\Profile;
/**
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class ProfilerNodeVisitor extends AbstractNodeVisitor
final class ProfilerNodeVisitor extends AbstractNodeVisitor
{
private $extensionName;
private $varName;
public function __construct($extensionName)
public function __construct(string $extensionName)
{
$this->extensionName = $extensionName;
$this->varName = sprintf('__internal_%s', hash(\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', $extensionName));
}
protected function doEnterNode(Node $node, Environment $env)
@ -44,33 +44,25 @@ class ProfilerNodeVisitor extends AbstractNodeVisitor
protected function doLeaveNode(Node $node, Environment $env)
{
if ($node instanceof ModuleNode) {
$varName = $this->getVarName();
$node->setNode('display_start', new Node([new EnterProfileNode($this->extensionName, Profile::TEMPLATE, $node->getTemplateName(), $varName), $node->getNode('display_start')]));
$node->setNode('display_end', new Node([new LeaveProfileNode($varName), $node->getNode('display_end')]));
$node->setNode('display_start', new Node([new EnterProfileNode($this->extensionName, Profile::TEMPLATE, $node->getTemplateName(), $this->varName), $node->getNode('display_start')]));
$node->setNode('display_end', new Node([new LeaveProfileNode($this->varName), $node->getNode('display_end')]));
} elseif ($node instanceof BlockNode) {
$varName = $this->getVarName();
$node->setNode('body', new BodyNode([
new EnterProfileNode($this->extensionName, Profile::BLOCK, $node->getAttribute('name'), $varName),
new EnterProfileNode($this->extensionName, Profile::BLOCK, $node->getAttribute('name'), $this->varName),
$node->getNode('body'),
new LeaveProfileNode($varName),
new LeaveProfileNode($this->varName),
]));
} elseif ($node instanceof MacroNode) {
$varName = $this->getVarName();
$node->setNode('body', new BodyNode([
new EnterProfileNode($this->extensionName, Profile::MACRO, $node->getAttribute('name'), $varName),
new EnterProfileNode($this->extensionName, Profile::MACRO, $node->getAttribute('name'), $this->varName),
$node->getNode('body'),
new LeaveProfileNode($varName),
new LeaveProfileNode($this->varName),
]));
}
return $node;
}
private function getVarName()
{
return sprintf('__internal_%s', hash('sha256', $this->extensionName));
}
public function getPriority()
{
return 0;

View File

@ -14,14 +14,14 @@ namespace Twig\Profiler;
/**
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
* @final since Twig 2.4.0
*/
class Profile implements \IteratorAggregate, \Serializable
{
const ROOT = 'ROOT';
const BLOCK = 'block';
const TEMPLATE = 'template';
const MACRO = 'macro';
public const ROOT = 'ROOT';
public const BLOCK = 'block';
public const TEMPLATE = 'template';
public const MACRO = 'macro';
private $template;
private $name;
@ -30,8 +30,12 @@ class Profile implements \IteratorAggregate, \Serializable
private $ends = [];
private $profiles = [];
public function __construct($template = 'main', $type = self::ROOT, $name = 'main')
public function __construct(string $template = 'main', string $type = self::ROOT, string $name = 'main')
{
if (__CLASS__ !== static::class) {
@trigger_error('Overriding '.__CLASS__.' is deprecated since Twig 2.4.0 and the class will be final in 3.0.', \E_USER_DEPRECATED);
}
$this->template = $template;
$this->type = $type;
$this->name = 0 === strpos($name, '__internal_') ? 'INTERNAL' : $name;
@ -153,17 +157,18 @@ class Profile implements \IteratorAggregate, \Serializable
$this->enter();
}
public function getIterator()
#[\ReturnTypeWillChange]
public function getIterator(): \Traversable
{
return new \ArrayIterator($this->profiles);
}
public function serialize()
public function serialize(): string
{
return serialize($this->__serialize());
}
public function unserialize($data)
public function unserialize($data): void
{
$this->__unserialize(unserialize($data));
}
@ -171,7 +176,7 @@ class Profile implements \IteratorAggregate, \Serializable
/**
* @internal
*/
public function __serialize()
public function __serialize(): array
{
return [$this->template, $this->name, $this->type, $this->starts, $this->ends, $this->profiles];
}
@ -179,7 +184,7 @@ class Profile implements \IteratorAggregate, \Serializable
/**
* @internal
*/
public function __unserialize(array $data)
public function __unserialize(array $data): void
{
list($this->template, $this->name, $this->type, $this->starts, $this->ends, $this->profiles) = $data;
}

View File

@ -23,7 +23,7 @@ class FactoryRuntimeLoader implements RuntimeLoaderInterface
/**
* @param array $map An array where keys are class names and values factory callables
*/
public function __construct($map = [])
public function __construct(array $map = [])
{
$this->map = $map;
}

View File

@ -15,13 +15,24 @@ namespace Twig\Sandbox;
* Exception thrown when a not allowed filter is used in a template.
*
* @author Martin Hasoň <martin.hason@gmail.com>
*
* @final
*/
class SecurityNotAllowedFilterError extends SecurityError
{
private $filterName;
public function __construct($message, $functionName, $lineno = -1, $filename = null, \Exception $previous = null)
public function __construct(string $message, string $functionName, int $lineno = -1, string $filename = null, \Exception $previous = null)
{
if (-1 !== $lineno) {
@trigger_error(sprintf('Passing $lineno as a 3th argument of the %s constructor is deprecated since Twig 2.8.1.', __CLASS__), \E_USER_DEPRECATED);
}
if (null !== $filename) {
@trigger_error(sprintf('Passing $filename as a 4th argument of the %s constructor is deprecated since Twig 2.8.1.', __CLASS__), \E_USER_DEPRECATED);
}
if (null !== $previous) {
@trigger_error(sprintf('Passing $previous as a 5th argument of the %s constructor is deprecated since Twig 2.8.1.', __CLASS__), \E_USER_DEPRECATED);
}
parent::__construct($message, $lineno, $filename, $previous);
$this->filterName = $functionName;
}

View File

@ -15,13 +15,24 @@ namespace Twig\Sandbox;
* Exception thrown when a not allowed function is used in a template.
*
* @author Martin Hasoň <martin.hason@gmail.com>
*
* @final
*/
class SecurityNotAllowedFunctionError extends SecurityError
{
private $functionName;
public function __construct($message, $functionName, $lineno = -1, $filename = null, \Exception $previous = null)
public function __construct(string $message, string $functionName, int $lineno = -1, string $filename = null, \Exception $previous = null)
{
if (-1 !== $lineno) {
@trigger_error(sprintf('Passing $lineno as a 3th argument of the %s constructor is deprecated since Twig 2.8.1.', __CLASS__), \E_USER_DEPRECATED);
}
if (null !== $filename) {
@trigger_error(sprintf('Passing $filename as a 4th argument of the %s constructor is deprecated since Twig 2.8.1.', __CLASS__), \E_USER_DEPRECATED);
}
if (null !== $previous) {
@trigger_error(sprintf('Passing $previous as a 5th argument of the %s constructor is deprecated since Twig 2.8.1.', __CLASS__), \E_USER_DEPRECATED);
}
parent::__construct($message, $lineno, $filename, $previous);
$this->functionName = $functionName;
}

View File

@ -15,14 +15,25 @@ namespace Twig\Sandbox;
* Exception thrown when a not allowed class method is used in a template.
*
* @author Kit Burton-Senior <mail@kitbs.com>
*
* @final
*/
class SecurityNotAllowedMethodError extends SecurityError
{
private $className;
private $methodName;
public function __construct($message, $className, $methodName, $lineno = -1, $filename = null, \Exception $previous = null)
public function __construct(string $message, string $className, string $methodName, int $lineno = -1, string $filename = null, \Exception $previous = null)
{
if (-1 !== $lineno) {
@trigger_error(sprintf('Passing $lineno as a 3th argument of the %s constructor is deprecated since Twig 2.8.1.', __CLASS__), \E_USER_DEPRECATED);
}
if (null !== $filename) {
@trigger_error(sprintf('Passing $filename as a 4th argument of the %s constructor is deprecated since Twig 2.8.1.', __CLASS__), \E_USER_DEPRECATED);
}
if (null !== $previous) {
@trigger_error(sprintf('Passing $previous as a 5th argument of the %s constructor is deprecated since Twig 2.8.1.', __CLASS__), \E_USER_DEPRECATED);
}
parent::__construct($message, $lineno, $filename, $previous);
$this->className = $className;
$this->methodName = $methodName;

View File

@ -15,14 +15,25 @@ namespace Twig\Sandbox;
* Exception thrown when a not allowed class property is used in a template.
*
* @author Kit Burton-Senior <mail@kitbs.com>
*
* @final
*/
class SecurityNotAllowedPropertyError extends SecurityError
{
private $className;
private $propertyName;
public function __construct($message, $className, $propertyName, $lineno = -1, $filename = null, \Exception $previous = null)
public function __construct(string $message, string $className, string $propertyName, int $lineno = -1, string $filename = null, \Exception $previous = null)
{
if (-1 !== $lineno) {
@trigger_error(sprintf('Passing $lineno as a 3th argument of the %s constructor is deprecated since Twig 2.8.1.', __CLASS__), \E_USER_DEPRECATED);
}
if (null !== $filename) {
@trigger_error(sprintf('Passing $filename as a 4th argument of the %s constructor is deprecated since Twig 2.8.1.', __CLASS__), \E_USER_DEPRECATED);
}
if (null !== $previous) {
@trigger_error(sprintf('Passing $previous as a 5th argument of the %s constructor is deprecated since Twig 2.8.1.', __CLASS__), \E_USER_DEPRECATED);
}
parent::__construct($message, $lineno, $filename, $previous);
$this->className = $className;
$this->propertyName = $propertyName;

View File

@ -15,13 +15,24 @@ namespace Twig\Sandbox;
* Exception thrown when a not allowed tag is used in a template.
*
* @author Martin Hasoň <martin.hason@gmail.com>
*
* @final
*/
class SecurityNotAllowedTagError extends SecurityError
{
private $tagName;
public function __construct($message, $tagName, $lineno = -1, $filename = null, \Exception $previous = null)
public function __construct(string $message, string $tagName, int $lineno = -1, string $filename = null, \Exception $previous = null)
{
if (-1 !== $lineno) {
@trigger_error(sprintf('Passing $lineno as a 3th argument of the %s constructor is deprecated since Twig 2.8.1.', __CLASS__), \E_USER_DEPRECATED);
}
if (null !== $filename) {
@trigger_error(sprintf('Passing $filename as a 4th argument of the %s constructor is deprecated since Twig 2.8.1.', __CLASS__), \E_USER_DEPRECATED);
}
if (null !== $previous) {
@trigger_error(sprintf('Passing $previous as a 5th argument of the %s constructor is deprecated since Twig 2.8.1.', __CLASS__), \E_USER_DEPRECATED);
}
parent::__construct($message, $lineno, $filename, $previous);
$this->tagName = $tagName;
}

Some files were not shown because too many files have changed in this diff Show More