vendor/twig/twig/src/Extension/CoreExtension.php line 1871

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of Twig.
  4.  *
  5.  * (c) Fabien Potencier
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Twig\Extension;
  11. use Twig\DeprecatedCallableInfo;
  12. use Twig\Environment;
  13. use Twig\Error\LoaderError;
  14. use Twig\Error\RuntimeError;
  15. use Twig\Error\SyntaxError;
  16. use Twig\ExpressionParser;
  17. use Twig\Markup;
  18. use Twig\Node\Expression\AbstractExpression;
  19. use Twig\Node\Expression\Binary\AddBinary;
  20. use Twig\Node\Expression\Binary\AndBinary;
  21. use Twig\Node\Expression\Binary\BitwiseAndBinary;
  22. use Twig\Node\Expression\Binary\BitwiseOrBinary;
  23. use Twig\Node\Expression\Binary\BitwiseXorBinary;
  24. use Twig\Node\Expression\Binary\ConcatBinary;
  25. use Twig\Node\Expression\Binary\DivBinary;
  26. use Twig\Node\Expression\Binary\ElvisBinary;
  27. use Twig\Node\Expression\Binary\EndsWithBinary;
  28. use Twig\Node\Expression\Binary\EqualBinary;
  29. use Twig\Node\Expression\Binary\FloorDivBinary;
  30. use Twig\Node\Expression\Binary\GreaterBinary;
  31. use Twig\Node\Expression\Binary\GreaterEqualBinary;
  32. use Twig\Node\Expression\Binary\HasEveryBinary;
  33. use Twig\Node\Expression\Binary\HasSomeBinary;
  34. use Twig\Node\Expression\Binary\InBinary;
  35. use Twig\Node\Expression\Binary\LessBinary;
  36. use Twig\Node\Expression\Binary\LessEqualBinary;
  37. use Twig\Node\Expression\Binary\MatchesBinary;
  38. use Twig\Node\Expression\Binary\ModBinary;
  39. use Twig\Node\Expression\Binary\MulBinary;
  40. use Twig\Node\Expression\Binary\NotEqualBinary;
  41. use Twig\Node\Expression\Binary\NotInBinary;
  42. use Twig\Node\Expression\Binary\NullCoalesceBinary;
  43. use Twig\Node\Expression\Binary\OrBinary;
  44. use Twig\Node\Expression\Binary\PowerBinary;
  45. use Twig\Node\Expression\Binary\RangeBinary;
  46. use Twig\Node\Expression\Binary\SpaceshipBinary;
  47. use Twig\Node\Expression\Binary\StartsWithBinary;
  48. use Twig\Node\Expression\Binary\SubBinary;
  49. use Twig\Node\Expression\Binary\XorBinary;
  50. use Twig\Node\Expression\BlockReferenceExpression;
  51. use Twig\Node\Expression\Filter\DefaultFilter;
  52. use Twig\Node\Expression\FunctionNode\EnumCasesFunction;
  53. use Twig\Node\Expression\FunctionNode\EnumFunction;
  54. use Twig\Node\Expression\GetAttrExpression;
  55. use Twig\Node\Expression\ParentExpression;
  56. use Twig\Node\Expression\Test\ConstantTest;
  57. use Twig\Node\Expression\Test\DefinedTest;
  58. use Twig\Node\Expression\Test\DivisiblebyTest;
  59. use Twig\Node\Expression\Test\EvenTest;
  60. use Twig\Node\Expression\Test\NullTest;
  61. use Twig\Node\Expression\Test\OddTest;
  62. use Twig\Node\Expression\Test\SameasTest;
  63. use Twig\Node\Expression\Unary\NegUnary;
  64. use Twig\Node\Expression\Unary\NotUnary;
  65. use Twig\Node\Expression\Unary\PosUnary;
  66. use Twig\Node\Node;
  67. use Twig\OperatorPrecedenceChange;
  68. use Twig\Parser;
  69. use Twig\Sandbox\SecurityNotAllowedMethodError;
  70. use Twig\Sandbox\SecurityNotAllowedPropertyError;
  71. use Twig\Source;
  72. use Twig\Template;
  73. use Twig\TemplateWrapper;
  74. use Twig\TokenParser\ApplyTokenParser;
  75. use Twig\TokenParser\BlockTokenParser;
  76. use Twig\TokenParser\DeprecatedTokenParser;
  77. use Twig\TokenParser\DoTokenParser;
  78. use Twig\TokenParser\EmbedTokenParser;
  79. use Twig\TokenParser\ExtendsTokenParser;
  80. use Twig\TokenParser\FlushTokenParser;
  81. use Twig\TokenParser\ForTokenParser;
  82. use Twig\TokenParser\FromTokenParser;
  83. use Twig\TokenParser\GuardTokenParser;
  84. use Twig\TokenParser\IfTokenParser;
  85. use Twig\TokenParser\ImportTokenParser;
  86. use Twig\TokenParser\IncludeTokenParser;
  87. use Twig\TokenParser\MacroTokenParser;
  88. use Twig\TokenParser\SetTokenParser;
  89. use Twig\TokenParser\TypesTokenParser;
  90. use Twig\TokenParser\UseTokenParser;
  91. use Twig\TokenParser\WithTokenParser;
  92. use Twig\TwigFilter;
  93. use Twig\TwigFunction;
  94. use Twig\TwigTest;
  95. use Twig\Util\CallableArgumentsExtractor;
  96. final class CoreExtension extends AbstractExtension
  97. {
  98.     public const ARRAY_LIKE_CLASSES = [
  99.         'ArrayIterator',
  100.         'ArrayObject',
  101.         'CachingIterator',
  102.         'RecursiveArrayIterator',
  103.         'RecursiveCachingIterator',
  104.         'SplDoublyLinkedList',
  105.         'SplFixedArray',
  106.         'SplObjectStorage',
  107.         'SplQueue',
  108.         'SplStack',
  109.         'WeakMap',
  110.     ];
  111.     private const DEFAULT_TRIM_CHARS " \t\n\r\0\x0B";
  112.     private $dateFormats = ['F j, Y H:i''%d days'];
  113.     private $numberFormat = [0'.'','];
  114.     private $timezone null;
  115.     /**
  116.      * Sets the default format to be used by the date filter.
  117.      *
  118.      * @param string|null $format             The default date format string
  119.      * @param string|null $dateIntervalFormat The default date interval format string
  120.      */
  121.     public function setDateFormat($format null$dateIntervalFormat null)
  122.     {
  123.         if (null !== $format) {
  124.             $this->dateFormats[0] = $format;
  125.         }
  126.         if (null !== $dateIntervalFormat) {
  127.             $this->dateFormats[1] = $dateIntervalFormat;
  128.         }
  129.     }
  130.     /**
  131.      * Gets the default format to be used by the date filter.
  132.      *
  133.      * @return array The default date format string and the default date interval format string
  134.      */
  135.     public function getDateFormat()
  136.     {
  137.         return $this->dateFormats;
  138.     }
  139.     /**
  140.      * Sets the default timezone to be used by the date filter.
  141.      *
  142.      * @param \DateTimeZone|string $timezone The default timezone string or a \DateTimeZone object
  143.      */
  144.     public function setTimezone($timezone)
  145.     {
  146.         $this->timezone $timezone instanceof \DateTimeZone $timezone : new \DateTimeZone($timezone);
  147.     }
  148.     /**
  149.      * Gets the default timezone to be used by the date filter.
  150.      *
  151.      * @return \DateTimeZone The default timezone currently in use
  152.      */
  153.     public function getTimezone()
  154.     {
  155.         if (null === $this->timezone) {
  156.             $this->timezone = new \DateTimeZone(date_default_timezone_get());
  157.         }
  158.         return $this->timezone;
  159.     }
  160.     /**
  161.      * Sets the default format to be used by the number_format filter.
  162.      *
  163.      * @param int    $decimal      the number of decimal places to use
  164.      * @param string $decimalPoint the character(s) to use for the decimal point
  165.      * @param string $thousandSep  the character(s) to use for the thousands separator
  166.      */
  167.     public function setNumberFormat($decimal$decimalPoint$thousandSep)
  168.     {
  169.         $this->numberFormat = [$decimal$decimalPoint$thousandSep];
  170.     }
  171.     /**
  172.      * Get the default format used by the number_format filter.
  173.      *
  174.      * @return array The arguments for number_format()
  175.      */
  176.     public function getNumberFormat()
  177.     {
  178.         return $this->numberFormat;
  179.     }
  180.     public function getTokenParsers(): array
  181.     {
  182.         return [
  183.             new ApplyTokenParser(),
  184.             new ForTokenParser(),
  185.             new IfTokenParser(),
  186.             new ExtendsTokenParser(),
  187.             new IncludeTokenParser(),
  188.             new BlockTokenParser(),
  189.             new UseTokenParser(),
  190.             new MacroTokenParser(),
  191.             new ImportTokenParser(),
  192.             new FromTokenParser(),
  193.             new SetTokenParser(),
  194.             new TypesTokenParser(),
  195.             new FlushTokenParser(),
  196.             new DoTokenParser(),
  197.             new EmbedTokenParser(),
  198.             new WithTokenParser(),
  199.             new DeprecatedTokenParser(),
  200.             new GuardTokenParser(),
  201.         ];
  202.     }
  203.     public function getFilters(): array
  204.     {
  205.         return [
  206.             // formatting filters
  207.             new TwigFilter('date', [$this'formatDate']),
  208.             new TwigFilter('date_modify', [$this'modifyDate']),
  209.             new TwigFilter('format', [self::class, 'sprintf']),
  210.             new TwigFilter('replace', [self::class, 'replace']),
  211.             new TwigFilter('number_format', [$this'formatNumber']),
  212.             new TwigFilter('abs''abs'),
  213.             new TwigFilter('round', [self::class, 'round']),
  214.             // encoding
  215.             new TwigFilter('url_encode', [self::class, 'urlencode']),
  216.             new TwigFilter('json_encode''json_encode'),
  217.             new TwigFilter('convert_encoding', [self::class, 'convertEncoding']),
  218.             // string filters
  219.             new TwigFilter('title', [self::class, 'titleCase'], ['needs_charset' => true]),
  220.             new TwigFilter('capitalize', [self::class, 'capitalize'], ['needs_charset' => true]),
  221.             new TwigFilter('upper', [self::class, 'upper'], ['needs_charset' => true]),
  222.             new TwigFilter('lower', [self::class, 'lower'], ['needs_charset' => true]),
  223.             new TwigFilter('striptags', [self::class, 'striptags']),
  224.             new TwigFilter('trim', [self::class, 'trim']),
  225.             new TwigFilter('nl2br', [self::class, 'nl2br'], ['pre_escape' => 'html''is_safe' => ['html']]),
  226.             new TwigFilter('spaceless', [self::class, 'spaceless'], ['is_safe' => ['html'], 'deprecation_info' => new DeprecatedCallableInfo('twig/twig''3.12')]),
  227.             // array helpers
  228.             new TwigFilter('join', [self::class, 'join']),
  229.             new TwigFilter('split', [self::class, 'split'], ['needs_charset' => true]),
  230.             new TwigFilter('sort', [self::class, 'sort'], ['needs_environment' => true]),
  231.             new TwigFilter('merge', [self::class, 'merge']),
  232.             new TwigFilter('batch', [self::class, 'batch']),
  233.             new TwigFilter('column', [self::class, 'column']),
  234.             new TwigFilter('filter', [self::class, 'filter'], ['needs_environment' => true]),
  235.             new TwigFilter('map', [self::class, 'map'], ['needs_environment' => true]),
  236.             new TwigFilter('reduce', [self::class, 'reduce'], ['needs_environment' => true]),
  237.             new TwigFilter('find', [self::class, 'find'], ['needs_environment' => true]),
  238.             // string/array filters
  239.             new TwigFilter('reverse', [self::class, 'reverse'], ['needs_charset' => true]),
  240.             new TwigFilter('shuffle', [self::class, 'shuffle'], ['needs_charset' => true]),
  241.             new TwigFilter('length', [self::class, 'length'], ['needs_charset' => true]),
  242.             new TwigFilter('slice', [self::class, 'slice'], ['needs_charset' => true]),
  243.             new TwigFilter('first', [self::class, 'first'], ['needs_charset' => true]),
  244.             new TwigFilter('last', [self::class, 'last'], ['needs_charset' => true]),
  245.             // iteration and runtime
  246.             new TwigFilter('default', [self::class, 'default'], ['node_class' => DefaultFilter::class]),
  247.             new TwigFilter('keys', [self::class, 'keys']),
  248.         ];
  249.     }
  250.     public function getFunctions(): array
  251.     {
  252.         return [
  253.             new TwigFunction('parent'null, ['parser_callable' => [self::class, 'parseParentFunction']]),
  254.             new TwigFunction('block'null, ['parser_callable' => [self::class, 'parseBlockFunction']]),
  255.             new TwigFunction('attribute'null, ['parser_callable' => [self::class, 'parseAttributeFunction']]),
  256.             new TwigFunction('max''max'),
  257.             new TwigFunction('min''min'),
  258.             new TwigFunction('range''range'),
  259.             new TwigFunction('constant', [self::class, 'constant']),
  260.             new TwigFunction('cycle', [self::class, 'cycle']),
  261.             new TwigFunction('random', [self::class, 'random'], ['needs_charset' => true]),
  262.             new TwigFunction('date', [$this'convertDate']),
  263.             new TwigFunction('include', [self::class, 'include'], ['needs_environment' => true'needs_context' => true'is_safe' => ['all']]),
  264.             new TwigFunction('source', [self::class, 'source'], ['needs_environment' => true'is_safe' => ['all']]),
  265.             new TwigFunction('enum_cases', [self::class, 'enumCases'], ['node_class' => EnumCasesFunction::class]),
  266.             new TwigFunction('enum', [self::class, 'enum'], ['node_class' => EnumFunction::class]),
  267.         ];
  268.     }
  269.     public function getTests(): array
  270.     {
  271.         return [
  272.             new TwigTest('even'null, ['node_class' => EvenTest::class]),
  273.             new TwigTest('odd'null, ['node_class' => OddTest::class]),
  274.             new TwigTest('defined'null, ['node_class' => DefinedTest::class]),
  275.             new TwigTest('same as'null, ['node_class' => SameasTest::class, 'one_mandatory_argument' => true]),
  276.             new TwigTest('none'null, ['node_class' => NullTest::class]),
  277.             new TwigTest('null'null, ['node_class' => NullTest::class]),
  278.             new TwigTest('divisible by'null, ['node_class' => DivisiblebyTest::class, 'one_mandatory_argument' => true]),
  279.             new TwigTest('constant'null, ['node_class' => ConstantTest::class]),
  280.             new TwigTest('empty', [self::class, 'testEmpty']),
  281.             new TwigTest('iterable''is_iterable'),
  282.             new TwigTest('sequence', [self::class, 'testSequence']),
  283.             new TwigTest('mapping', [self::class, 'testMapping']),
  284.         ];
  285.     }
  286.     public function getNodeVisitors(): array
  287.     {
  288.         return [];
  289.     }
  290.     public function getOperators(): array
  291.     {
  292.         return [
  293.             [
  294.                 'not' => ['precedence' => 50'precedence_change' => new OperatorPrecedenceChange('twig/twig''3.15'70), 'class' => NotUnary::class],
  295.                 '-' => ['precedence' => 500'class' => NegUnary::class],
  296.                 '+' => ['precedence' => 500'class' => PosUnary::class],
  297.             ],
  298.             [
  299.                 '? :' => ['precedence' => 5'class' => ElvisBinary::class, 'associativity' => ExpressionParser::OPERATOR_RIGHT],
  300.                 '?:' => ['precedence' => 5'class' => ElvisBinary::class, 'associativity' => ExpressionParser::OPERATOR_RIGHT],
  301.                 '??' => ['precedence' => 300'precedence_change' => new OperatorPrecedenceChange('twig/twig''3.15'5), 'class' => NullCoalesceBinary::class, 'associativity' => ExpressionParser::OPERATOR_RIGHT],
  302.                 'or' => ['precedence' => 10'class' => OrBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  303.                 'xor' => ['precedence' => 12'class' => XorBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  304.                 'and' => ['precedence' => 15'class' => AndBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  305.                 'b-or' => ['precedence' => 16'class' => BitwiseOrBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  306.                 'b-xor' => ['precedence' => 17'class' => BitwiseXorBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  307.                 'b-and' => ['precedence' => 18'class' => BitwiseAndBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  308.                 '==' => ['precedence' => 20'class' => EqualBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  309.                 '!=' => ['precedence' => 20'class' => NotEqualBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  310.                 '<=>' => ['precedence' => 20'class' => SpaceshipBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  311.                 '<' => ['precedence' => 20'class' => LessBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  312.                 '>' => ['precedence' => 20'class' => GreaterBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  313.                 '>=' => ['precedence' => 20'class' => GreaterEqualBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  314.                 '<=' => ['precedence' => 20'class' => LessEqualBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  315.                 'not in' => ['precedence' => 20'class' => NotInBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  316.                 'in' => ['precedence' => 20'class' => InBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  317.                 'matches' => ['precedence' => 20'class' => MatchesBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  318.                 'starts with' => ['precedence' => 20'class' => StartsWithBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  319.                 'ends with' => ['precedence' => 20'class' => EndsWithBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  320.                 'has some' => ['precedence' => 20'class' => HasSomeBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  321.                 'has every' => ['precedence' => 20'class' => HasEveryBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  322.                 '..' => ['precedence' => 25'class' => RangeBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  323.                 '+' => ['precedence' => 30'class' => AddBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  324.                 '-' => ['precedence' => 30'class' => SubBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  325.                 '~' => ['precedence' => 40'precedence_change' => new OperatorPrecedenceChange('twig/twig''3.15'27), 'class' => ConcatBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  326.                 '*' => ['precedence' => 60'class' => MulBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  327.                 '/' => ['precedence' => 60'class' => DivBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  328.                 '//' => ['precedence' => 60'class' => FloorDivBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  329.                 '%' => ['precedence' => 60'class' => ModBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  330.                 'is' => ['precedence' => 100'associativity' => ExpressionParser::OPERATOR_LEFT],
  331.                 'is not' => ['precedence' => 100'associativity' => ExpressionParser::OPERATOR_LEFT],
  332.                 '**' => ['precedence' => 200'class' => PowerBinary::class, 'associativity' => ExpressionParser::OPERATOR_RIGHT],
  333.             ],
  334.         ];
  335.     }
  336.     /**
  337.      * Cycles over a sequence.
  338.      *
  339.      * @param array|\ArrayAccess $values   A non-empty sequence of values
  340.      * @param int<0, max>        $position The position of the value to return in the cycle
  341.      *
  342.      * @return mixed The value at the given position in the sequence, wrapping around as needed
  343.      *
  344.      * @internal
  345.      */
  346.     public static function cycle($values$position): mixed
  347.     {
  348.         if (!\is_array($values)) {
  349.             if (!$values instanceof \ArrayAccess) {
  350.                 throw new RuntimeError('The "cycle" function expects an array or "ArrayAccess" as first argument.');
  351.             }
  352.             if (!is_countable($values)) {
  353.                 // To be uncommented in 4.0
  354.                 // throw new RuntimeError('The "cycle" function expects a countable sequence as first argument.');
  355.                 trigger_deprecation('twig/twig''3.12''Passing a non-countable sequence of values to "%s()" is deprecated.'__METHOD__);
  356.                 return $values;
  357.             }
  358.             $values self::toArray($valuesfalse);
  359.         }
  360.         if (!$count \count($values)) {
  361.             throw new RuntimeError('The "cycle" function expects a non-empty sequence.');
  362.         }
  363.         return $values[$position $count];
  364.     }
  365.     /**
  366.      * Returns a random value depending on the supplied parameter type:
  367.      * - a random item from a \Traversable or array
  368.      * - a random character from a string
  369.      * - a random integer between 0 and the integer parameter.
  370.      *
  371.      * @param \Traversable|array|int|float|string $values The values to pick a random item from
  372.      * @param int|null                            $max    Maximum value used when $values is an int
  373.      *
  374.      * @return mixed A random value from the given sequence
  375.      *
  376.      * @throws RuntimeError when $values is an empty array (does not apply to an empty string which is returned as is)
  377.      *
  378.      * @internal
  379.      */
  380.     public static function random(string $charset$values null$max null)
  381.     {
  382.         if (null === $values) {
  383.             return null === $max mt_rand() : mt_rand(0, (int) $max);
  384.         }
  385.         if (\is_int($values) || \is_float($values)) {
  386.             if (null === $max) {
  387.                 if ($values 0) {
  388.                     $max 0;
  389.                     $min $values;
  390.                 } else {
  391.                     $max $values;
  392.                     $min 0;
  393.                 }
  394.             } else {
  395.                 $min $values;
  396.             }
  397.             return mt_rand((int) $min, (int) $max);
  398.         }
  399.         if (\is_string($values)) {
  400.             if ('' === $values) {
  401.                 return '';
  402.             }
  403.             if ('UTF-8' !== $charset) {
  404.                 $values self::convertEncoding($values'UTF-8'$charset);
  405.             }
  406.             // unicode version of str_split()
  407.             // split at all positions, but not after the start and not before the end
  408.             $values preg_split('/(?<!^)(?!$)/u'$values);
  409.             if ('UTF-8' !== $charset) {
  410.                 foreach ($values as $i => $value) {
  411.                     $values[$i] = self::convertEncoding($value$charset'UTF-8');
  412.                 }
  413.             }
  414.         }
  415.         if (!is_iterable($values)) {
  416.             return $values;
  417.         }
  418.         $values self::toArray($values);
  419.         if (=== \count($values)) {
  420.             throw new RuntimeError('The "random" function cannot pick from an empty sequence or mapping.');
  421.         }
  422.         return $values[array_rand($values1)];
  423.     }
  424.     /**
  425.      * Formats a date.
  426.      *
  427.      *   {{ post.published_at|date("m/d/Y") }}
  428.      *
  429.      * @param \DateTimeInterface|\DateInterval|string|int|null $date     A date, a timestamp or null to use the current time
  430.      * @param string|null                                      $format   The target format, null to use the default
  431.      * @param \DateTimeZone|string|false|null                  $timezone The target timezone, null to use the default, false to leave unchanged
  432.      */
  433.     public function formatDate($date$format null$timezone null): string
  434.     {
  435.         if (null === $format) {
  436.             $formats $this->getDateFormat();
  437.             $format $date instanceof \DateInterval $formats[1] : $formats[0];
  438.         }
  439.         if ($date instanceof \DateInterval) {
  440.             return $date->format($format);
  441.         }
  442.         return $this->convertDate($date$timezone)->format($format);
  443.     }
  444.     /**
  445.      * Returns a new date object modified.
  446.      *
  447.      *   {{ post.published_at|date_modify("-1day")|date("m/d/Y") }}
  448.      *
  449.      * @param \DateTimeInterface|string|int|null $date     A date, a timestamp or null to use the current time
  450.      * @param string                             $modifier A modifier string
  451.      *
  452.      * @return \DateTime|\DateTimeImmutable
  453.      *
  454.      * @internal
  455.      */
  456.     public function modifyDate($date$modifier)
  457.     {
  458.         return $this->convertDate($datefalse)->modify($modifier);
  459.     }
  460.     /**
  461.      * Returns a formatted string.
  462.      *
  463.      * @param string|null $format
  464.      * @param ...$values
  465.      *
  466.      * @internal
  467.      */
  468.     public static function sprintf($format, ...$values): string
  469.     {
  470.         return \sprintf($format ?? '', ...$values);
  471.     }
  472.     /**
  473.      * @internal
  474.      */
  475.     public static function dateConverter(Environment $env$date$format null$timezone null): string
  476.     {
  477.         return $env->getExtension(self::class)->formatDate($date$format$timezone);
  478.     }
  479.     /**
  480.      * Converts an input to a \DateTime instance.
  481.      *
  482.      *    {% if date(user.created_at) < date('+2days') %}
  483.      *      {# do something #}
  484.      *    {% endif %}
  485.      *
  486.      * @param \DateTimeInterface|string|int|null $date     A date, a timestamp or null to use the current time
  487.      * @param \DateTimeZone|string|false|null    $timezone The target timezone, null to use the default, false to leave unchanged
  488.      *
  489.      * @return \DateTime|\DateTimeImmutable
  490.      */
  491.     public function convertDate($date null$timezone null)
  492.     {
  493.         // determine the timezone
  494.         if (false !== $timezone) {
  495.             if (null === $timezone) {
  496.                 $timezone $this->getTimezone();
  497.             } elseif (!$timezone instanceof \DateTimeZone) {
  498.                 $timezone = new \DateTimeZone($timezone);
  499.             }
  500.         }
  501.         // immutable dates
  502.         if ($date instanceof \DateTimeImmutable) {
  503.             return false !== $timezone $date->setTimezone($timezone) : $date;
  504.         }
  505.         if ($date instanceof \DateTime) {
  506.             $date = clone $date;
  507.             if (false !== $timezone) {
  508.                 $date->setTimezone($timezone);
  509.             }
  510.             return $date;
  511.         }
  512.         if (null === $date || 'now' === $date) {
  513.             if (null === $date) {
  514.                 $date 'now';
  515.             }
  516.             return new \DateTime($datefalse !== $timezone $timezone $this->getTimezone());
  517.         }
  518.         $asString = (string) $date;
  519.         if (ctype_digit($asString) || ('' !== $asString && '-' === $asString[0] && ctype_digit(substr($asString1)))) {
  520.             $date = new \DateTime('@'.$date);
  521.         } else {
  522.             $date = new \DateTime($date$this->getTimezone());
  523.         }
  524.         if (false !== $timezone) {
  525.             $date->setTimezone($timezone);
  526.         }
  527.         return $date;
  528.     }
  529.     /**
  530.      * Replaces strings within a string.
  531.      *
  532.      * @param string|null        $str  String to replace in
  533.      * @param array|\Traversable $from Replace values
  534.      *
  535.      * @internal
  536.      */
  537.     public static function replace($str$from): string
  538.     {
  539.         if (!is_iterable($from)) {
  540.             throw new RuntimeError(\sprintf('The "replace" filter expects a sequence or a mapping, got "%s".'get_debug_type($from)));
  541.         }
  542.         return strtr($str ?? ''self::toArray($from));
  543.     }
  544.     /**
  545.      * Rounds a number.
  546.      *
  547.      * @param int|float|string|null   $value     The value to round
  548.      * @param int|float               $precision The rounding precision
  549.      * @param 'common'|'ceil'|'floor' $method    The method to use for rounding
  550.      *
  551.      * @return float The rounded number
  552.      *
  553.      * @internal
  554.      */
  555.     public static function round($value$precision 0$method 'common')
  556.     {
  557.         $value = (float) $value;
  558.         if ('common' === $method) {
  559.             return round($value$precision);
  560.         }
  561.         if ('ceil' !== $method && 'floor' !== $method) {
  562.             throw new RuntimeError('The "round" filter only supports the "common", "ceil", and "floor" methods.');
  563.         }
  564.         return $method($value 10 ** $precision) / 10 ** $precision;
  565.     }
  566.     /**
  567.      * Formats a number.
  568.      *
  569.      * All of the formatting options can be left null, in that case the defaults will
  570.      * be used. Supplying any of the parameters will override the defaults set in the
  571.      * environment object.
  572.      *
  573.      * @param mixed       $number       A float/int/string of the number to format
  574.      * @param int|null    $decimal      the number of decimal points to display
  575.      * @param string|null $decimalPoint the character(s) to use for the decimal point
  576.      * @param string|null $thousandSep  the character(s) to use for the thousands separator
  577.      */
  578.     public function formatNumber($number$decimal null$decimalPoint null$thousandSep null): string
  579.     {
  580.         $defaults $this->getNumberFormat();
  581.         if (null === $decimal) {
  582.             $decimal $defaults[0];
  583.         }
  584.         if (null === $decimalPoint) {
  585.             $decimalPoint $defaults[1];
  586.         }
  587.         if (null === $thousandSep) {
  588.             $thousandSep $defaults[2];
  589.         }
  590.         return number_format((float) $number$decimal$decimalPoint$thousandSep);
  591.     }
  592.     /**
  593.      * URL encodes (RFC 3986) a string as a path segment or an array as a query string.
  594.      *
  595.      * @param string|array|null $url A URL or an array of query parameters
  596.      *
  597.      * @internal
  598.      */
  599.     public static function urlencode($url): string
  600.     {
  601.         if (\is_array($url)) {
  602.             return http_build_query($url'''&'\PHP_QUERY_RFC3986);
  603.         }
  604.         return rawurlencode($url ?? '');
  605.     }
  606.     /**
  607.      * Merges any number of arrays or Traversable objects.
  608.      *
  609.      *  {% set items = { 'apple': 'fruit', 'orange': 'fruit' } %}
  610.      *
  611.      *  {% set items = items|merge({ 'peugeot': 'car' }, { 'banana': 'fruit' }) %}
  612.      *
  613.      *  {# items now contains { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'car', 'banana': 'fruit' } #}
  614.      *
  615.      * @param array|\Traversable ...$arrays Any number of arrays or Traversable objects to merge
  616.      *
  617.      * @internal
  618.      */
  619.     public static function merge(...$arrays): array
  620.     {
  621.         $result = [];
  622.         foreach ($arrays as $argNumber => $array) {
  623.             if (!is_iterable($array)) {
  624.                 throw new RuntimeError(\sprintf('The "merge" filter expects a sequence or a mapping, got "%s" for argument %d.'get_debug_type($array), $argNumber 1));
  625.             }
  626.             $result array_merge($resultself::toArray($array));
  627.         }
  628.         return $result;
  629.     }
  630.     /**
  631.      * Slices a variable.
  632.      *
  633.      * @param mixed $item         A variable
  634.      * @param int   $start        Start of the slice
  635.      * @param int   $length       Size of the slice
  636.      * @param bool  $preserveKeys Whether to preserve key or not (when the input is an array)
  637.      *
  638.      * @return mixed The sliced variable
  639.      *
  640.      * @internal
  641.      */
  642.     public static function slice(string $charset$item$start$length null$preserveKeys false)
  643.     {
  644.         if ($item instanceof \Traversable) {
  645.             while ($item instanceof \IteratorAggregate) {
  646.                 $item $item->getIterator();
  647.             }
  648.             if ($start >= && $length >= && $item instanceof \Iterator) {
  649.                 try {
  650.                     return iterator_to_array(new \LimitIterator($item$start$length ?? -1), $preserveKeys);
  651.                 } catch (\OutOfBoundsException $e) {
  652.                     return [];
  653.                 }
  654.             }
  655.             $item iterator_to_array($item$preserveKeys);
  656.         }
  657.         if (\is_array($item)) {
  658.             return \array_slice($item$start$length$preserveKeys);
  659.         }
  660.         return mb_substr((string) $item$start$length$charset);
  661.     }
  662.     /**
  663.      * Returns the first element of the item.
  664.      *
  665.      * @param mixed $item A variable
  666.      *
  667.      * @return mixed The first element of the item
  668.      *
  669.      * @internal
  670.      */
  671.     public static function first(string $charset$item)
  672.     {
  673.         $elements self::slice($charset$item01false);
  674.         return \is_string($elements) ? $elements current($elements);
  675.     }
  676.     /**
  677.      * Returns the last element of the item.
  678.      *
  679.      * @param mixed $item A variable
  680.      *
  681.      * @return mixed The last element of the item
  682.      *
  683.      * @internal
  684.      */
  685.     public static function last(string $charset$item)
  686.     {
  687.         $elements self::slice($charset$item, -11false);
  688.         return \is_string($elements) ? $elements current($elements);
  689.     }
  690.     /**
  691.      * Joins the values to a string.
  692.      *
  693.      * The separators between elements are empty strings per default, you can define them with the optional parameters.
  694.      *
  695.      *  {{ [1, 2, 3]|join(', ', ' and ') }}
  696.      *  {# returns 1, 2 and 3 #}
  697.      *
  698.      *  {{ [1, 2, 3]|join('|') }}
  699.      *  {# returns 1|2|3 #}
  700.      *
  701.      *  {{ [1, 2, 3]|join }}
  702.      *  {# returns 123 #}
  703.      *
  704.      * @param iterable|array|string|float|int|bool|null $value An array
  705.      * @param string                                    $glue  The separator
  706.      * @param string|null                               $and   The separator for the last pair
  707.      *
  708.      * @internal
  709.      */
  710.     public static function join($value$glue ''$and null): string
  711.     {
  712.         if (!is_iterable($value)) {
  713.             $value = (array) $value;
  714.         }
  715.         $value self::toArray($valuefalse);
  716.         if (=== \count($value)) {
  717.             return '';
  718.         }
  719.         if (null === $and || $and === $glue) {
  720.             return implode($glue$value);
  721.         }
  722.         if (=== \count($value)) {
  723.             return $value[0];
  724.         }
  725.         return implode($glue\array_slice($value0, -1)).$and.$value[\count($value) - 1];
  726.     }
  727.     /**
  728.      * Splits the string into an array.
  729.      *
  730.      *  {{ "one,two,three"|split(',') }}
  731.      *  {# returns [one, two, three] #}
  732.      *
  733.      *  {{ "one,two,three,four,five"|split(',', 3) }}
  734.      *  {# returns [one, two, "three,four,five"] #}
  735.      *
  736.      *  {{ "123"|split('') }}
  737.      *  {# returns [1, 2, 3] #}
  738.      *
  739.      *  {{ "aabbcc"|split('', 2) }}
  740.      *  {# returns [aa, bb, cc] #}
  741.      *
  742.      * @param string|null $value     A string
  743.      * @param string      $delimiter The delimiter
  744.      * @param int|null    $limit     The limit
  745.      *
  746.      * @internal
  747.      */
  748.     public static function split(string $charset$value$delimiter$limit null): array
  749.     {
  750.         $value $value ?? '';
  751.         if ('' !== $delimiter) {
  752.             return null === $limit explode($delimiter$value) : explode($delimiter$value$limit);
  753.         }
  754.         if ($limit <= 1) {
  755.             return preg_split('/(?<!^)(?!$)/u'$value);
  756.         }
  757.         $length mb_strlen($value$charset);
  758.         if ($length $limit) {
  759.             return [$value];
  760.         }
  761.         $r = [];
  762.         for ($i 0$i $length$i += $limit) {
  763.             $r[] = mb_substr($value$i$limit$charset);
  764.         }
  765.         return $r;
  766.     }
  767.     /**
  768.      * @internal
  769.      */
  770.     public static function default($value$default '')
  771.     {
  772.         if (self::testEmpty($value)) {
  773.             return $default;
  774.         }
  775.         return $value;
  776.     }
  777.     /**
  778.      * Returns the keys for the given array.
  779.      *
  780.      * It is useful when you want to iterate over the keys of an array:
  781.      *
  782.      *  {% for key in array|keys %}
  783.      *      {# ... #}
  784.      *  {% endfor %}
  785.      *
  786.      * @internal
  787.      */
  788.     public static function keys($array): array
  789.     {
  790.         if ($array instanceof \Traversable) {
  791.             while ($array instanceof \IteratorAggregate) {
  792.                 $array $array->getIterator();
  793.             }
  794.             $keys = [];
  795.             if ($array instanceof \Iterator) {
  796.                 $array->rewind();
  797.                 while ($array->valid()) {
  798.                     $keys[] = $array->key();
  799.                     $array->next();
  800.                 }
  801.                 return $keys;
  802.             }
  803.             foreach ($array as $key => $item) {
  804.                 $keys[] = $key;
  805.             }
  806.             return $keys;
  807.         }
  808.         if (!\is_array($array)) {
  809.             return [];
  810.         }
  811.         return array_keys($array);
  812.     }
  813.     /**
  814.      * Reverses a variable.
  815.      *
  816.      * @param array|\Traversable|string|null $item         An array, a \Traversable instance, or a string
  817.      * @param bool                           $preserveKeys Whether to preserve key or not
  818.      *
  819.      * @return mixed The reversed input
  820.      *
  821.      * @internal
  822.      */
  823.     public static function reverse(string $charset$item$preserveKeys false)
  824.     {
  825.         if ($item instanceof \Traversable) {
  826.             return array_reverse(iterator_to_array($item), $preserveKeys);
  827.         }
  828.         if (\is_array($item)) {
  829.             return array_reverse($item$preserveKeys);
  830.         }
  831.         $string = (string) $item;
  832.         if ('UTF-8' !== $charset) {
  833.             $string self::convertEncoding($string'UTF-8'$charset);
  834.         }
  835.         preg_match_all('/./us'$string$matches);
  836.         $string implode(''array_reverse($matches[0]));
  837.         if ('UTF-8' !== $charset) {
  838.             $string self::convertEncoding($string$charset'UTF-8');
  839.         }
  840.         return $string;
  841.     }
  842.     /**
  843.      * Shuffles an array, a \Traversable instance, or a string.
  844.      * The function does not preserve keys.
  845.      *
  846.      * @param array|\Traversable|string|null $item
  847.      *
  848.      * @return mixed
  849.      *
  850.      * @internal
  851.      */
  852.     public static function shuffle(string $charset$item)
  853.     {
  854.         if (\is_string($item)) {
  855.             if ('UTF-8' !== $charset) {
  856.                 $item self::convertEncoding($item'UTF-8'$charset);
  857.             }
  858.             $item preg_split('/(?<!^)(?!$)/u'$item, -1);
  859.             shuffle($item);
  860.             $item implode(''$item);
  861.             if ('UTF-8' !== $charset) {
  862.                 $item self::convertEncoding($item$charset'UTF-8');
  863.             }
  864.             return $item;
  865.         }
  866.         if (is_iterable($item)) {
  867.             $item self::toArray($itemfalse);
  868.             shuffle($item);
  869.         }
  870.         return $item;
  871.     }
  872.     /**
  873.      * Sorts an array.
  874.      *
  875.      * @param array|\Traversable $array
  876.      * @param ?\Closure          $arrow
  877.      *
  878.      * @internal
  879.      */
  880.     public static function sort(Environment $env$array$arrow null): array
  881.     {
  882.         if ($array instanceof \Traversable) {
  883.             $array iterator_to_array($array);
  884.         } elseif (!\is_array($array)) {
  885.             throw new RuntimeError(\sprintf('The "sort" filter expects a sequence or a mapping, got "%s".'get_debug_type($array)));
  886.         }
  887.         if (null !== $arrow) {
  888.             self::checkArrow($env$arrow'sort''filter');
  889.             uasort($array$arrow);
  890.         } else {
  891.             asort($array);
  892.         }
  893.         return $array;
  894.     }
  895.     /**
  896.      * @internal
  897.      */
  898.     public static function inFilter($value$compare)
  899.     {
  900.         if ($value instanceof Markup) {
  901.             $value = (string) $value;
  902.         }
  903.         if ($compare instanceof Markup) {
  904.             $compare = (string) $compare;
  905.         }
  906.         if (\is_string($compare)) {
  907.             if (\is_string($value) || \is_int($value) || \is_float($value)) {
  908.                 return '' === $value || str_contains($compare, (string) $value);
  909.             }
  910.             return false;
  911.         }
  912.         if (!is_iterable($compare)) {
  913.             return false;
  914.         }
  915.         if (\is_object($value) || \is_resource($value)) {
  916.             if (!\is_array($compare)) {
  917.                 foreach ($compare as $item) {
  918.                     if ($item === $value) {
  919.                         return true;
  920.                     }
  921.                 }
  922.                 return false;
  923.             }
  924.             return \in_array($value$comparetrue);
  925.         }
  926.         foreach ($compare as $item) {
  927.             if (=== self::compare($value$item)) {
  928.                 return true;
  929.             }
  930.         }
  931.         return false;
  932.     }
  933.     /**
  934.      * Compares two values using a more strict version of the PHP non-strict comparison operator.
  935.      *
  936.      * @see https://wiki.php.net/rfc/string_to_number_comparison
  937.      * @see https://wiki.php.net/rfc/trailing_whitespace_numerics
  938.      *
  939.      * @internal
  940.      */
  941.     public static function compare($a$b)
  942.     {
  943.         // int <=> string
  944.         if (\is_int($a) && \is_string($b)) {
  945.             $bTrim trim($b" \t\n\r\v\f");
  946.             if (!is_numeric($bTrim)) {
  947.                 return (string) $a <=> $b;
  948.             }
  949.             if ((int) $bTrim == $bTrim) {
  950.                 return $a <=> (int) $bTrim;
  951.             } else {
  952.                 return (float) $a <=> (float) $bTrim;
  953.             }
  954.         }
  955.         if (\is_string($a) && \is_int($b)) {
  956.             $aTrim trim($a" \t\n\r\v\f");
  957.             if (!is_numeric($aTrim)) {
  958.                 return $a <=> (string) $b;
  959.             }
  960.             if ((int) $aTrim == $aTrim) {
  961.                 return (int) $aTrim <=> $b;
  962.             } else {
  963.                 return (float) $aTrim <=> (float) $b;
  964.             }
  965.         }
  966.         // float <=> string
  967.         if (\is_float($a) && \is_string($b)) {
  968.             if (is_nan($a)) {
  969.                 return 1;
  970.             }
  971.             $bTrim trim($b" \t\n\r\v\f");
  972.             if (!is_numeric($bTrim)) {
  973.                 return (string) $a <=> $b;
  974.             }
  975.             return $a <=> (float) $bTrim;
  976.         }
  977.         if (\is_string($a) && \is_float($b)) {
  978.             if (is_nan($b)) {
  979.                 return 1;
  980.             }
  981.             $aTrim trim($a" \t\n\r\v\f");
  982.             if (!is_numeric($aTrim)) {
  983.                 return $a <=> (string) $b;
  984.             }
  985.             return (float) $aTrim <=> $b;
  986.         }
  987.         // fallback to <=>
  988.         return $a <=> $b;
  989.     }
  990.     /**
  991.      * @throws RuntimeError When an invalid pattern is used
  992.      *
  993.      * @internal
  994.      */
  995.     public static function matches(string $regexp, ?string $str): int
  996.     {
  997.         set_error_handler(function ($t$m) use ($regexp) {
  998.             throw new RuntimeError(\sprintf('Regexp "%s" passed to "matches" is not valid'$regexp).substr($m12));
  999.         });
  1000.         try {
  1001.             return preg_match($regexp$str ?? '');
  1002.         } finally {
  1003.             restore_error_handler();
  1004.         }
  1005.     }
  1006.     /**
  1007.      * Returns a trimmed string.
  1008.      *
  1009.      * @param string|\Stringable|null $string
  1010.      * @param string|null             $characterMask
  1011.      * @param string                  $side          left, right, or both
  1012.      *
  1013.      * @throws RuntimeError When an invalid trimming side is used
  1014.      *
  1015.      * @internal
  1016.      */
  1017.     public static function trim($string$characterMask null$side 'both'): string|\Stringable
  1018.     {
  1019.         if (null === $characterMask) {
  1020.             $characterMask self::DEFAULT_TRIM_CHARS;
  1021.         }
  1022.         $trimmed = match ($side) {
  1023.             'both' => trim($string ?? ''$characterMask),
  1024.             'left' => ltrim($string ?? ''$characterMask),
  1025.             'right' => rtrim($string ?? ''$characterMask),
  1026.             default => throw new RuntimeError('Trimming side must be "left", "right" or "both".'),
  1027.         };
  1028.         // trimming a safe string with the default character mask always returns a safe string (independently of the context)
  1029.         return $string instanceof Markup && self::DEFAULT_TRIM_CHARS === $characterMask ? new Markup($trimmed$string->getCharset()) : $trimmed;
  1030.     }
  1031.     /**
  1032.      * Inserts HTML line breaks before all newlines in a string.
  1033.      *
  1034.      * @param string|null $string
  1035.      *
  1036.      * @internal
  1037.      */
  1038.     public static function nl2br($string): string
  1039.     {
  1040.         return nl2br($string ?? '');
  1041.     }
  1042.     /**
  1043.      * Removes whitespaces between HTML tags.
  1044.      *
  1045.      * @param string|null $content
  1046.      *
  1047.      * @internal
  1048.      */
  1049.     public static function spaceless($content): string
  1050.     {
  1051.         return trim(preg_replace('/>\s+</''><'$content ?? ''));
  1052.     }
  1053.     /**
  1054.      * @param string|null $string
  1055.      * @param string      $to
  1056.      * @param string      $from
  1057.      *
  1058.      * @internal
  1059.      */
  1060.     public static function convertEncoding($string$to$from): string
  1061.     {
  1062.         if (!\function_exists('iconv')) {
  1063.             throw new RuntimeError('Unable to convert encoding: required function iconv() does not exist. You should install ext-iconv or symfony/polyfill-iconv.');
  1064.         }
  1065.         return iconv($from$to$string ?? '');
  1066.     }
  1067.     /**
  1068.      * Returns the length of a variable.
  1069.      *
  1070.      * @param mixed $thing A variable
  1071.      *
  1072.      * @internal
  1073.      */
  1074.     public static function length(string $charset$thing): int
  1075.     {
  1076.         if (null === $thing) {
  1077.             return 0;
  1078.         }
  1079.         if (\is_scalar($thing)) {
  1080.             return mb_strlen($thing$charset);
  1081.         }
  1082.         if ($thing instanceof \Countable || \is_array($thing) || $thing instanceof \SimpleXMLElement) {
  1083.             return \count($thing);
  1084.         }
  1085.         if ($thing instanceof \Traversable) {
  1086.             return iterator_count($thing);
  1087.         }
  1088.         if ($thing instanceof \Stringable) {
  1089.             return mb_strlen((string) $thing$charset);
  1090.         }
  1091.         return 1;
  1092.     }
  1093.     /**
  1094.      * Converts a string to uppercase.
  1095.      *
  1096.      * @param string|null $string A string
  1097.      *
  1098.      * @internal
  1099.      */
  1100.     public static function upper(string $charset$string): string
  1101.     {
  1102.         return mb_strtoupper($string ?? ''$charset);
  1103.     }
  1104.     /**
  1105.      * Converts a string to lowercase.
  1106.      *
  1107.      * @param string|null $string A string
  1108.      *
  1109.      * @internal
  1110.      */
  1111.     public static function lower(string $charset$string): string
  1112.     {
  1113.         return mb_strtolower($string ?? ''$charset);
  1114.     }
  1115.     /**
  1116.      * Strips HTML and PHP tags from a string.
  1117.      *
  1118.      * @param string|null          $string
  1119.      * @param string[]|string|null $allowable_tags
  1120.      *
  1121.      * @internal
  1122.      */
  1123.     public static function striptags($string$allowable_tags null): string
  1124.     {
  1125.         return strip_tags($string ?? ''$allowable_tags);
  1126.     }
  1127.     /**
  1128.      * Returns a titlecased string.
  1129.      *
  1130.      * @param string|null $string A string
  1131.      *
  1132.      * @internal
  1133.      */
  1134.     public static function titleCase(string $charset$string): string
  1135.     {
  1136.         return mb_convert_case($string ?? ''\MB_CASE_TITLE$charset);
  1137.     }
  1138.     /**
  1139.      * Returns a capitalized string.
  1140.      *
  1141.      * @param string|null $string A string
  1142.      *
  1143.      * @internal
  1144.      */
  1145.     public static function capitalize(string $charset$string): string
  1146.     {
  1147.         return mb_strtoupper(mb_substr($string ?? ''01$charset), $charset).mb_strtolower(mb_substr($string ?? ''1null$charset), $charset);
  1148.     }
  1149.     /**
  1150.      * @internal
  1151.      *
  1152.      * to be removed in 4.0
  1153.      */
  1154.     public static function callMacro(Template $templatestring $method, array $argsint $lineno, array $contextSource $source)
  1155.     {
  1156.         if (!method_exists($template$method)) {
  1157.             $parent $template;
  1158.             while ($parent $parent->getParent($context)) {
  1159.                 if (method_exists($parent$method)) {
  1160.                     return $parent->$method(...$args);
  1161.                 }
  1162.             }
  1163.             throw new RuntimeError(\sprintf('Macro "%s" is not defined in template "%s".'substr($method\strlen('macro_')), $template->getTemplateName()), $lineno$source);
  1164.         }
  1165.         return $template->$method(...$args);
  1166.     }
  1167.     /**
  1168.      * @template TSequence
  1169.      *
  1170.      * @param TSequence $seq
  1171.      *
  1172.      * @return ($seq is iterable ? TSequence : array{})
  1173.      *
  1174.      * @internal
  1175.      */
  1176.     public static function ensureTraversable($seq)
  1177.     {
  1178.         if (is_iterable($seq)) {
  1179.             return $seq;
  1180.         }
  1181.         return [];
  1182.     }
  1183.     /**
  1184.      * @internal
  1185.      */
  1186.     public static function toArray($seq$preserveKeys true)
  1187.     {
  1188.         if ($seq instanceof \Traversable) {
  1189.             return iterator_to_array($seq$preserveKeys);
  1190.         }
  1191.         if (!\is_array($seq)) {
  1192.             return $seq;
  1193.         }
  1194.         return $preserveKeys $seq array_values($seq);
  1195.     }
  1196.     /**
  1197.      * Checks if a variable is empty.
  1198.      *
  1199.      *    {# evaluates to true if the foo variable is null, false, or the empty string #}
  1200.      *    {% if foo is empty %}
  1201.      *        {# ... #}
  1202.      *    {% endif %}
  1203.      *
  1204.      * @param mixed $value A variable
  1205.      *
  1206.      * @internal
  1207.      */
  1208.     public static function testEmpty($value): bool
  1209.     {
  1210.         if ($value instanceof \Countable) {
  1211.             return === \count($value);
  1212.         }
  1213.         if ($value instanceof \Traversable) {
  1214.             return !iterator_count($value);
  1215.         }
  1216.         if ($value instanceof \Stringable) {
  1217.             return '' === (string) $value;
  1218.         }
  1219.         return '' === $value || false === $value || null === $value || [] === $value;
  1220.     }
  1221.     /**
  1222.      * Checks if a variable is a sequence.
  1223.      *
  1224.      *    {# evaluates to true if the foo variable is a sequence #}
  1225.      *    {% if foo is sequence %}
  1226.      *        {# ... #}
  1227.      *    {% endif %}
  1228.      *
  1229.      * @param mixed $value
  1230.      *
  1231.      * @internal
  1232.      */
  1233.     public static function testSequence($value): bool
  1234.     {
  1235.         if ($value instanceof \ArrayObject) {
  1236.             $value $value->getArrayCopy();
  1237.         }
  1238.         if ($value instanceof \Traversable) {
  1239.             $value iterator_to_array($value);
  1240.         }
  1241.         return \is_array($value) && array_is_list($value);
  1242.     }
  1243.     /**
  1244.      * Checks if a variable is a mapping.
  1245.      *
  1246.      *    {# evaluates to true if the foo variable is a mapping #}
  1247.      *    {% if foo is mapping %}
  1248.      *        {# ... #}
  1249.      *    {% endif %}
  1250.      *
  1251.      * @param mixed $value
  1252.      *
  1253.      * @internal
  1254.      */
  1255.     public static function testMapping($value): bool
  1256.     {
  1257.         if ($value instanceof \ArrayObject) {
  1258.             $value $value->getArrayCopy();
  1259.         }
  1260.         if ($value instanceof \Traversable) {
  1261.             $value iterator_to_array($value);
  1262.         }
  1263.         return (\is_array($value) && !array_is_list($value)) || \is_object($value);
  1264.     }
  1265.     /**
  1266.      * Renders a template.
  1267.      *
  1268.      * @param array                        $context
  1269.      * @param string|array|TemplateWrapper $template      The template to render or an array of templates to try consecutively
  1270.      * @param array                        $variables     The variables to pass to the template
  1271.      * @param bool                         $withContext
  1272.      * @param bool                         $ignoreMissing Whether to ignore missing templates or not
  1273.      * @param bool                         $sandboxed     Whether to sandbox the template or not
  1274.      *
  1275.      * @internal
  1276.      */
  1277.     public static function include(Environment $env$context$template$variables = [], $withContext true$ignoreMissing false$sandboxed false): string
  1278.     {
  1279.         $alreadySandboxed false;
  1280.         $sandbox null;
  1281.         if ($withContext) {
  1282.             $variables array_merge($context$variables);
  1283.         }
  1284.         if ($isSandboxed $sandboxed && $env->hasExtension(SandboxExtension::class)) {
  1285.             $sandbox $env->getExtension(SandboxExtension::class);
  1286.             if (!$alreadySandboxed $sandbox->isSandboxed()) {
  1287.                 $sandbox->enableSandbox();
  1288.             }
  1289.         }
  1290.         try {
  1291.             $loaded null;
  1292.             try {
  1293.                 $loaded $env->resolveTemplate($template);
  1294.             } catch (LoaderError $e) {
  1295.                 if (!$ignoreMissing) {
  1296.                     throw $e;
  1297.                 }
  1298.                 return '';
  1299.             }
  1300.             if ($isSandboxed) {
  1301.                 $loaded->unwrap()->checkSecurity();
  1302.             }
  1303.             return $loaded->render($variables);
  1304.         } finally {
  1305.             if ($isSandboxed && !$alreadySandboxed) {
  1306.                 $sandbox->disableSandbox();
  1307.             }
  1308.         }
  1309.     }
  1310.     /**
  1311.      * Returns a template content without rendering it.
  1312.      *
  1313.      * @param string $name          The template name
  1314.      * @param bool   $ignoreMissing Whether to ignore missing templates or not
  1315.      *
  1316.      * @internal
  1317.      */
  1318.     public static function source(Environment $env$name$ignoreMissing false): string
  1319.     {
  1320.         $loader $env->getLoader();
  1321.         try {
  1322.             return $loader->getSourceContext($name)->getCode();
  1323.         } catch (LoaderError $e) {
  1324.             if (!$ignoreMissing) {
  1325.                 throw $e;
  1326.             }
  1327.             return '';
  1328.         }
  1329.     }
  1330.     /**
  1331.      * Returns the list of cases of the enum.
  1332.      *
  1333.      * @template T of \UnitEnum
  1334.      *
  1335.      * @param class-string<T> $enum
  1336.      *
  1337.      * @return list<T>
  1338.      *
  1339.      * @internal
  1340.      */
  1341.     public static function enumCases(string $enum): array
  1342.     {
  1343.         if (!enum_exists($enum)) {
  1344.             throw new RuntimeError(\sprintf('Enum "%s" does not exist.'$enum));
  1345.         }
  1346.         return $enum::cases();
  1347.     }
  1348.     /**
  1349.      * Provides the ability to access enums by their class names.
  1350.      *
  1351.      * @template T of \UnitEnum
  1352.      *
  1353.      * @param class-string<T> $enum
  1354.      *
  1355.      * @return T
  1356.      *
  1357.      * @internal
  1358.      */
  1359.     public static function enum(string $enum): \UnitEnum
  1360.     {
  1361.         if (!enum_exists($enum)) {
  1362.             throw new RuntimeError(\sprintf('"%s" is not an enum.'$enum));
  1363.         }
  1364.         if (!$cases $enum::cases()) {
  1365.             throw new RuntimeError(\sprintf('"%s" is an empty enum.'$enum));
  1366.         }
  1367.         return $cases[0];
  1368.     }
  1369.     /**
  1370.      * Provides the ability to get constants from instances as well as class/global constants.
  1371.      *
  1372.      * @param string      $constant     The name of the constant
  1373.      * @param object|null $object       The object to get the constant from
  1374.      * @param bool        $checkDefined Whether to check if the constant is defined or not
  1375.      *
  1376.      * @return mixed Class constants can return many types like scalars, arrays, and
  1377.      *               objects depending on the PHP version (\BackedEnum, \UnitEnum, etc.)
  1378.      *               When $checkDefined is true, returns true when the constant is defined, false otherwise
  1379.      *
  1380.      * @internal
  1381.      */
  1382.     public static function constant($constant$object nullbool $checkDefined false)
  1383.     {
  1384.         if (null !== $object) {
  1385.             if ('class' === $constant) {
  1386.                 return $checkDefined true \get_class($object);
  1387.             }
  1388.             $constant \get_class($object).'::'.$constant;
  1389.         }
  1390.         if (!\defined($constant)) {
  1391.             if ($checkDefined) {
  1392.                 return false;
  1393.             }
  1394.             if ('::class' === strtolower(substr($constant, -7))) {
  1395.                 throw new RuntimeError(\sprintf('You cannot use the Twig function "constant" to access "%s". You could provide an object and call constant("class", $object) or use the class name directly as a string.'$constant));
  1396.             }
  1397.             throw new RuntimeError(\sprintf('Constant "%s" is undefined.'$constant));
  1398.         }
  1399.         return $checkDefined true \constant($constant);
  1400.     }
  1401.     /**
  1402.      * Batches item.
  1403.      *
  1404.      * @param array $items An array of items
  1405.      * @param int   $size  The size of the batch
  1406.      * @param mixed $fill  A value used to fill missing items
  1407.      *
  1408.      * @internal
  1409.      */
  1410.     public static function batch($items$size$fill null$preserveKeys true): array
  1411.     {
  1412.         if (!is_iterable($items)) {
  1413.             throw new RuntimeError(\sprintf('The "batch" filter expects a sequence or a mapping, got "%s".'get_debug_type($items)));
  1414.         }
  1415.         $size = (int) ceil($size);
  1416.         $result array_chunk(self::toArray($items$preserveKeys), $size$preserveKeys);
  1417.         if (null !== $fill && $result) {
  1418.             $last \count($result) - 1;
  1419.             if ($fillCount $size \count($result[$last])) {
  1420.                 for ($i 0$i $fillCount; ++$i) {
  1421.                     $result[$last][] = $fill;
  1422.                 }
  1423.             }
  1424.         }
  1425.         return $result;
  1426.     }
  1427.     /**
  1428.      * Returns the attribute value for a given array/object.
  1429.      *
  1430.      * @param mixed  $object            The object or array from where to get the item
  1431.      * @param mixed  $item              The item to get from the array or object
  1432.      * @param array  $arguments         An array of arguments to pass if the item is an object method
  1433.      * @param string $type              The type of attribute (@see \Twig\Template constants)
  1434.      * @param bool   $isDefinedTest     Whether this is only a defined check
  1435.      * @param bool   $ignoreStrictCheck Whether to ignore the strict attribute check or not
  1436.      * @param int    $lineno            The template line where the attribute was called
  1437.      *
  1438.      * @return mixed The attribute value, or a Boolean when $isDefinedTest is true, or null when the attribute is not set and $ignoreStrictCheck is true
  1439.      *
  1440.      * @throws RuntimeError if the attribute does not exist and Twig is running in strict mode and $isDefinedTest is false
  1441.      *
  1442.      * @internal
  1443.      */
  1444.     public static function getAttribute(Environment $envSource $source$object$item, array $arguments = [], $type Template::ANY_CALL$isDefinedTest false$ignoreStrictCheck false$sandboxed falseint $lineno = -1)
  1445.     {
  1446.         $propertyNotAllowedError null;
  1447.         // array
  1448.         if (Template::METHOD_CALL !== $type) {
  1449.             $arrayItem \is_bool($item) || \is_float($item) ? (int) $item $item;
  1450.             if ($sandboxed && $object instanceof \ArrayAccess && !\in_array($object::class, self::ARRAY_LIKE_CLASSEStrue)) {
  1451.                 try {
  1452.                     $env->getExtension(SandboxExtension::class)->checkPropertyAllowed($object$arrayItem$lineno$source);
  1453.                 } catch (SecurityNotAllowedPropertyError $propertyNotAllowedError) {
  1454.                     goto methodCheck;
  1455.                 }
  1456.             }
  1457.             if (match (true) {
  1458.                 \is_array($object) => \array_key_exists($arrayItem$object),
  1459.                 $object instanceof \ArrayAccess => $object->offsetExists($arrayItem),
  1460.                 default => false,
  1461.             }) {
  1462.                 if ($isDefinedTest) {
  1463.                     return true;
  1464.                 }
  1465.                 return $object[$arrayItem];
  1466.             }
  1467.             if (Template::ARRAY_CALL === $type || !\is_object($object)) {
  1468.                 if ($isDefinedTest) {
  1469.                     return false;
  1470.                 }
  1471.                 if ($ignoreStrictCheck || !$env->isStrictVariables()) {
  1472.                     return;
  1473.                 }
  1474.                 if ($object instanceof \ArrayAccess) {
  1475.                     $message \sprintf('Key "%s" in object with ArrayAccess of class "%s" does not exist.'$arrayItem\get_class($object));
  1476.                 } elseif (\is_object($object)) {
  1477.                     $message \sprintf('Impossible to access a key "%s" on an object of class "%s" that does not implement ArrayAccess interface.'$item\get_class($object));
  1478.                 } elseif (\is_array($object)) {
  1479.                     if (!$object) {
  1480.                         $message \sprintf('Key "%s" does not exist as the sequence/mapping is empty.'$arrayItem);
  1481.                     } else {
  1482.                         $message \sprintf('Key "%s" for sequence/mapping with keys "%s" does not exist.'$arrayItemimplode(', 'array_keys($object)));
  1483.                     }
  1484.                 } elseif (Template::ARRAY_CALL === $type) {
  1485.                     if (null === $object) {
  1486.                         $message \sprintf('Impossible to access a key ("%s") on a null variable.'$item);
  1487.                     } else {
  1488.                         $message \sprintf('Impossible to access a key ("%s") on a %s variable ("%s").'$itemget_debug_type($object), $object);
  1489.                     }
  1490.                 } elseif (null === $object) {
  1491.                     $message \sprintf('Impossible to access an attribute ("%s") on a null variable.'$item);
  1492.                 } else {
  1493.                     $message \sprintf('Impossible to access an attribute ("%s") on a %s variable ("%s").'$itemget_debug_type($object), $object);
  1494.                 }
  1495.                 throw new RuntimeError($message$lineno$source);
  1496.             }
  1497.         }
  1498.         $item = (string) $item;
  1499.         if (!\is_object($object)) {
  1500.             if ($isDefinedTest) {
  1501.                 return false;
  1502.             }
  1503.             if ($ignoreStrictCheck || !$env->isStrictVariables()) {
  1504.                 return;
  1505.             }
  1506.             if (null === $object) {
  1507.                 $message \sprintf('Impossible to invoke a method ("%s") on a null variable.'$item);
  1508.             } elseif (\is_array($object)) {
  1509.                 $message \sprintf('Impossible to invoke a method ("%s") on a sequence/mapping.'$item);
  1510.             } else {
  1511.                 $message \sprintf('Impossible to invoke a method ("%s") on a %s variable ("%s").'$itemget_debug_type($object), $object);
  1512.             }
  1513.             throw new RuntimeError($message$lineno$source);
  1514.         }
  1515.         if ($object instanceof Template) {
  1516.             throw new RuntimeError('Accessing \Twig\Template attributes is forbidden.'$lineno$source);
  1517.         }
  1518.         // object property
  1519.         if (Template::METHOD_CALL !== $type) {
  1520.             if ($sandboxed) {
  1521.                 try {
  1522.                     $env->getExtension(SandboxExtension::class)->checkPropertyAllowed($object$item$lineno$source);
  1523.                 } catch (SecurityNotAllowedPropertyError $propertyNotAllowedError) {
  1524.                     goto methodCheck;
  1525.                 }
  1526.             }
  1527.             static $propertyCheckers = [];
  1528.             if ($object instanceof \Closure && '__invoke' === $item) {
  1529.                 return $isDefinedTest true $object();
  1530.             }
  1531.             if (isset($object->$item)
  1532.                 || ($propertyCheckers[$object::class][$item] ??= self::getPropertyChecker($object::class, $item))($object$item)
  1533.             ) {
  1534.                 if ($isDefinedTest) {
  1535.                     return true;
  1536.                 }
  1537.                 return $object->$item;
  1538.             }
  1539.             if ($object instanceof \DateTimeInterface && \in_array($item, ['date''timezone''timezone_type'], true)) {
  1540.                 if ($isDefinedTest) {
  1541.                     return true;
  1542.                 }
  1543.                 return ((array) $object)[$item];
  1544.             }
  1545.             if (\defined($object::class.'::'.$item)) {
  1546.                 if ($isDefinedTest) {
  1547.                     return true;
  1548.                 }
  1549.                 return \constant($object::class.'::'.$item);
  1550.             }
  1551.         }
  1552.         methodCheck:
  1553.         static $cache = [];
  1554.         $class \get_class($object);
  1555.         // object method
  1556.         // precedence: getXxx() > isXxx() > hasXxx()
  1557.         if (!isset($cache[$class])) {
  1558.             $methods get_class_methods($object);
  1559.             if ($object instanceof \Closure) {
  1560.                 $methods[] = '__invoke';
  1561.             }
  1562.             sort($methods);
  1563.             $lcMethods array_map('strtolower'$methods);
  1564.             $classCache = [];
  1565.             foreach ($methods as $i => $method) {
  1566.                 $classCache[$method] = $method;
  1567.                 $classCache[$lcName $lcMethods[$i]] = $method;
  1568.                 if ('g' === $lcName[0] && str_starts_with($lcName'get')) {
  1569.                     $name substr($method3);
  1570.                     $lcName substr($lcName3);
  1571.                 } elseif ('i' === $lcName[0] && str_starts_with($lcName'is')) {
  1572.                     $name substr($method2);
  1573.                     $lcName substr($lcName2);
  1574.                 } elseif ('h' === $lcName[0] && str_starts_with($lcName'has')) {
  1575.                     $name substr($method3);
  1576.                     $lcName substr($lcName3);
  1577.                     if (\in_array('is'.$lcName$lcMethods)) {
  1578.                         continue;
  1579.                     }
  1580.                 } else {
  1581.                     continue;
  1582.                 }
  1583.                 // skip get() and is() methods (in which case, $name is empty)
  1584.                 if ($name) {
  1585.                     if (!isset($classCache[$name])) {
  1586.                         $classCache[$name] = $method;
  1587.                     }
  1588.                     if (!isset($classCache[$lcName])) {
  1589.                         $classCache[$lcName] = $method;
  1590.                     }
  1591.                 }
  1592.             }
  1593.             $cache[$class] = $classCache;
  1594.         }
  1595.         $call false;
  1596.         if (isset($cache[$class][$item])) {
  1597.             $method $cache[$class][$item];
  1598.         } elseif (isset($cache[$class][$lcItem strtolower($item)])) {
  1599.             $method $cache[$class][$lcItem];
  1600.         } elseif (isset($cache[$class]['__call'])) {
  1601.             $method $item;
  1602.             $call true;
  1603.         } else {
  1604.             if ($isDefinedTest) {
  1605.                 return false;
  1606.             }
  1607.             if ($propertyNotAllowedError) {
  1608.                 throw $propertyNotAllowedError;
  1609.             }
  1610.             if ($ignoreStrictCheck || !$env->isStrictVariables()) {
  1611.                 return;
  1612.             }
  1613.             throw new RuntimeError(\sprintf('Neither the property "%1$s" nor one of the methods "%1$s()", "get%1$s()"/"is%1$s()"/"has%1$s()" or "__call()" exist and have public access in class "%2$s".'$item$class), $lineno$source);
  1614.         }
  1615.         if ($sandboxed) {
  1616.             try {
  1617.                 $env->getExtension(SandboxExtension::class)->checkMethodAllowed($object$method$lineno$source);
  1618.             } catch (SecurityNotAllowedMethodError $e) {
  1619.                 if ($isDefinedTest) {
  1620.                     return false;
  1621.                 }
  1622.                 if ($propertyNotAllowedError) {
  1623.                     throw $propertyNotAllowedError;
  1624.                 }
  1625.                 throw $e;
  1626.             }
  1627.         }
  1628.         if ($isDefinedTest) {
  1629.             return true;
  1630.         }
  1631.         // Some objects throw exceptions when they have __call, and the method we try
  1632.         // to call is not supported. If ignoreStrictCheck is true, we should return null.
  1633.         try {
  1634.             $ret $object->$method(...$arguments);
  1635.         } catch (\BadMethodCallException $e) {
  1636.             if ($call && ($ignoreStrictCheck || !$env->isStrictVariables())) {
  1637.                 return;
  1638.             }
  1639.             throw $e;
  1640.         }
  1641.         return $ret;
  1642.     }
  1643.     /**
  1644.      * Returns the values from a single column in the input array.
  1645.      *
  1646.      * <pre>
  1647.      *  {% set items = [{ 'fruit' : 'apple'}, {'fruit' : 'orange' }] %}
  1648.      *
  1649.      *  {% set fruits = items|column('fruit') %}
  1650.      *
  1651.      *  {# fruits now contains ['apple', 'orange'] #}
  1652.      * </pre>
  1653.      *
  1654.      * @param array|\Traversable $array An array
  1655.      * @param int|string         $name  The column name
  1656.      * @param int|string|null    $index The column to use as the index/keys for the returned array
  1657.      *
  1658.      * @return array The array of values
  1659.      *
  1660.      * @internal
  1661.      */
  1662.     public static function column($array$name$index null): array
  1663.     {
  1664.         if (!is_iterable($array)) {
  1665.             throw new RuntimeError(\sprintf('The "column" filter expects a sequence or a mapping, got "%s".'get_debug_type($array)));
  1666.         }
  1667.         if ($array instanceof \Traversable) {
  1668.             $array iterator_to_array($array);
  1669.         }
  1670.         return array_column($array$name$index);
  1671.     }
  1672.     /**
  1673.      * @param \Closure $arrow
  1674.      *
  1675.      * @internal
  1676.      */
  1677.     public static function filter(Environment $env$array$arrow)
  1678.     {
  1679.         if (!is_iterable($array)) {
  1680.             throw new RuntimeError(\sprintf('The "filter" filter expects a sequence/mapping or "Traversable", got "%s".'get_debug_type($array)));
  1681.         }
  1682.         self::checkArrow($env$arrow'filter''filter');
  1683.         if (\is_array($array)) {
  1684.             return array_filter($array$arrow\ARRAY_FILTER_USE_BOTH);
  1685.         }
  1686.         // the IteratorIterator wrapping is needed as some internal PHP classes are \Traversable but do not implement \Iterator
  1687.         return new \CallbackFilterIterator(new \IteratorIterator($array), $arrow);
  1688.     }
  1689.     /**
  1690.      * @param \Closure $arrow
  1691.      *
  1692.      * @internal
  1693.      */
  1694.     public static function find(Environment $env$array$arrow)
  1695.     {
  1696.         if (!is_iterable($array)) {
  1697.             throw new RuntimeError(\sprintf('The "find" filter expects a sequence or a mapping, got "%s".'get_debug_type($array)));
  1698.         }
  1699.         self::checkArrow($env$arrow'find''filter');
  1700.         foreach ($array as $k => $v) {
  1701.             if ($arrow($v$k)) {
  1702.                 return $v;
  1703.             }
  1704.         }
  1705.         return null;
  1706.     }
  1707.     /**
  1708.      * @param \Closure $arrow
  1709.      *
  1710.      * @internal
  1711.      */
  1712.     public static function map(Environment $env$array$arrow)
  1713.     {
  1714.         if (!is_iterable($array)) {
  1715.             throw new RuntimeError(\sprintf('The "map" filter expects a sequence or a mapping, got "%s".'get_debug_type($array)));
  1716.         }
  1717.         self::checkArrow($env$arrow'map''filter');
  1718.         $r = [];
  1719.         foreach ($array as $k => $v) {
  1720.             $r[$k] = $arrow($v$k);
  1721.         }
  1722.         return $r;
  1723.     }
  1724.     /**
  1725.      * @param \Closure $arrow
  1726.      *
  1727.      * @internal
  1728.      */
  1729.     public static function reduce(Environment $env$array$arrow$initial null)
  1730.     {
  1731.         if (!is_iterable($array)) {
  1732.             throw new RuntimeError(\sprintf('The "reduce" filter expects a sequence or a mapping, got "%s".'get_debug_type($array)));
  1733.         }
  1734.         self::checkArrow($env$arrow'reduce''filter');
  1735.         $accumulator $initial;
  1736.         foreach ($array as $key => $value) {
  1737.             $accumulator $arrow($accumulator$value$key);
  1738.         }
  1739.         return $accumulator;
  1740.     }
  1741.     /**
  1742.      * @param \Closure $arrow
  1743.      *
  1744.      * @internal
  1745.      */
  1746.     public static function arraySome(Environment $env$array$arrow)
  1747.     {
  1748.         if (!is_iterable($array)) {
  1749.             throw new RuntimeError(\sprintf('The "has some" test expects a sequence or a mapping, got "%s".'get_debug_type($array)));
  1750.         }
  1751.         self::checkArrow($env$arrow'has some''operator');
  1752.         foreach ($array as $k => $v) {
  1753.             if ($arrow($v$k)) {
  1754.                 return true;
  1755.             }
  1756.         }
  1757.         return false;
  1758.     }
  1759.     /**
  1760.      * @param \Closure $arrow
  1761.      *
  1762.      * @internal
  1763.      */
  1764.     public static function arrayEvery(Environment $env$array$arrow)
  1765.     {
  1766.         if (!is_iterable($array)) {
  1767.             throw new RuntimeError(\sprintf('The "has every" test expects a sequence or a mapping, got "%s".'get_debug_type($array)));
  1768.         }
  1769.         self::checkArrow($env$arrow'has every''operator');
  1770.         foreach ($array as $k => $v) {
  1771.             if (!$arrow($v$k)) {
  1772.                 return false;
  1773.             }
  1774.         }
  1775.         return true;
  1776.     }
  1777.     /**
  1778.      * @internal
  1779.      */
  1780.     public static function checkArrow(Environment $env$arrow$thing$type)
  1781.     {
  1782.         if ($arrow instanceof \Closure) {
  1783.             return;
  1784.         }
  1785.         if ($env->hasExtension(SandboxExtension::class) && $env->getExtension(SandboxExtension::class)->isSandboxed()) {
  1786.             throw new RuntimeError(\sprintf('The callable passed to the "%s" %s must be a Closure in sandbox mode.'$thing$type));
  1787.         }
  1788.         trigger_deprecation('twig/twig''3.15''Passing a callable that is not a PHP \Closure as an argument to the "%s" %s is deprecated.'$thing$type);
  1789.     }
  1790.     /**
  1791.      * @internal to be removed in Twig 4
  1792.      */
  1793.     public static function captureOutput(iterable $body): string
  1794.     {
  1795.         $level ob_get_level();
  1796.         ob_start();
  1797.         try {
  1798.             foreach ($body as $data) {
  1799.                 echo $data;
  1800.             }
  1801.         } catch (\Throwable $e) {
  1802.             while (ob_get_level() > $level) {
  1803.                 ob_end_clean();
  1804.             }
  1805.             throw $e;
  1806.         }
  1807.         return ob_get_clean();
  1808.     }
  1809.     /**
  1810.      * @internal
  1811.      */
  1812.     public static function parseParentFunction(Parser $parserNode $fakeNode$argsint $line): AbstractExpression
  1813.     {
  1814.         if (!$blockName $parser->peekBlockStack()) {
  1815.             throw new SyntaxError('Calling the "parent" function outside of a block is forbidden.'$line$parser->getStream()->getSourceContext());
  1816.         }
  1817.         if (!$parser->hasInheritance()) {
  1818.             throw new SyntaxError('Calling the "parent" function on a template that does not call "extends" or "use" is forbidden.'$line$parser->getStream()->getSourceContext());
  1819.         }
  1820.         return new ParentExpression($blockName$line);
  1821.     }
  1822.     /**
  1823.      * @internal
  1824.      */
  1825.     public static function parseBlockFunction(Parser $parserNode $fakeNode$argsint $line): AbstractExpression
  1826.     {
  1827.         $fakeFunction = new TwigFunction('block', fn ($name$template null) => null);
  1828.         $args = (new CallableArgumentsExtractor($fakeNode$fakeFunction))->extractArguments($args);
  1829.         return new BlockReferenceExpression($args[0], $args[1] ?? null$line);
  1830.     }
  1831.     /**
  1832.      * @internal
  1833.      */
  1834.     public static function parseAttributeFunction(Parser $parserNode $fakeNode$argsint $line): AbstractExpression
  1835.     {
  1836.         $fakeFunction = new TwigFunction('attribute', fn ($variable$attribute$arguments null) => null);
  1837.         $args = (new CallableArgumentsExtractor($fakeNode$fakeFunction))->extractArguments($args);
  1838.         /*
  1839.         Deprecation to uncomment sometimes during the lifetime of the 4.x branch
  1840.         $src = $parser->getStream()->getSourceContext();
  1841.         $dep = new DeprecatedCallableInfo('twig/twig', '3.15', 'The "attribute" function is deprecated, use the "." notation instead.');
  1842.         $dep->setName('attribute');
  1843.         $dep->setType('function');
  1844.         $dep->triggerDeprecation($src->getPath() ?: $src->getName(), $line);
  1845.         */
  1846.         return new GetAttrExpression($args[0], $args[1], $args[2] ?? nullTemplate::ANY_CALL$line);
  1847.     }
  1848.     private static function getPropertyChecker(string $classstring $property): \Closure
  1849.     {
  1850.         static $classReflectors = [];
  1851.         $class $classReflectors[$class] ??= new \ReflectionClass($class);
  1852.         if (!$class->hasProperty($property)) {
  1853.             static $propertyExists;
  1854.             return $propertyExists ??= \Closure::fromCallable('property_exists');
  1855.         }
  1856.         $property $class->getProperty($property);
  1857.         if (!$property->isPublic()) {
  1858.             static $false;
  1859.             return $false ??= static fn () => false;
  1860.         }
  1861.         return static fn ($object) => $property->isInitialized($object);
  1862.     }
  1863. }