diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9286b499b5b..909e97dd62b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,6 +29,7 @@ jobs: - '8.0' - '8.1' - '8.2' + - '8.3' experimental: [false] steps: @@ -75,6 +76,7 @@ jobs: - '8.0' - '8.1' - '8.2' + - '8.3' extension: - 'extra/cache-extra' - 'extra/cssinliner-extra' diff --git a/CHANGELOG b/CHANGELOG index a0b9de2b713..087fd408090 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,17 @@ -# 3.7.2 (2023-XX-XX) +# 3.8.1 (2023-XX-XX) * n/a +# 3.8.0 (2023-11-21) + + * Catch errors thrown during template rendering + * Fix IntlExtension::formatDateTime use of date formatter prototype + * Fix premature loop exit in Security Policy lookup of allowed methods/properties + * Remove NumberFormatter::TYPE_CURRENCY (deprecated in PHP 8.3) + * Restore return type annotations + * Allow Symfony 7 packages to be installed + * Deprecate `twig_test_iterable` function. Use the native `is_iterable` instead. + # 3.7.1 (2023-08-28) * Fix some phpdocs diff --git a/composer.json b/composer.json index 04dc93ffccf..1b1726fe882 100644 --- a/composer.json +++ b/composer.json @@ -25,6 +25,7 @@ ], "require": { "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.22", "symfony/polyfill-mbstring": "^1.3", "symfony/polyfill-ctype": "^1.8" }, diff --git a/doc/deprecated.rst b/doc/deprecated.rst index ac22338e184..b624b569628 100644 --- a/doc/deprecated.rst +++ b/doc/deprecated.rst @@ -4,3 +4,6 @@ Deprecated Features This document lists deprecated features in Twig 3.x. Deprecated features are kept for backward compatibility and removed in the next major release (a feature that was deprecated in Twig 3.x is removed in Twig 4.0). + +The `twig_test_iterable` function is deprecated; use the native `is_iterable` +instead. diff --git a/doc/tags/macro.rst b/doc/tags/macro.rst index 42fc460cae8..effa6b6bdc9 100644 --- a/doc/tags/macro.rst +++ b/doc/tags/macro.rst @@ -104,10 +104,6 @@ When calling ``import`` or ``from`` from a ``block`` tag, the imported macros are only defined in the current block and they override macros defined at the template level with the same names. -When calling ``import`` or ``from`` from a ``macro`` tag, the imported macros -are only defined in the current macro and they override macros defined at the -template level with the same names. - Checking if a Macro is defined ------------------------------ diff --git a/doc/templates.rst b/doc/templates.rst index c145895da0a..ccfea484cf9 100644 --- a/doc/templates.rst +++ b/doc/templates.rst @@ -59,7 +59,7 @@ Many IDEs support syntax highlighting and auto-completion for Twig: * *Notepad++* via the `Notepad++ Twig Highlighter`_ * *Emacs* via `web-mode.el`_ * *Atom* via the `PHP-twig for atom`_ -* *Visual Studio Code* via the `Twig pack`_ or `Modern Twig`_ +* *Visual Studio Code* via the `Twig pack`_, `Modern Twig`_ or `Twiggy`_ You might also be interested in: @@ -753,10 +753,10 @@ The following operators don't fit into any of the other categories: .. code-block:: twig - {{ 1..5 }} + {% for i in 1..5 %}{{ i }}{% endfor %} - {# equivalent to #} - {{ range(1, 5) }} + {# is equivalent to #} + {% for i in range(1, 5) %}{{ i }}{% endfor %} Note that you must use parentheses when combining it with the filter operator due to the :ref:`operator precedence rules `: @@ -891,3 +891,4 @@ Twig can be extended. If you want to create your own extensions, read the .. _`Twig pack`: https://marketplace.visualstudio.com/items?itemName=bajdzis.vscode-twig-pack .. _`Modern Twig`: https://marketplace.visualstudio.com/items?itemName=Stanislav.vscode-twig .. _`Twig Language Server`: https://github.com/kaermorchen/twig-language-server/tree/master/packages/language-server +.. _`Twiggy`: https://marketplace.visualstudio.com/items?itemName=moetelo.twiggy diff --git a/extra/cache-extra/composer.json b/extra/cache-extra/composer.json index b34e3142e26..04177a419de 100644 --- a/extra/cache-extra/composer.json +++ b/extra/cache-extra/composer.json @@ -17,10 +17,10 @@ "require": { "php": ">=7.2.5", "symfony/cache": "^5.0|^6.0|^7.0", - "twig/twig": "^2.4|^3.0" + "twig/twig": "^3.0" }, "require-dev": { - "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0|^7.0" + "symfony/phpunit-bridge": "^6.4|^7.0" }, "autoload": { "psr-4" : { "Twig\\Extra\\Cache\\" : "" }, diff --git a/extra/cssinliner-extra/composer.json b/extra/cssinliner-extra/composer.json index a1a6af7a97b..32be7b44de4 100644 --- a/extra/cssinliner-extra/composer.json +++ b/extra/cssinliner-extra/composer.json @@ -15,12 +15,12 @@ } ], "require": { - "php": ">=7.1.3", + "php": ">=7.2.5", "tijsverkoyen/css-to-inline-styles": "^2.0", - "twig/twig": "^2.7|^3.0" + "twig/twig": "^3.0" }, "require-dev": { - "symfony/phpunit-bridge": "^5.4|^6.3|^7.0" + "symfony/phpunit-bridge": "^6.4|^7.0" }, "autoload": { "psr-4" : { "Twig\\Extra\\CssInliner\\" : "" }, diff --git a/extra/html-extra/composer.json b/extra/html-extra/composer.json index 98670abfacc..e67e53e5a82 100644 --- a/extra/html-extra/composer.json +++ b/extra/html-extra/composer.json @@ -15,12 +15,12 @@ } ], "require": { - "php": ">=7.1.3", + "php": ">=7.2.5", "symfony/mime": "^5.4|^6.0|^7.0", - "twig/twig": "^2.7|^3.0" + "twig/twig": "^3.0" }, "require-dev": { - "symfony/phpunit-bridge": "^5.4|^6.3|^7.0" + "symfony/phpunit-bridge": "^6.4|^7.0" }, "autoload": { "psr-4" : { "Twig\\Extra\\Html\\" : "" }, diff --git a/extra/inky-extra/composer.json b/extra/inky-extra/composer.json index c4e42ea7fcd..2cb5a5ad308 100644 --- a/extra/inky-extra/composer.json +++ b/extra/inky-extra/composer.json @@ -15,12 +15,12 @@ } ], "require": { - "php": ">=7.1.3", + "php": ">=7.2.5", "lorenzo/pinky": "^1.0.5", - "twig/twig": "^2.7|^3.0" + "twig/twig": "^3.0" }, "require-dev": { - "symfony/phpunit-bridge": "^5.4|^6.3|^7.0" + "symfony/phpunit-bridge": "^6.4|^7.0" }, "autoload": { "psr-4" : { "Twig\\Extra\\Inky\\" : "" }, diff --git a/extra/intl-extra/IntlExtension.php b/extra/intl-extra/IntlExtension.php index 955d6ec9233..142d25e10c5 100644 --- a/extra/intl-extra/IntlExtension.php +++ b/extra/intl-extra/IntlExtension.php @@ -67,7 +67,6 @@ private static function availableDateFormats(): array 'int32' => \NumberFormatter::TYPE_INT32, 'int64' => \NumberFormatter::TYPE_INT64, 'double' => \NumberFormatter::TYPE_DOUBLE, - 'currency' => \NumberFormatter::TYPE_CURRENCY, ]; private const NUMBER_STYLES = [ 'decimal' => \NumberFormatter::DECIMAL, @@ -370,7 +369,14 @@ public function formatNumberStyle(string $style, $number, array $attrs = [], str public function formatDateTime(Environment $env, $date, ?string $dateFormat = 'medium', ?string $timeFormat = 'medium', string $pattern = '', $timezone = null, string $calendar = 'gregorian', string $locale = null): string { $date = twig_date_converter($env, $date, $timezone); - $formatter = $this->createDateFormatter($locale, $dateFormat, $timeFormat, $pattern, $date->getTimezone(), $calendar); + + $formatterTimezone = $timezone; + if (null === $formatterTimezone) { + $formatterTimezone = $date->getTimezone(); + } elseif (\is_string($formatterTimezone)) { + $formatterTimezone = new \DateTimeZone($timezone); + } + $formatter = $this->createDateFormatter($locale, $dateFormat, $timeFormat, $pattern, $formatterTimezone, $calendar); if (false === $ret = $formatter->format($date)) { throw new RuntimeError('Unable to format the given date.'); @@ -397,7 +403,7 @@ public function formatTime(Environment $env, $date, ?string $timeFormat = 'mediu return $this->formatDateTime($env, $date, 'none', $timeFormat, $pattern, $timezone, $calendar, $locale); } - private function createDateFormatter(?string $locale, ?string $dateFormat, ?string $timeFormat, string $pattern, \DateTimeZone $timezone, string $calendar): \IntlDateFormatter + private function createDateFormatter(?string $locale, ?string $dateFormat, ?string $timeFormat, string $pattern, ?\DateTimeZone $timezone, string $calendar): \IntlDateFormatter { $dateFormats = self::availableDateFormats(); @@ -410,7 +416,10 @@ private function createDateFormatter(?string $locale, ?string $dateFormat, ?stri } if (null === $locale) { - $locale = \Locale::getDefault(); + if ($this->dateFormatterPrototype) { + $locale = $this->dateFormatterPrototype->getLocale(); + } + $locale = $locale ?: \Locale::getDefault(); } $calendar = 'gregorian' === $calendar ? \IntlDateFormatter::GREGORIAN : \IntlDateFormatter::TRADITIONAL; @@ -421,12 +430,14 @@ private function createDateFormatter(?string $locale, ?string $dateFormat, ?stri if ($this->dateFormatterPrototype) { $dateFormatValue = $dateFormatValue ?: $this->dateFormatterPrototype->getDateType(); $timeFormatValue = $timeFormatValue ?: $this->dateFormatterPrototype->getTimeType(); - $timezone = $timezone ?: $this->dateFormatterPrototype->getTimeType(); + $timezone = $timezone ?: $this->dateFormatterPrototype->getTimeZone()->toDateTimeZone(); $calendar = $calendar ?: $this->dateFormatterPrototype->getCalendar(); $pattern = $pattern ?: $this->dateFormatterPrototype->getPattern(); } - $hash = $locale.'|'.$dateFormatValue.'|'.$timeFormatValue.'|'.$timezone->getName().'|'.$calendar.'|'.$pattern; + $timezoneName = $timezone ? $timezone->getName() : '(none)'; + + $hash = $locale.'|'.$dateFormatValue.'|'.$timeFormatValue.'|'.$timezoneName.'|'.$calendar.'|'.$pattern; if (!isset($this->dateFormatters[$hash])) { $this->dateFormatters[$hash] = new \IntlDateFormatter($locale, $dateFormatValue, $timeFormatValue, $timezone, $calendar, $pattern); diff --git a/extra/intl-extra/Tests/IntlExtensionTest.php b/extra/intl-extra/Tests/IntlExtensionTest.php index f2637620693..6afa7036b75 100644 --- a/extra/intl-extra/Tests/IntlExtensionTest.php +++ b/extra/intl-extra/Tests/IntlExtensionTest.php @@ -12,17 +12,71 @@ namespace Twig\Extra\Intl\Tests; use PHPUnit\Framework\TestCase; +use Twig\Environment; +use Twig\Extension\CoreExtension; use Twig\Extra\Intl\IntlExtension; +use Twig\Loader\ArrayLoader; class IntlExtensionTest extends TestCase { + public function testFormatterWithoutProto() + { + $ext = new IntlExtension(); + $env = new Environment(new ArrayLoader()); + + $this->assertSame('12.346', $ext->formatNumber('12.3456')); + $this->assertSame( + 'Feb 20, 2020, 1:37:00 PM', + $ext->formatDateTime($env, new \DateTime('2020-02-20T13:37:00+00:00')) + ); + } + + public function testFormatterWithoutProtoFallsBackToCoreExtensionTimezone() + { + $ext = new IntlExtension(); + $env = new Environment(new ArrayLoader()); + // EET is always +2 without changes for daylight saving time + // so it has a fixed difference to UTC + $env->getExtension(CoreExtension::class)->setTimezone('EET'); + + $this->assertSame( + 'Feb 20, 2020, 3:37:00 PM', + $ext->formatDateTime($env, new \DateTime('2020-02-20T13:37:00+00:00', new \DateTimeZone('UTC'))) + ); + } + public function testFormatterProto() { - $dateFormatterProto = new \IntlDateFormatter('fr', \IntlDateFormatter::FULL, \IntlDateFormatter::FULL); + $dateFormatterProto = new \IntlDateFormatter('fr', \IntlDateFormatter::FULL, \IntlDateFormatter::FULL, new \DateTimeZone('Europe/Paris')); $numberFormatterProto = new \NumberFormatter('fr', \NumberFormatter::DECIMAL); $numberFormatterProto->setTextAttribute(\NumberFormatter::POSITIVE_PREFIX, '++'); $numberFormatterProto->setAttribute(\NumberFormatter::FRACTION_DIGITS, 1); $ext = new IntlExtension($dateFormatterProto, $numberFormatterProto); + $env = new Environment(new ArrayLoader()); + $this->assertSame('++12,3', $ext->formatNumber('12.3456')); + $this->assertSame( + 'jeudi 20 février 2020 à 14:37:00 heure normale d’Europe centrale', + $ext->formatDateTime($env, new \DateTime('2020-02-20T13:37:00+00:00')) + ); + } + + public function testFormatterOverridenProto() + { + $dateFormatterProto = new \IntlDateFormatter('fr', \IntlDateFormatter::FULL, \IntlDateFormatter::FULL, new \DateTimeZone('Europe/Paris')); + $numberFormatterProto = new \NumberFormatter('fr', \NumberFormatter::DECIMAL); + $numberFormatterProto->setTextAttribute(\NumberFormatter::POSITIVE_PREFIX, '++'); + $numberFormatterProto->setAttribute(\NumberFormatter::FRACTION_DIGITS, 1); + $ext = new IntlExtension($dateFormatterProto, $numberFormatterProto); + $env = new Environment(new ArrayLoader()); + + $this->assertSame( + 'twelve point three', + $ext->formatNumber('12.3456', [], 'spellout', 'default', 'en_US') + ); + $this->assertSame( + '2020-02-20 13:37:00', + $ext->formatDateTime($env, new \DateTime('2020-02-20T13:37:00+00:00'), 'short', 'short', 'yyyy-MM-dd HH:mm:ss', 'UTC', 'gregorian', 'en_US') + ); } } diff --git a/extra/intl-extra/composer.json b/extra/intl-extra/composer.json index e670bea3e3e..208347077a2 100644 --- a/extra/intl-extra/composer.json +++ b/extra/intl-extra/composer.json @@ -15,12 +15,12 @@ } ], "require": { - "php": ">=7.1.3", - "twig/twig": "^2.7|^3.0", + "php": ">=7.2.5", + "twig/twig": "^3.0", "symfony/intl": "^5.4|^6.0|^7.0" }, "require-dev": { - "symfony/phpunit-bridge": "^5.4|^6.3|^7.0" + "symfony/phpunit-bridge": "^6.4|^7.0" }, "autoload": { "psr-4" : { "Twig\\Extra\\Intl\\" : "" }, diff --git a/extra/markdown-extra/MarkdownExtension.php b/extra/markdown-extra/MarkdownExtension.php index 8f249ce6d86..6c4296bbf1b 100644 --- a/extra/markdown-extra/MarkdownExtension.php +++ b/extra/markdown-extra/MarkdownExtension.php @@ -34,7 +34,7 @@ function twig_html_to_markdown(string $body, array $options = []): string throw new \LogicException('You cannot use the "html_to_markdown" filter as league/html-to-markdown is not installed; try running "composer require league/html-to-markdown".'); } - $options = $options + [ + $options += [ 'hard_break' => true, 'strip_tags' => true, 'remove_nodes' => 'head style', diff --git a/extra/markdown-extra/Tests/FunctionalTest.php b/extra/markdown-extra/Tests/FunctionalTest.php index 368a0467b3b..42f4ff65a97 100644 --- a/extra/markdown-extra/Tests/FunctionalTest.php +++ b/extra/markdown-extra/Tests/FunctionalTest.php @@ -29,7 +29,7 @@ class FunctionalTest extends TestCase */ public function testMarkdown(string $template, string $expected): void { - foreach ([LeagueMarkdown::class, ErusevMarkdown::class, /*MichelfMarkdown::class,*/ DefaultMarkdown::class] as $class) { + foreach ([LeagueMarkdown::class, ErusevMarkdown::class, /* MichelfMarkdown::class, */ DefaultMarkdown::class] as $class) { $twig = new Environment(new ArrayLoader([ 'index' => $template, 'html' => <<=7.1.3", - "twig/twig": "^2.7|^3.0" + "php": ">=7.2.5", + "twig/twig": "^3.0" }, "require-dev": { - "symfony/phpunit-bridge": "^5.4|^6.3|^7.0", + "symfony/phpunit-bridge": "^6.4|^7.0", "erusev/parsedown": "^1.7", "league/commonmark": "^1.0|^2.0", "league/html-to-markdown": "^4.8|^5.0", diff --git a/extra/string-extra/composer.json b/extra/string-extra/composer.json index ddd16c66f72..77050a11785 100644 --- a/extra/string-extra/composer.json +++ b/extra/string-extra/composer.json @@ -18,10 +18,10 @@ "php": ">=7.2.5", "symfony/string": "^5.4|^6.0|^7.0", "symfony/translation-contracts": "^1.1|^2|^3", - "twig/twig": "^2.7|^3.0" + "twig/twig": "^3.0" }, "require-dev": { - "symfony/phpunit-bridge": "^5.4|^6.3|^7.0" + "symfony/phpunit-bridge": "^6.4|^7.0" }, "autoload": { "psr-4" : { "Twig\\Extra\\String\\" : "" }, diff --git a/extra/twig-extra-bundle/DependencyInjection/TwigExtraExtension.php b/extra/twig-extra-bundle/DependencyInjection/TwigExtraExtension.php index a1d5decd56f..f8ab840e450 100644 --- a/extra/twig-extra-bundle/DependencyInjection/TwigExtraExtension.php +++ b/extra/twig-extra-bundle/DependencyInjection/TwigExtraExtension.php @@ -38,7 +38,7 @@ public function load(array $configs, ContainerBuilder $container) if ($this->isConfigEnabled($container, $config[$extension])) { $loader->load($extension.'.php'); - if ('markdown' === $extension && \class_exists(CommonMarkConverter::class)) { + if ('markdown' === $extension && class_exists(CommonMarkConverter::class)) { $loader->load('markdown_league.php'); } } diff --git a/extra/twig-extra-bundle/Tests/Fixture/Kernel.php b/extra/twig-extra-bundle/Tests/Fixture/Kernel.php index cbc9c4bd850..3df6357fe1f 100644 --- a/extra/twig-extra-bundle/Tests/Fixture/Kernel.php +++ b/extra/twig-extra-bundle/Tests/Fixture/Kernel.php @@ -4,11 +4,11 @@ use League\CommonMark\Extension\Strikethrough\StrikethroughExtension; use Symfony\Bundle\FrameworkBundle\FrameworkBundle; +use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; use Symfony\Bundle\TwigBundle\TwigBundle; use Symfony\Component\Config\Loader\LoaderInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Kernel as BaseKernel; -use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; use Twig\Extra\TwigExtraBundle\TwigExtraBundle; class Kernel extends BaseKernel diff --git a/extra/twig-extra-bundle/composer.json b/extra/twig-extra-bundle/composer.json index 942f6ae4c52..cbdbd4b8ab0 100644 --- a/extra/twig-extra-bundle/composer.json +++ b/extra/twig-extra-bundle/composer.json @@ -18,11 +18,11 @@ "php": ">=7.2.5", "symfony/framework-bundle": "^5.4|^6.0|^7.0", "symfony/twig-bundle": "^5.4|^6.0|^7.0", - "twig/twig": "^2.7|^3.0" + "twig/twig": "^3.0" }, "require-dev": { "league/commonmark": "^1.0|^2.0", - "symfony/phpunit-bridge": "^5.4|^6.3|^7.0", + "symfony/phpunit-bridge": "^6.4|^7.0", "twig/cache-extra": "^3.0", "twig/cssinliner-extra": "^2.12|^3.0", "twig/html-extra": "^2.12|^3.0", diff --git a/src/Cache/FilesystemCache.php b/src/Cache/FilesystemCache.php index e075563aef6..4024adbd70d 100644 --- a/src/Cache/FilesystemCache.php +++ b/src/Cache/FilesystemCache.php @@ -63,7 +63,7 @@ public function write(string $key, string $content): void 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); diff --git a/src/Environment.php b/src/Environment.php index 1477f687f7d..5329b143ee2 100644 --- a/src/Environment.php +++ b/src/Environment.php @@ -40,11 +40,11 @@ */ class Environment { - public const VERSION = '3.7.2-DEV'; - public const VERSION_ID = 30702; + public const VERSION = '3.8.1-DEV'; + public const VERSION_ID = 30801; public const MAJOR_VERSION = 3; - public const MINOR_VERSION = 7; - public const RELEASE_VERSION = 2; + public const MINOR_VERSION = 8; + public const RELEASE_VERSION = 1; public const EXTRA_VERSION = 'DEV'; private $charset; @@ -345,7 +345,6 @@ public function loadTemplate(string $cls, string $name, int $index = null): Temp $this->cache->load($key); } - $source = null; if (!class_exists($cls, false)) { $source = $this->getLoader()->getSourceContext($name); $content = $this->compileSource($source); diff --git a/src/Error/Error.php b/src/Error/Error.php index a68be65f203..bca1fa64c5b 100644 --- a/src/Error/Error.php +++ b/src/Error/Error.php @@ -53,7 +53,7 @@ class Error extends \Exception * @param int $lineno The template line where the error occurred * @param Source|null $source The source context where the error occurred */ - public function __construct(string $message, int $lineno = -1, Source $source = null, \Exception $previous = null) + public function __construct(string $message, int $lineno = -1, Source $source = null, \Throwable $previous = null) { parent::__construct('', 0, $previous); @@ -130,13 +130,13 @@ private function updateRepr(): void } $dot = false; - if ('.' === substr($this->message, -1)) { + if (str_ends_with($this->message, '.')) { $this->message = substr($this->message, 0, -1); $dot = true; } $questionMark = false; - if ('?' === substr($this->message, -1)) { + if (str_ends_with($this->message, '?')) { $this->message = substr($this->message, 0, -1); $questionMark = true; } @@ -172,7 +172,7 @@ private function guessTemplateInfo(): void foreach ($backtrace as $trace) { if (isset($trace['object']) && $trace['object'] instanceof Template) { $currentClass = \get_class($trace['object']); - $isEmbedContainer = null === $templateClass ? false : 0 === strpos($templateClass, $currentClass); + $isEmbedContainer = null === $templateClass ? false : str_starts_with($templateClass, $currentClass); if (null === $this->name || ($this->name == $trace['object']->getTemplateName() && !$isEmbedContainer)) { $template = $trace['object']; $templateClass = \get_class($trace['object']); diff --git a/src/Error/SyntaxError.php b/src/Error/SyntaxError.php index 726b3309e5b..77c437c6882 100644 --- a/src/Error/SyntaxError.php +++ b/src/Error/SyntaxError.php @@ -30,7 +30,7 @@ public function addSuggestions(string $name, array $items): void $alternatives = []; foreach ($items as $item) { $lev = levenshtein($name, $item); - if ($lev <= \strlen($name) / 3 || false !== strpos($item, $name)) { + if ($lev <= \strlen($name) / 3 || str_contains($item, $name)) { $alternatives[$item] = $lev; } } diff --git a/src/ExpressionParser.php b/src/ExpressionParser.php index 38347cb391d..13e0f0876ed 100644 --- a/src/ExpressionParser.php +++ b/src/ExpressionParser.php @@ -183,11 +183,14 @@ private function parseConditionalExpression($expr): AbstractExpression if (!$this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ':')) { $expr2 = $this->parseExpression(); if ($this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ':')) { + // Ternary operator (expr ? expr2 : expr3) $expr3 = $this->parseExpression(); } else { + // Ternary without else (expr ? expr2) $expr3 = new ConstantExpression('', $this->parser->getCurrentToken()->getLine()); } } else { + // Ternary without then (expr ?: expr3) $expr2 = $expr; $expr3 = $this->parseExpression(); } @@ -506,10 +509,6 @@ public function parseSubscriptExpression($node) } if ($node instanceof NameExpression && null !== $this->parser->getImportedSymbol('template', $node->getAttribute('name'))) { - if (!$arg instanceof ConstantExpression) { - throw new SyntaxError(sprintf('Dynamic macro names are not supported (called on "%s").', $node->getAttribute('name')), $token->getLine(), $stream->getSourceContext()); - } - $name = $arg->getAttribute('value'); $node = new MethodCallExpression($node, 'macro_'.$name, $arguments, $lineno); diff --git a/src/Extension/CoreExtension.php b/src/Extension/CoreExtension.php index 7b5bf8749c9..90567257ac9 100644 --- a/src/Extension/CoreExtension.php +++ b/src/Extension/CoreExtension.php @@ -251,7 +251,7 @@ public function getTests(): array new TwigTest('divisible by', null, ['node_class' => DivisiblebyTest::class, 'one_mandatory_argument' => true]), new TwigTest('constant', null, ['node_class' => ConstantTest::class]), new TwigTest('empty', 'twig_test_empty'), - new TwigTest('iterable', 'twig_test_iterable'), + new TwigTest('iterable', 'is_iterable'), ]; } @@ -343,9 +343,9 @@ function twig_cycle($values, $position) * @param \Traversable|array|int|float|string $values The values to pick a random item from * @param int|null $max Maximum value used when $values is an int * - * @throws RuntimeError when $values is an empty array (does not apply to an empty string which is returned as is) - * * @return mixed A random value from the given sequence + * + * @throws RuntimeError when $values is an empty array (does not apply to an empty string which is returned as is) */ function twig_random(Environment $env, $values = null, $max = null) { @@ -364,7 +364,6 @@ function twig_random(Environment $env, $values = null, $max = null) } } else { $min = $values; - $max = $max; } return mt_rand((int) $min, (int) $max); @@ -392,7 +391,7 @@ function twig_random(Environment $env, $values = null, $max = null) } } - if (!twig_test_iterable($values)) { + if (!is_iterable($values)) { return $values; } @@ -529,7 +528,7 @@ function twig_date_converter(Environment $env, $date = null, $timezone = null) */ function twig_replace_filter($str, $from) { - if (!twig_test_iterable($from)) { + if (!is_iterable($from)) { throw new RuntimeError(sprintf('The "replace" filter expects an array or "Traversable" as replace values, got "%s".', \is_object($from) ? \get_class($from) : \gettype($from))); } @@ -626,7 +625,7 @@ function twig_array_merge(...$arrays) $result = []; foreach ($arrays as $argNumber => $array) { - if (!twig_test_iterable($array)) { + if (!is_iterable($array)) { throw new RuntimeError(sprintf('The merge filter only works with arrays or "Traversable", got "%s" for argument %d.', \gettype($array), $argNumber + 1)); } @@ -656,7 +655,7 @@ function twig_slice(Environment $env, $item, $start, $length = null, $preserveKe if ($start >= 0 && $length >= 0 && $item instanceof \Iterator) { try { - return iterator_to_array(new \LimitIterator($item, $start, null === $length ? -1 : $length), $preserveKeys); + return iterator_to_array(new \LimitIterator($item, $start, $length ?? -1), $preserveKeys); } catch (\OutOfBoundsException $e) { return []; } @@ -669,7 +668,7 @@ function twig_slice(Environment $env, $item, $start, $length = null, $preserveKe return \array_slice($item, $start, $length, $preserveKeys); } - return (string) mb_substr((string) $item, $start, $length, $env->getCharset()); + return mb_substr((string) $item, $start, $length, $env->getCharset()); } /** @@ -722,7 +721,7 @@ function twig_last(Environment $env, $item) */ function twig_join_filter($value, $glue = '', $and = null) { - if (!twig_test_iterable($value)) { + if (!is_iterable($value)) { $value = (array) $value; } @@ -768,7 +767,7 @@ function twig_split_filter(Environment $env, $value, $delimiter, $limit = null) { $value = $value ?? ''; - if (\strlen($delimiter) > 0) { + if ('' !== $delimiter) { return null === $limit ? explode($delimiter, $value) : explode($delimiter, $value, $limit); } @@ -926,7 +925,7 @@ function twig_in_filter($value, $compare) if (\is_string($compare)) { if (\is_string($value) || \is_int($value) || \is_float($value)) { - return '' === $value || false !== strpos($compare, (string) $value); + return '' === $value || str_contains($compare, (string) $value); } return false; @@ -1022,9 +1021,6 @@ function twig_compare($a, $b) } /** - * @param string $pattern - * @param string|null $subject - * * @return int * * @throws RuntimeError When an invalid pattern is used @@ -1123,7 +1119,7 @@ function twig_length_filter(Environment $env, $thing) return 0; } - if (is_scalar($thing)) { + if (\is_scalar($thing)) { return mb_strlen($thing, $env->getCharset()); } @@ -1233,7 +1229,7 @@ function twig_call_macro(Template $template, string $method, array $args, int $l */ function twig_ensure_traversable($seq) { - if ($seq instanceof \Traversable || \is_array($seq)) { + if (is_iterable($seq)) { return $seq; } @@ -1296,21 +1292,23 @@ function twig_test_empty($value) * @param mixed $value A variable * * @return bool true if the value is traversable + * + * @deprecated since Twig 3.8, to be removed in 4.0 (use the native "is_iterable" function instead) */ function twig_test_iterable($value) { - return $value instanceof \Traversable || \is_array($value); + return is_iterable($value); } /** * Renders a template. * - * @param array $context - * @param string|array $template The template to render or an array of templates to try consecutively - * @param array $variables The variables to pass to the template - * @param bool $withContext - * @param bool $ignoreMissing Whether to ignore missing templates or not - * @param bool $sandboxed Whether to sandbox the template or not + * @param array $context + * @param string|array|TemplateWrapper $template The template to render or an array of templates to try consecutively + * @param array $variables The variables to pass to the template + * @param bool $withContext + * @param bool $ignoreMissing Whether to ignore missing templates or not + * @param bool $sandboxed Whether to sandbox the template or not * * @return string The rendered template */ @@ -1431,7 +1429,7 @@ function twig_constant_is_defined($constant, $object = null) */ function twig_array_batch($items, $size, $fill = null, $preserveKeys = true) { - if (!twig_test_iterable($items)) { + if (!is_iterable($items)) { throw new RuntimeError(sprintf('The "batch" filter expects an array or "Traversable", got "%s".', \is_object($items) ? \get_class($items) : \gettype($items))); } @@ -1587,13 +1585,13 @@ function twig_get_attribute(Environment $env, Source $source, $object, $item, ar $classCache[$method] = $method; $classCache[$lcName = $lcMethods[$i]] = $method; - if ('g' === $lcName[0] && 0 === strpos($lcName, 'get')) { + if ('g' === $lcName[0] && str_starts_with($lcName, 'get')) { $name = substr($method, 3); $lcName = substr($lcName, 3); - } elseif ('i' === $lcName[0] && 0 === strpos($lcName, 'is')) { + } elseif ('i' === $lcName[0] && str_starts_with($lcName, 'is')) { $name = substr($method, 2); $lcName = substr($lcName, 2); - } elseif ('h' === $lcName[0] && 0 === strpos($lcName, 'has')) { + } elseif ('h' === $lcName[0] && str_starts_with($lcName, 'has')) { $name = substr($method, 3); $lcName = substr($lcName, 3); if (\in_array('is'.$lcName, $lcMethods)) { @@ -1689,7 +1687,7 @@ function twig_array_column($array, $name, $index = null): array function twig_array_filter(Environment $env, $array, $arrow) { - if (!twig_test_iterable($array)) { + if (!is_iterable($array)) { throw new RuntimeError(sprintf('The "filter" filter expects an array or "Traversable", got "%s".', \is_object($array) ? \get_class($array) : \gettype($array))); } diff --git a/src/Extension/DebugExtension.php b/src/Extension/DebugExtension.php index bfb23d7bd4f..c0f10d5a303 100644 --- a/src/Extension/DebugExtension.php +++ b/src/Extension/DebugExtension.php @@ -19,10 +19,10 @@ public function getFunctions(): array // dump is safe if var_dump is overridden by xdebug $isDumpOutputHtmlSafe = \extension_loaded('xdebug') // false means that it was not set (and the default is on) or it explicitly enabled - && (false === ini_get('xdebug.overload_var_dump') || ini_get('xdebug.overload_var_dump')) + && (false === \ini_get('xdebug.overload_var_dump') || \ini_get('xdebug.overload_var_dump')) // false means that it was not set (and the default is on) or it explicitly enabled // xdebug.overload_var_dump produces HTML only when html_errors is also enabled - && (false === ini_get('html_errors') || ini_get('html_errors')) + && (false === \ini_get('html_errors') || \ini_get('html_errors')) || 'cli' === \PHP_SAPI ; diff --git a/src/Extension/EscaperExtension.php b/src/Extension/EscaperExtension.php index 9d2251dc6e1..ef8879dbdc6 100644 --- a/src/Extension/EscaperExtension.php +++ b/src/Extension/EscaperExtension.php @@ -341,7 +341,7 @@ function twig_escape_filter(Environment $env, $string, $strategy = 'html', $char * 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)) { + if (($ord <= 0x1F && "\t" != $chr && "\n" != $chr && "\r" != $chr) || ($ord >= 0x7F && $ord <= 0x9F)) { return '�'; } @@ -388,7 +388,7 @@ function twig_escape_filter(Environment $env, $string, $strategy = 'html', $char default: $escapers = $env->getExtension(EscaperExtension::class)->getEscapers(); - if (array_key_exists($strategy, $escapers)) { + if (\array_key_exists($strategy, $escapers)) { return $escapers[$strategy]($env, $string, $charset); } diff --git a/src/FileExtensionEscapingStrategy.php b/src/FileExtensionEscapingStrategy.php index 65198bbb649..812071bf971 100644 --- a/src/FileExtensionEscapingStrategy.php +++ b/src/FileExtensionEscapingStrategy.php @@ -37,7 +37,7 @@ public static function guess(string $name) return 'html'; // return html for directories } - if ('.twig' === substr($name, -5)) { + if (str_ends_with($name, '.twig')) { $name = substr($name, 0, -5); } diff --git a/src/Lexer.php b/src/Lexer.php index 6c45efbc9f6..b23080f58e0 100644 --- a/src/Lexer.php +++ b/src/Lexer.php @@ -345,13 +345,13 @@ private function lexExpression(): void $this->moveCursor($match[0]); } // punctuation - elseif (false !== strpos(self::PUNCTUATION, $this->code[$this->cursor])) { + elseif (str_contains(self::PUNCTUATION, $this->code[$this->cursor])) { // opening bracket - if (false !== strpos('([{', $this->code[$this->cursor])) { + if (str_contains('([{', $this->code[$this->cursor])) { $this->brackets[] = [$this->code[$this->cursor], $this->lineno]; } // closing bracket - elseif (false !== strpos(')]}', $this->code[$this->cursor])) { + elseif (str_contains(')]}', $this->code[$this->cursor])) { if (empty($this->brackets)) { throw new SyntaxError(sprintf('Unexpected "%s".', $this->code[$this->cursor]), $this->lineno, $this->source); } @@ -422,7 +422,7 @@ private function lexString(): void $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) { + } elseif (preg_match(self::REGEX_DQ_STRING_PART, $this->code, $match, 0, $this->cursor) && '' !== $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)) { diff --git a/src/Loader/FilesystemLoader.php b/src/Loader/FilesystemLoader.php index 62267a11c89..1073a406a06 100644 --- a/src/Loader/FilesystemLoader.php +++ b/src/Loader/FilesystemLoader.php @@ -36,7 +36,7 @@ class FilesystemLoader implements LoaderInterface */ public function __construct($paths = [], string $rootPath = null) { - $this->rootPath = (null === $rootPath ? getcwd() : $rootPath).\DIRECTORY_SEPARATOR; + $this->rootPath = ($rootPath ?? getcwd()).\DIRECTORY_SEPARATOR; if (null !== $rootPath && false !== ($realPath = realpath($rootPath))) { $this->rootPath = $realPath.\DIRECTORY_SEPARATOR; } @@ -250,7 +250,7 @@ private function parseName(string $name, string $default = self::MAIN_NAMESPACE) private function validateName(string $name): void { - if (false !== strpos($name, "\0")) { + if (str_contains($name, "\0")) { throw new LoaderError('A template name cannot contain NUL bytes.'); } diff --git a/src/Node/Expression/Binary/EndsWithBinary.php b/src/Node/Expression/Binary/EndsWithBinary.php index c3516b853fc..73fa20b1f66 100644 --- a/src/Node/Expression/Binary/EndsWithBinary.php +++ b/src/Node/Expression/Binary/EndsWithBinary.php @@ -24,7 +24,7 @@ public function compile(Compiler $compiler): void ->subcompile($this->getNode('left')) ->raw(sprintf(') && is_string($%s = ', $right)) ->subcompile($this->getNode('right')) - ->raw(sprintf(') && (\'\' === $%2$s || $%2$s === substr($%1$s, -strlen($%2$s))))', $left, $right)) + ->raw(sprintf(') && str_ends_with($%1$s, $%2$s))', $left, $right)) ; } diff --git a/src/Node/Expression/Binary/StartsWithBinary.php b/src/Node/Expression/Binary/StartsWithBinary.php index d0df1c4b639..22eff92a794 100644 --- a/src/Node/Expression/Binary/StartsWithBinary.php +++ b/src/Node/Expression/Binary/StartsWithBinary.php @@ -24,7 +24,7 @@ public function compile(Compiler $compiler): void ->subcompile($this->getNode('left')) ->raw(sprintf(') && is_string($%s = ', $right)) ->subcompile($this->getNode('right')) - ->raw(sprintf(') && (\'\' === $%2$s || 0 === strpos($%1$s, $%2$s)))', $left, $right)) + ->raw(sprintf(') && str_starts_with($%1$s, $%2$s))', $left, $right)) ; } diff --git a/src/Node/Expression/CallExpression.php b/src/Node/Expression/CallExpression.php index 11a6b1abcae..3a2d7a4fca4 100644 --- a/src/Node/Expression/CallExpression.php +++ b/src/Node/Expression/CallExpression.php @@ -24,7 +24,7 @@ protected function compileCallable(Compiler $compiler) { $callable = $this->getAttribute('callable'); - if (\is_string($callable) && false === strpos($callable, '::')) { + if (\is_string($callable) && !str_contains($callable, '::')) { $compiler->raw($callable); } else { [$r, $callable] = $this->reflectCallable($callable); @@ -297,13 +297,13 @@ private function reflectCallable($callable) } $r = new \ReflectionFunction($closure); - if (false !== strpos($r->name, '{closure}')) { + if (str_contains($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; + $callableName = get_debug_type($object).'::'.$r->name; } elseif (\PHP_VERSION_ID >= 80111 && $class = $r->getClosureCalledClass()) { $callableName = $class->name.'::'.$r->name; } elseif (\PHP_VERSION_ID < 80111 && $class = $r->getClosureScopeClass()) { diff --git a/src/Node/Expression/ConditionalExpression.php b/src/Node/Expression/ConditionalExpression.php index 2c7bd0a276c..d7db993579c 100644 --- a/src/Node/Expression/ConditionalExpression.php +++ b/src/Node/Expression/ConditionalExpression.php @@ -23,14 +23,23 @@ public function __construct(AbstractExpression $expr1, AbstractExpression $expr2 public function compile(Compiler $compiler): void { - $compiler - ->raw('((') - ->subcompile($this->getNode('expr1')) - ->raw(') ? (') - ->subcompile($this->getNode('expr2')) - ->raw(') : (') - ->subcompile($this->getNode('expr3')) - ->raw('))') - ; + // Ternary with no then uses Elvis operator + if ($this->getNode('expr1') === $this->getNode('expr2')) { + $compiler + ->raw('((') + ->subcompile($this->getNode('expr1')) + ->raw(') ?: (') + ->subcompile($this->getNode('expr3')) + ->raw('))'); + } else { + $compiler + ->raw('((') + ->subcompile($this->getNode('expr1')) + ->raw(') ? (') + ->subcompile($this->getNode('expr2')) + ->raw(') : (') + ->subcompile($this->getNode('expr3')) + ->raw('))'); + } } } diff --git a/src/Node/ModuleNode.php b/src/Node/ModuleNode.php index e972b6ba582..9b485eeaf03 100644 --- a/src/Node/ModuleNode.php +++ b/src/Node/ModuleNode.php @@ -355,6 +355,9 @@ protected function compileMacros(Compiler $compiler) protected function compileGetTemplateName(Compiler $compiler) { $compiler + ->write("/**\n") + ->write(" * @codeCoverageIgnore\n") + ->write(" */\n") ->write("public function getTemplateName()\n", "{\n") ->indent() ->write('return ') @@ -409,6 +412,9 @@ protected function compileIsTraitable(Compiler $compiler) } $compiler + ->write("/**\n") + ->write(" * @codeCoverageIgnore\n") + ->write(" */\n") ->write("public function isTraitable()\n", "{\n") ->indent() ->write(sprintf("return %s;\n", $traitable ? 'true' : 'false')) @@ -420,6 +426,9 @@ protected function compileIsTraitable(Compiler $compiler) protected function compileDebugInfo(Compiler $compiler) { $compiler + ->write("/**\n") + ->write(" * @codeCoverageIgnore\n") + ->write(" */\n") ->write("public function getDebugInfo()\n", "{\n") ->indent() ->write(sprintf("return %s;\n", str_replace("\n", '', var_export(array_reverse($compiler->getDebugInfo(), true), true)))) diff --git a/src/Node/Node.php b/src/Node/Node.php index c0558b9afdc..30659ae0fd1 100644 --- a/src/Node/Node.php +++ b/src/Node/Node.php @@ -27,7 +27,6 @@ class Node implements \Countable, \IteratorAggregate protected $lineno; protected $tag; - private $name; private $sourceContext; /** diff --git a/src/Node/WithNode.php b/src/Node/WithNode.php index 56a334496e9..2ac9123d0d1 100644 --- a/src/Node/WithNode.php +++ b/src/Node/WithNode.php @@ -45,7 +45,7 @@ public function compile(Compiler $compiler): void ->write(sprintf('$%s = ', $varsName)) ->subcompile($node) ->raw(";\n") - ->write(sprintf("if (!twig_test_iterable(\$%s)) {\n", $varsName)) + ->write(sprintf("if (!is_iterable(\$%s)) {\n", $varsName)) ->indent() ->write("throw new RuntimeError('Variables passed to the \"with\" tag must be a hash.', ") ->repr($node->getTemplateLine()) diff --git a/src/NodeVisitor/EscaperNodeVisitor.php b/src/NodeVisitor/EscaperNodeVisitor.php index fe56ea30741..c390d7cc71b 100644 --- a/src/NodeVisitor/EscaperNodeVisitor.php +++ b/src/NodeVisitor/EscaperNodeVisitor.php @@ -57,7 +57,7 @@ public function enterNode(Node $node, Environment $env): Node } elseif ($node instanceof AutoEscapeNode) { $this->statusStack[] = $node->getAttribute('value'); } elseif ($node instanceof BlockNode) { - $this->statusStack[] = isset($this->blocks[$node->getAttribute('name')]) ? $this->blocks[$node->getAttribute('name')] : $this->needEscaping($env); + $this->statusStack[] = $this->blocks[$node->getAttribute('name')] ?? $this->needEscaping(); } elseif ($node instanceof ImportNode) { $this->safeVars[] = $node->getNode('var')->getAttribute('name'); } @@ -73,7 +73,7 @@ public function leaveNode(Node $node, Environment $env): ?Node $this->blocks = []; } elseif ($node instanceof FilterExpression) { return $this->preEscapeFilterNode($node, $env); - } elseif ($node instanceof PrintNode && false !== $type = $this->needEscaping($env)) { + } elseif ($node instanceof PrintNode && false !== $type = $this->needEscaping()) { $expression = $node->getNode('expr'); if ($expression instanceof ConditionalExpression && $this->shouldUnwrapConditional($expression, $env, $type)) { return new DoNode($this->unwrapConditional($expression, $env, $type), $expression->getTemplateLine()); @@ -85,7 +85,7 @@ public function leaveNode(Node $node, Environment $env): ?Node if ($node instanceof AutoEscapeNode || $node instanceof BlockNode) { array_pop($this->statusStack); } elseif ($node instanceof BlockReferenceNode) { - $this->blocks[$node->getAttribute('name')] = $this->needEscaping($env); + $this->blocks[$node->getAttribute('name')] = $this->needEscaping(); } return $node; @@ -183,13 +183,13 @@ private function isSafeFor(string $type, Node $expression, Environment $env): bo return \in_array($type, $safe) || \in_array('all', $safe); } - private function needEscaping(Environment $env) + private function needEscaping() { if (\count($this->statusStack)) { return $this->statusStack[\count($this->statusStack) - 1]; } - return $this->defaultStrategy ? $this->defaultStrategy : false; + return $this->defaultStrategy ?: false; } private function getEscaperFilter(string $type, Node $node): FilterExpression diff --git a/src/NodeVisitor/MacroAutoImportNodeVisitor.php b/src/NodeVisitor/MacroAutoImportNodeVisitor.php index af477e65356..d6a7781ba27 100644 --- a/src/NodeVisitor/MacroAutoImportNodeVisitor.php +++ b/src/NodeVisitor/MacroAutoImportNodeVisitor.php @@ -50,10 +50,10 @@ public function leaveNode(Node $node, Environment $env): Node } } elseif ($this->inAModule) { if ( - $node instanceof GetAttrExpression && - $node->getNode('node') instanceof NameExpression && - '_self' === $node->getNode('node')->getAttribute('name') && - $node->getNode('attribute') instanceof ConstantExpression + $node instanceof GetAttrExpression + && $node->getNode('node') instanceof NameExpression + && '_self' === $node->getNode('node')->getAttribute('name') + && $node->getNode('attribute') instanceof ConstantExpression ) { $this->hasMacroCalls = true; diff --git a/src/NodeVisitor/OptimizerNodeVisitor.php b/src/NodeVisitor/OptimizerNodeVisitor.php index 7ac75e41ad3..6b39f00947c 100644 --- a/src/NodeVisitor/OptimizerNodeVisitor.php +++ b/src/NodeVisitor/OptimizerNodeVisitor.php @@ -63,7 +63,7 @@ public function __construct(int $optimizers = -1) public function enterNode(Node $node, Environment $env): Node { if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) { - $this->enterOptimizeFor($node, $env); + $this->enterOptimizeFor($node); } return $node; @@ -72,14 +72,14 @@ public function enterNode(Node $node, Environment $env): Node public function leaveNode(Node $node, Environment $env): ?Node { if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) { - $this->leaveOptimizeFor($node, $env); + $this->leaveOptimizeFor($node); } if (self::OPTIMIZE_RAW_FILTER === (self::OPTIMIZE_RAW_FILTER & $this->optimizers)) { - $node = $this->optimizeRawFilter($node, $env); + $node = $this->optimizeRawFilter($node); } - $node = $this->optimizePrintNode($node, $env); + $node = $this->optimizePrintNode($node); return $node; } @@ -91,7 +91,7 @@ public function leaveNode(Node $node, Environment $env): ?Node * * * "echo $this->render(Parent)Block()" with "$this->display(Parent)Block()" */ - private function optimizePrintNode(Node $node, Environment $env): Node + private function optimizePrintNode(Node $node): Node { if (!$node instanceof PrintNode) { return $node; @@ -99,8 +99,8 @@ private function optimizePrintNode(Node $node, Environment $env): Node $exprNode = $node->getNode('expr'); if ( - $exprNode instanceof BlockReferenceExpression || - $exprNode instanceof ParentExpression + $exprNode instanceof BlockReferenceExpression + || $exprNode instanceof ParentExpression ) { $exprNode->setAttribute('output', true); @@ -113,7 +113,7 @@ private function optimizePrintNode(Node $node, Environment $env): Node /** * Removes "raw" filters. */ - private function optimizeRawFilter(Node $node, Environment $env): Node + private function optimizeRawFilter(Node $node): Node { if ($node instanceof FilterExpression && 'raw' == $node->getNode('filter')->getAttribute('value')) { return $node->getNode('node'); @@ -125,7 +125,7 @@ private function optimizeRawFilter(Node $node, Environment $env): Node /** * Optimizes "for" tag by removing the "loop" variable creation whenever possible. */ - private function enterOptimizeFor(Node $node, Environment $env): void + private function enterOptimizeFor(Node $node): void { if ($node instanceof ForNode) { // disable the loop variable by default @@ -166,7 +166,7 @@ private function enterOptimizeFor(Node $node, Environment $env): void && 'include' === $node->getAttribute('name') && (!$node->getNode('arguments')->hasNode('with_context') || false !== $node->getNode('arguments')->getNode('with_context')->getAttribute('value') - ) + ) ) { $this->addLoopToAll(); } @@ -175,12 +175,12 @@ private function enterOptimizeFor(Node $node, Environment $env): void elseif ($node instanceof GetAttrExpression && (!$node->getNode('attribute') instanceof ConstantExpression || 'parent' === $node->getNode('attribute')->getAttribute('value') - ) + ) && (true === $this->loops[0]->getAttribute('with_loop') - || ($node->getNode('node') instanceof NameExpression - && 'loop' === $node->getNode('node')->getAttribute('name') - ) - ) + || ($node->getNode('node') instanceof NameExpression + && 'loop' === $node->getNode('node')->getAttribute('name') + ) + ) ) { $this->addLoopToAll(); } @@ -189,7 +189,7 @@ private function enterOptimizeFor(Node $node, Environment $env): void /** * Optimizes "for" tag by removing the "loop" variable creation whenever possible. */ - private function leaveOptimizeFor(Node $node, Environment $env): void + private function leaveOptimizeFor(Node $node): void { if ($node instanceof ForNode) { array_shift($this->loops); diff --git a/src/Parser.php b/src/Parser.php index 4428208fed3..4016a5f39ab 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -303,10 +303,9 @@ private function filterBodyNodes(Node $node, bool $nested = false): ?Node // 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) + || (!$node instanceof TextNode && !$node instanceof BlockReferenceNode && $node instanceof NodeOutputInterface) ) { - if (false !== strpos((string) $node, \chr(0xEF).\chr(0xBB).\chr(0xBF))) { + if (str_contains((string) $node, \chr(0xEF).\chr(0xBB).\chr(0xBF))) { $t = substr($node->getAttribute('data'), 3); if ('' === $t || ctype_space($t)) { // bypass empty nodes starting with a BOM diff --git a/src/Profiler/Dumper/HtmlDumper.php b/src/Profiler/Dumper/HtmlDumper.php index 1f2433b4d36..3c0daf1c8d3 100644 --- a/src/Profiler/Dumper/HtmlDumper.php +++ b/src/Profiler/Dumper/HtmlDumper.php @@ -37,7 +37,7 @@ protected function formatTemplate(Profile $profile, $prefix): string protected function formatNonTemplate(Profile $profile, $prefix): string { - return sprintf('%s└ %s::%s(%s)', $prefix, $profile->getTemplate(), $profile->getType(), isset(self::$colors[$profile->getType()]) ? self::$colors[$profile->getType()] : 'auto', $profile->getName()); + return sprintf('%s└ %s::%s(%s)', $prefix, $profile->getTemplate(), $profile->getType(), self::$colors[$profile->getType()] ?? 'auto', $profile->getName()); } protected function formatTime(Profile $profile, $percent): string diff --git a/src/Profiler/Profile.php b/src/Profiler/Profile.php index 252ca9b0cf4..7979a23c67a 100644 --- a/src/Profiler/Profile.php +++ b/src/Profiler/Profile.php @@ -32,7 +32,7 @@ public function __construct(string $template = 'main', string $type = self::ROOT { $this->template = $template; $this->type = $type; - $this->name = 0 === strpos($name, '__internal_') ? 'INTERNAL' : $name; + $this->name = str_starts_with($name, '__internal_') ? 'INTERNAL' : $name; $this->enter(); } diff --git a/src/Sandbox/SecurityPolicy.php b/src/Sandbox/SecurityPolicy.php index 2fc0d01318a..a725aa4f104 100644 --- a/src/Sandbox/SecurityPolicy.php +++ b/src/Sandbox/SecurityPolicy.php @@ -94,9 +94,8 @@ public function checkMethodAllowed($obj, $method): void $allowed = false; $method = strtr($method, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); foreach ($this->allowedMethods as $class => $methods) { - if ($obj instanceof $class) { - $allowed = \in_array($method, $methods); - + if ($obj instanceof $class && \in_array($method, $methods)) { + $allowed = true; break; } } @@ -111,9 +110,8 @@ public function checkPropertyAllowed($obj, $property): void { $allowed = false; foreach ($this->allowedProperties as $class => $properties) { - if ($obj instanceof $class) { - $allowed = \in_array($property, \is_array($properties) ? $properties : [$properties]); - + if ($obj instanceof $class && \in_array($property, \is_array($properties) ? $properties : [$properties])) { + $allowed = true; break; } } diff --git a/src/Template.php b/src/Template.php index e04bd04a63e..ffbaae1ea1c 100644 --- a/src/Template.php +++ b/src/Template.php @@ -181,7 +181,7 @@ public function displayBlock($name, array $context, array $blocks = [], $useBloc } throw $e; - } catch (\Exception $e) { + } catch (\Throwable $e) { $e = new RuntimeError(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $template->getSourceContext(), $e); $e->guess(); @@ -404,7 +404,7 @@ protected function displayWithErrorHandling(array $context, array $blocks = []) } throw $e; - } catch (\Exception $e) { + } catch (\Throwable $e) { $e = new RuntimeError(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $this->getSourceContext(), $e); $e->guess(); diff --git a/src/TemplateWrapper.php b/src/TemplateWrapper.php index c9c6b07c669..1ecd82251f3 100644 --- a/src/TemplateWrapper.php +++ b/src/TemplateWrapper.php @@ -35,9 +35,7 @@ public function __construct(Environment $env, Template $template) public function render(array $context = []): string { - // using func_get_args() allows to not expose the blocks argument - // as it should only be used by internal code - return $this->template->render($context, \func_get_args()[1] ?? []); + return $this->template->render($context); } public function display(array $context = []) diff --git a/src/Test/IntegrationTestCase.php b/src/Test/IntegrationTestCase.php index 307302bb624..e97ad417062 100644 --- a/src/Test/IntegrationTestCase.php +++ b/src/Test/IntegrationTestCase.php @@ -84,6 +84,7 @@ public function testIntegration($file, $message, $condition, $templates, $except /** * @dataProvider getLegacyTests + * * @group legacy */ public function testLegacyIntegration($file, $message, $condition, $templates, $exception, $outputs, $deprecation = '') @@ -101,7 +102,7 @@ public function getTests($name, $legacyTests = false) continue; } - if ($legacyTests xor false !== strpos($file->getRealpath(), '.legacy.test')) { + if ($legacyTests xor str_contains($file->getRealpath(), '.legacy.test')) { continue; } @@ -257,7 +258,7 @@ protected static function parseTemplates($test) $templates = []; preg_match_all('/--TEMPLATE(?:\((.*?)\))?--(.*?)(?=\-\-TEMPLATE|$)/s', $test, $matches, \PREG_SET_ORDER); foreach ($matches as $match) { - $templates[($match[1] ?: 'index.twig')] = $match[2]; + $templates[$match[1] ?: 'index.twig'] = $match[2]; } return $templates; diff --git a/src/Test/NodeTestCase.php b/src/Test/NodeTestCase.php index 3b8b2c86c67..8b1bef776d3 100644 --- a/src/Test/NodeTestCase.php +++ b/src/Test/NodeTestCase.php @@ -43,7 +43,7 @@ public function assertNodeCompilation($source, Node $node, Environment $environm protected function getCompiler(Environment $environment = null) { - return new Compiler(null === $environment ? $this->getEnvironment() : $environment); + return new Compiler($environment ?? $this->getEnvironment()); } protected function getEnvironment() diff --git a/src/Token.php b/src/Token.php index fd1a89d2adc..59279b8fe7c 100644 --- a/src/Token.php +++ b/src/Token.php @@ -68,9 +68,9 @@ public function test($type, $values = null): bool } return ($this->type === $type) && ( - null === $values || - (\is_array($values) && \in_array($this->value, $values)) || - $this->value == $values + null === $values + || (\is_array($values) && \in_array($this->value, $values)) + || $this->value == $values ); } diff --git a/src/TokenParser/FromTokenParser.php b/src/TokenParser/FromTokenParser.php index 35098c267b1..31b6cde4148 100644 --- a/src/TokenParser/FromTokenParser.php +++ b/src/TokenParser/FromTokenParser.php @@ -32,7 +32,7 @@ public function parse(Token $token): Node $stream->expect(/* Token::NAME_TYPE */ 5, 'import'); $targets = []; - do { + while (true) { $name = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue(); $alias = $name; @@ -45,7 +45,7 @@ public function parse(Token $token): Node if (!$stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) { break; } - } while (true); + } $stream->expect(/* Token::BLOCK_END_TYPE */ 3); diff --git a/src/TokenParser/UseTokenParser.php b/src/TokenParser/UseTokenParser.php index d0a2de41a2e..3cdbb98ad01 100644 --- a/src/TokenParser/UseTokenParser.php +++ b/src/TokenParser/UseTokenParser.php @@ -43,7 +43,7 @@ public function parse(Token $token): Node $targets = []; if ($stream->nextIf('with')) { - do { + while (true) { $name = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue(); $alias = $name; @@ -56,7 +56,7 @@ public function parse(Token $token): Node if (!$stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) { break; } - } while (true); + } } $stream->expect(/* Token::BLOCK_END_TYPE */ 3); diff --git a/tests/ErrorTest.php b/tests/ErrorTest.php index 7892be9d07b..db6418ed685 100644 --- a/tests/ErrorTest.php +++ b/tests/ErrorTest.php @@ -234,6 +234,28 @@ public function testTwigArrayReduceThrowsRuntimeExceptions() } } + public function testTwigArgumentCountErrorThrowsRuntimeExceptions() + { + $loader = new ArrayLoader([ + 'argument-error.html' => << true, 'cache' => false]); + + $template = $twig->load('argument-error.html'); + try { + $template->render(); + + $this->fail(); + } catch (RuntimeError $e) { + $this->assertEquals(2, $e->getTemplateLine()); + $this->assertEquals('argument-error.html', $e->getSourceContext()->getName()); + } + } + public function getErroredTemplates() { return [ diff --git a/tests/Extension/CoreTest.php b/tests/Extension/CoreTest.php index 29a799b8103..431517f6b21 100644 --- a/tests/Extension/CoreTest.php +++ b/tests/Extension/CoreTest.php @@ -380,9 +380,6 @@ public function rewind(): void $this->position = 0; } - /** - * @return mixed - */ #[\ReturnTypeWillChange] public function current() { @@ -393,9 +390,6 @@ public function current() throw new \LogicException('Code should only use the keys, not the values provided by iterator.'); } - /** - * @return mixed - */ #[\ReturnTypeWillChange] public function key() { diff --git a/tests/Extension/EscaperTest.php b/tests/Extension/EscaperTest.php index 9804feaa5c7..7c558c3ac1d 100644 --- a/tests/Extension/EscaperTest.php +++ b/tests/Extension/EscaperTest.php @@ -189,10 +189,10 @@ public function testJavascriptEscapingConvertsSpecialCharsWithInternalEncoding() try { mb_internal_encoding('ISO-8859-1'); foreach ($this->jsSpecialChars as $key => $value) { - $this->assertEquals($value, twig_escape_filter($twig, $key, 'js'), 'Failed to escape: ' . $key); + $this->assertEquals($value, twig_escape_filter($twig, $key, 'js'), 'Failed to escape: '.$key); } } finally { - if ($previousInternalEncoding !== false) { + if (false !== $previousInternalEncoding) { mb_internal_encoding($previousInternalEncoding); } } @@ -249,7 +249,7 @@ public function testUrlEscapingConvertsSpecialChars() public function testUnicodeCodepointConversionToUtf8() { $expected = ' ~ޙ'; - $codepoints = [0x20, 0x7e, 0x799]; + $codepoints = [0x20, 0x7E, 0x799]; $result = ''; foreach ($codepoints as $value) { $result .= $this->codepointToUtf8($value); @@ -270,19 +270,19 @@ protected function codepointToUtf8($codepoint) return \chr($codepoint); } if ($codepoint < 0x800) { - return \chr($codepoint >> 6 & 0x3f | 0xc0) - .\chr($codepoint & 0x3f | 0x80); + return \chr($codepoint >> 6 & 0x3F | 0xC0) + .\chr($codepoint & 0x3F | 0x80); } if ($codepoint < 0x10000) { - return \chr($codepoint >> 12 & 0x0f | 0xe0) - .\chr($codepoint >> 6 & 0x3f | 0x80) - .\chr($codepoint & 0x3f | 0x80); + return \chr($codepoint >> 12 & 0x0F | 0xE0) + .\chr($codepoint >> 6 & 0x3F | 0x80) + .\chr($codepoint & 0x3F | 0x80); } if ($codepoint < 0x110000) { - return \chr($codepoint >> 18 & 0x07 | 0xf0) - .\chr($codepoint >> 12 & 0x3f | 0x80) - .\chr($codepoint >> 6 & 0x3f | 0x80) - .\chr($codepoint & 0x3f | 0x80); + return \chr($codepoint >> 18 & 0x07 | 0xF0) + .\chr($codepoint >> 12 & 0x3F | 0x80) + .\chr($codepoint >> 6 & 0x3F | 0x80) + .\chr($codepoint & 0x3F | 0x80); } throw new \Exception('Codepoint requested outside of Unicode range.'); } diff --git a/tests/Extension/SandboxTest.php b/tests/Extension/SandboxTest.php index e365da63280..2e40a825da3 100644 --- a/tests/Extension/SandboxTest.php +++ b/tests/Extension/SandboxTest.php @@ -36,6 +36,7 @@ protected function setUp(): void 'name' => 'Fabien', 'obj' => new FooObject(), 'arr' => ['obj' => new FooObject()], + 'child_obj' => new ChildClass(), ]; self::$templates = [ @@ -56,6 +57,8 @@ protected function setUp(): void '1_range_operator' => '{{ (1..2)[0] }}', '1_syntax_error_wrapper' => '{% sandbox %}{% include "1_syntax_error" %}{% endsandbox %}', '1_syntax_error' => '{% syntax error }}', + '1_childobj_parentmethod' => '{{ child_obj.ParentMethod() }}', + '1_childobj_childmethod' => '{{ child_obj.ChildMethod() }}', ]; } @@ -320,7 +323,7 @@ public function testSandboxAllowRangeOperator() $this->assertEquals('1', $twig->load('1_range_operator')->render(self::$params), 'Sandbox allow the range operator'); } - public function testSandboxAllowFunctionsCaseInsensitive() + public function testSandboxAllowMethodsCaseInsensitive() { foreach (['getfoobar', 'getFoobar', 'getFooBar'] as $name) { $twig = $this->getEnvironment(true, [], self::$templates, [], [], ['Twig\Tests\Extension\FooObject' => $name]); @@ -410,6 +413,42 @@ public function testSandboxWithClosureFilter() $this->assertSame('foo, bar', $twig->load('index')->render([])); } + public function testMultipleClassMatchesViaInheritanceInAllowedMethods() + { + $twig_child_first = $this->getEnvironment(true, [], self::$templates, [], [], [ + 'Twig\Tests\Extension\ChildClass' => ['ChildMethod'], + 'Twig\Tests\Extension\ParentClass' => ['ParentMethod'], + ]); + $twig_parent_first = $this->getEnvironment(true, [], self::$templates, [], [], [ + 'Twig\Tests\Extension\ParentClass' => ['ParentMethod'], + 'Twig\Tests\Extension\ChildClass' => ['ChildMethod'], + ]); + + try { + $twig_child_first->load('1_childobj_childmethod')->render(self::$params); + } catch (SecurityError $e) { + $this->fail('This test case is malfunctioning as even the child class method which comes first is not being allowed.'); + } + + try { + $twig_parent_first->load('1_childobj_parentmethod')->render(self::$params); + } catch (SecurityError $e) { + $this->fail('This test case is malfunctioning as even the parent class method which comes first is not being allowed.'); + } + + try { + $twig_parent_first->load('1_childobj_childmethod')->render(self::$params); + } catch (SecurityError $e) { + $this->fail('checkMethodAllowed is exiting prematurely after matching a parent class and not seeing a method allowed on a child class later in the list'); + } + + try { + $twig_child_first->load('1_childobj_parentmethod')->render(self::$params); + } catch (SecurityError $e) { + $this->fail('checkMethodAllowed is exiting prematurely after matching a child class and not seeing a method allowed on its parent class later in the list'); + } + } + protected function getEnvironment($sandboxed, $options, $templates, $tags = [], $filters = [], $methods = [], $properties = [], $functions = []) { $loader = new ArrayLoader($templates); @@ -421,6 +460,19 @@ protected function getEnvironment($sandboxed, $options, $templates, $tags = [], } } +class ParentClass +{ + public function ParentMethod() + { + } +} +class ChildClass extends ParentClass +{ + public function ChildMethod() + { + } +} + class FooObject { public static $called = ['__toString' => 0, 'foo' => 0, 'getFooBar' => 0]; diff --git a/tests/IntegrationTest.php b/tests/IntegrationTest.php index 893bda345e8..aa24d5fbe8f 100644 --- a/tests/IntegrationTest.php +++ b/tests/IntegrationTest.php @@ -105,18 +105,12 @@ public function rewind(): void $this->position = 0; } - /** - * @return mixed - */ #[\ReturnTypeWillChange] public function current() { return $this->array[$this->position]; } - /** - * @return mixed - */ #[\ReturnTypeWillChange] public function key() { @@ -371,9 +365,6 @@ class SimpleIteratorForTesting implements \Iterator private $data = [1, 2, 3, 4, 5, 6, 7]; private $key = 0; - /** - * @return mixed - */ #[\ReturnTypeWillChange] public function current() { @@ -385,9 +376,6 @@ public function next(): void ++$this->key; } - /** - * @return mixed - */ #[\ReturnTypeWillChange] public function key() { diff --git a/tests/Node/IncludeTest.php b/tests/Node/IncludeTest.php index ab1fdf0bfe0..92681662da7 100644 --- a/tests/Node/IncludeTest.php +++ b/tests/Node/IncludeTest.php @@ -47,11 +47,11 @@ public function getTests() ]; $expr = new ConditionalExpression( - new ConstantExpression(true, 1), - new ConstantExpression('foo', 1), - new ConstantExpression('foo', 1), - 0 - ); + new ConstantExpression(true, 1), + new ConstantExpression('foo', 1), + new ConstantExpression('foo', 1), + 0 + ); $node = new IncludeNode($expr, null, false, false, 1); $tests[] = [$node, << 1,); @@ -168,16 +174,25 @@ protected function doDisplay(array \$context, array \$blocks = []) \$this->parent->display(\$context, array_merge(\$this->blocks, \$blocks)); } + /** + * @codeCoverageIgnore + */ public function getTemplateName() { return "foo.twig"; } + /** + * @codeCoverageIgnore + */ public function isTraitable() { return false; } + /** + * @codeCoverageIgnore + */ public function getDebugInfo() { return array ( 43 => 1, 41 => 2, 34 => 1,); @@ -194,11 +209,11 @@ public function getSourceContext() $set = new SetNode(false, new Node([new AssignNameExpression('foo', 4)]), new Node([new ConstantExpression('foo', 4)]), 4); $body = new Node([$set]); $extends = new ConditionalExpression( - new ConstantExpression(true, 2), - new ConstantExpression('foo', 2), - new ConstantExpression('foo', 2), - 2 - ); + new ConstantExpression(true, 2), + new ConstantExpression('foo', 2), + new ConstantExpression('foo', 2), + 2 + ); $twig = new Environment($this->createMock(LoaderInterface::class), ['debug' => true]); $node = new ModuleNode($body, $extends, $blocks, $macros, $traits, new Node([]), $source); @@ -248,16 +263,25 @@ protected function doDisplay(array \$context, array \$blocks = []) \$this->getParent(\$context)->display(\$context, array_merge(\$this->blocks, \$blocks)); } + /** + * @codeCoverageIgnore + */ public function getTemplateName() { return "foo.twig"; } + /** + * @codeCoverageIgnore + */ public function isTraitable() { return false; } + /** + * @codeCoverageIgnore + */ public function getDebugInfo() { return array ( 43 => 2, 41 => 4, 34 => 2,); diff --git a/tests/ParserTest.php b/tests/ParserTest.php index 65956cf7e74..cdd8e875743 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -168,7 +168,7 @@ public function testGetVarName() {{ foo }} {% endmacro %} EOF - , 'index'))); + , 'index'))); // The getVarName() must not depend on the template loaders, // If this test does not throw any exception, that's good. diff --git a/tests/Profiler/Dumper/AbstractTest.php b/tests/Profiler/Dumper/AbstractTest.php index 29e40d26cbc..1891c27507a 100644 --- a/tests/Profiler/Dumper/AbstractTest.php +++ b/tests/Profiler/Dumper/AbstractTest.php @@ -73,7 +73,6 @@ private function getMacroProfile(array $subProfiles = []) /** * @param string $name * @param float $duration - * @param bool $isTemplate * @param string $type * @param string $templateName * diff --git a/tests/Profiler/Dumper/BlackfireTest.php b/tests/Profiler/Dumper/BlackfireTest.php index b1c2cd7d1f0..3a33d94031b 100644 --- a/tests/Profiler/Dumper/BlackfireTest.php +++ b/tests/Profiler/Dumper/BlackfireTest.php @@ -31,6 +31,6 @@ public function testDump() embedded.twig==>included.twig//2 %d %d %d index.twig==>index.twig::macro(foo)//1 %d %d %d EOF - , $dumper->dump($this->getProfile())); + , $dumper->dump($this->getProfile())); } } diff --git a/tests/Profiler/Dumper/HtmlTest.php b/tests/Profiler/Dumper/HtmlTest.php index 20a1ab439c5..2dcbb9aec57 100644 --- a/tests/Profiler/Dumper/HtmlTest.php +++ b/tests/Profiler/Dumper/HtmlTest.php @@ -29,6 +29,6 @@ public function testDump() └ included.twig EOF - , $dumper->dump($this->getProfile())); + , $dumper->dump($this->getProfile())); } } diff --git a/tests/Profiler/Dumper/TextTest.php b/tests/Profiler/Dumper/TextTest.php index 8240e356bd2..ba19c2c90dc 100644 --- a/tests/Profiler/Dumper/TextTest.php +++ b/tests/Profiler/Dumper/TextTest.php @@ -29,6 +29,6 @@ public function testDump() └ included.twig EOF - , $dumper->dump($this->getProfile())); + , $dumper->dump($this->getProfile())); } } diff --git a/tests/TemplateTest.php b/tests/TemplateTest.php index bd26e9d95b7..9c2364c1d21 100644 --- a/tests/TemplateTest.php +++ b/tests/TemplateTest.php @@ -89,7 +89,7 @@ public function getAttributeExceptions() public function testGetAttributeWithSandbox($object, $item, $allowed) { $twig = new Environment($this->createMock(LoaderInterface::class)); - $policy = new SecurityPolicy([], [], [/*method*/], [/*prop*/], []); + $policy = new SecurityPolicy([], [], [/* method */], [/* prop */], []); $twig->addExtension(new SandboxExtension($policy, !$allowed)); $template = new TemplateForTest($twig); @@ -477,25 +477,22 @@ class TemplateArrayAccessObject implements \ArrayAccess '+4' => '+4', ]; - public function offsetExists($name) : bool + public function offsetExists($name): bool { return \array_key_exists($name, $this->attributes); } - /** - * @return mixed - */ #[\ReturnTypeWillChange] public function offsetGet($name) { return \array_key_exists($name, $this->attributes) ? $this->attributes[$name] : null; } - public function offsetSet($name, $value) : void + public function offsetSet($name, $value): void { } - public function offsetUnset($name) : void + public function offsetUnset($name): void { } } @@ -570,25 +567,22 @@ class TemplatePropertyObjectAndArrayAccess extends TemplatePropertyObject implem 'baf' => 'baf', ]; - public function offsetExists($offset) : bool + public function offsetExists($offset): bool { return \array_key_exists($offset, $this->data); } - /** - * @return mixed - */ #[\ReturnTypeWillChange] public function offsetGet($offset) { return $this->offsetExists($offset) ? $this->data[$offset] : 'n/a'; } - public function offsetSet($offset, $value) : void + public function offsetSet($offset, $value): void { } - public function offsetUnset($offset) : void + public function offsetUnset($offset): void { } } @@ -722,26 +716,23 @@ class TemplateArrayAccess implements \ArrayAccess ]; private $children = []; - public function offsetExists($offset) : bool + public function offsetExists($offset): bool { return \array_key_exists($offset, $this->children); } - /** - * @return mixed - */ #[\ReturnTypeWillChange] public function offsetGet($offset) { return $this->children[$offset]; } - public function offsetSet($offset, $value) : void + public function offsetSet($offset, $value): void { $this->children[$offset] = $value; } - public function offsetUnset($offset) : void + public function offsetUnset($offset): void { unset($this->children[$offset]); }