Merge branch '3.x' into 4.x

* 3.x:
  Fixing minor typo in Update inline_css.rst
  Ignore static properties when using the dot operator
  Fix CS
  Fix constant() behavior when used with ??
  Finish the work
  fix indentation
  Apply suggestions from code review
  typo
  fix merge
  docs and changelog
  typehint instead of checkArrow
  checkArrow, typehints
  invoke filter
This commit is contained in:
Fabien Potencier
2025-01-24 11:32:17 +01:00
19 changed files with 79 additions and 24 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
``inline_css``
==============
The ``inline_css`` filter inline CSS styles in HTML documents:
The ``inline_css`` filter inlines CSS styles in HTML documents:
.. code-block:: html+twig
+16
View File
@@ -0,0 +1,16 @@
``invoke``
==========
.. versionadded:: 3.19
The ``invoke`` filter has been added in Twig 3.19.
The ``invoke`` filter invokes an arrow function with the given arguments:
.. code-block:: twig
{% set person = { first: "Bob", last: "Smith" } %}
{% set func = p => "#{p.first} #{p.last}" %}
{{ func|invoke(person) }}
{# outputs Bob Smith #}
+3
View File
@@ -994,6 +994,9 @@ The following operators don't fit into any of the other categories:
{{ people|map(first_name_fn)|join(', ') }}
Arrow functions can be called using the :doc:`invoke </filters/invoke>`
filter.
Operators
~~~~~~~~~
+1 -1
View File
@@ -29,7 +29,7 @@ class IntegrationTest extends IntegrationTestCase
protected function getRuntimeLoaders()
{
return [
new class() implements RuntimeLoaderInterface {
new class implements RuntimeLoaderInterface {
public function load(string $class): ?object
{
return CacheRuntime::class === $class ? new CacheRuntime(new ArrayAdapter()) : null;
@@ -36,7 +36,7 @@ Hello
=====
Great!
EOF
EOF,
]));
$twig->addExtension(new MarkdownExtension());
$twig->addRuntimeLoader(new class($class) implements RuntimeLoaderInterface {
+1 -1
View File
@@ -53,7 +53,7 @@ abstract class AbstractExtension implements LastModifiedExtensionInterface
$lastModified = filemtime($filename);
// Track modifications of the runtime class if it exists and follows the naming convention
if (str_ends_with($filename, 'Extension.php') && is_file($filename = substr($filename, 0, -13) . 'Runtime.php')) {
if (str_ends_with($filename, 'Extension.php') && is_file($filename = substr($filename, 0, -13).'Runtime.php')) {
$lastModified = max($lastModified, filemtime($filename));
}
+12 -1
View File
@@ -268,6 +268,7 @@ final class CoreExtension extends AbstractExtension
// iteration and runtime
new TwigFilter('default', self::default(...), ['node_class' => DefaultFilter::class]),
new TwigFilter('keys', self::keys(...)),
new TwigFilter('invoke', self::invoke(...)),
];
}
@@ -908,6 +909,16 @@ final class CoreExtension extends AbstractExtension
return array_keys($array);
}
/**
* Invokes a callable.
*
* @internal
*/
public static function invoke(\Closure $arrow, ...$arguments): mixed
{
return $arrow(...$arguments);
}
/**
* Reverses a variable.
*
@@ -2027,7 +2038,7 @@ final class CoreExtension extends AbstractExtension
$property = $class->getProperty($property);
if (!$property->isPublic()) {
if (!$property->isPublic() || $property->isStatic()) {
static $false;
return $false ??= static fn () => false;
+1 -1
View File
@@ -68,7 +68,7 @@ class Lexer
(?<DNUM>(?&LNUM)(?:(?&FRAC))?) # Decimal number 123_456.456
)(?:(?&DNUM)(?:(?&EXPONENT))?) # 123_456.456E+10
/Ax';
public const REGEX_DQ_STRING_DELIM = '/"/A';
public const REGEX_DQ_STRING_PART = '/[^#"\\\\]*(?:(?:\\\\.|#(?!\{))[^#"\\\\]*)*/As';
public const REGEX_INLINE_COMMENT = '/#[^\n]*/A';
+7
View File
@@ -98,6 +98,13 @@ abstract class Node implements \Countable, \IteratorAggregate
return $repr;
}
public function __clone()
{
foreach ($this->nodes as $name => $node) {
$this->nodes[$name] = clone $node;
}
}
public function compile(Compiler $compiler): void
{
foreach ($this->nodes as $node) {
+1 -1
View File
@@ -442,7 +442,7 @@ abstract class Template
return $parent->hasMacro($name, $context);
}
protected function getTemplateForMacro(string $name, array $context, int $line, Source $source): Template
protected function getTemplateForMacro(string $name, array $context, int $line, Source $source): self
{
if (method_exists($this, $name)) {
return $this;
+2 -2
View File
@@ -178,7 +178,7 @@ class EnvironmentTest extends TestCase
// force compilation
$twig = new Environment($loader = new ArrayLoader(['index' => '{{ foo }}']), $options);
$twig->addExtension($extension = new class() extends AbstractExtension {
$twig->addExtension($extension = new class extends AbstractExtension {
public bool $throw = false;
public function getFilters(): array
@@ -464,7 +464,7 @@ class EnvironmentTest extends TestCase
public function testResettingGlobals()
{
$twig = new Environment(new ArrayLoader(['index' => '']));
$twig->addExtension(new class() extends AbstractExtension implements GlobalsInterface {
$twig->addExtension(new class extends AbstractExtension implements GlobalsInterface {
public function getGlobals(): array
{
return [
+5 -5
View File
@@ -42,7 +42,7 @@ class ErrorTest extends TestCase
{% block foo %}
{{ foo.bar }}
{% endblock %}
EOHTML
EOHTML,
]);
$twig = new Environment($loader, ['strict_variables' => true, 'debug' => true, 'cache' => false]);
@@ -71,7 +71,7 @@ EOHTML
{% block foo %}
{{ foo.bar }}
{% endblock %}
EOHTML
EOHTML,
]);
$twig = new Environment($loader, ['strict_variables' => true, 'debug' => true, 'cache' => false]);
@@ -162,7 +162,7 @@ EOHTML
{% for n in variable|filter(x => x > 3) %}
This list contains {{n}}.
{% endfor %}
EOHTML
EOHTML,
]);
$twig = new Environment($loader, ['debug' => true, 'cache' => false]);
@@ -189,7 +189,7 @@ EOHTML
{% for n in variable|map(x => x * 3) %}
{{- n -}}
{% endfor %}
EOHTML
EOHTML,
]);
$twig = new Environment($loader, ['debug' => true, 'cache' => false]);
@@ -214,7 +214,7 @@ EOHTML
'reduce-null.html' => <<<EOHTML
{# We expect a runtime error if `variable` is not traversable #}
{{ variable|reduce((carry, x) => carry + x) }}
EOHTML
EOHTML,
]);
$twig = new Environment($loader, ['debug' => true, 'cache' => false]);
+5 -5
View File
@@ -395,7 +395,7 @@ class ExpressionParserTest extends TestCase
public function testCompiledCodeForDynamicTest()
{
$env = new Environment(new ArrayLoader(['index' => '{{ "a" is foo_foo_bar_bar }}']), ['cache' => false, 'autoescape' => false]);
$env->addExtension(new class() extends AbstractExtension {
$env->addExtension(new class extends AbstractExtension {
public function getTests(): array
{
return [
@@ -410,7 +410,7 @@ class ExpressionParserTest extends TestCase
public function testCompiledCodeForDynamicFunction()
{
$env = new Environment(new ArrayLoader(['index' => '{{ foo_foo_bar_bar("a") }}']), ['cache' => false, 'autoescape' => false]);
$env->addExtension(new class() extends AbstractExtension {
$env->addExtension(new class extends AbstractExtension {
public function getFunctions(): array
{
return [
@@ -425,7 +425,7 @@ class ExpressionParserTest extends TestCase
public function testCompiledCodeForDynamicFilter()
{
$env = new Environment(new ArrayLoader(['index' => '{{ "a"|foo_foo_bar_bar }}']), ['cache' => false, 'autoescape' => false]);
$env->addExtension(new class() extends AbstractExtension {
$env->addExtension(new class extends AbstractExtension {
public function getFilters(): array
{
return [
@@ -451,10 +451,10 @@ class ExpressionParserTest extends TestCase
public function testUnaryPrecedenceChange()
{
$env = new Environment(new ArrayLoader(), ['cache' => false, 'autoescape' => false]);
$env->addExtension(new class () extends AbstractExtension {
$env->addExtension(new class extends AbstractExtension {
public function getOperators(): array
{
$class = new class (new ConstantExpression('foo', 1), 1) extends AbstractUnary {
$class = new class(new ConstantExpression('foo', 1), 1) extends AbstractUnary {
public function operator(Compiler $compiler): Compiler
{
return $compiler->raw('!');
+1 -1
View File
@@ -55,7 +55,7 @@ class CoreTest extends TestCase
{
return [
'empty' => [[]],
'non-countable' => [new class() extends \ArrayObject {
'non-countable' => [new class extends \ArrayObject {
}],
];
}
+3 -3
View File
@@ -507,7 +507,7 @@ EOF
public function testSandboxSourcePolicyEnableReturningFalse()
{
$twig = $this->getEnvironment(false, [], self::$templates, [], [], [], [], [], new class() implements \Twig\Sandbox\SourcePolicyInterface {
$twig = $this->getEnvironment(false, [], self::$templates, [], [], [], [], [], new class implements \Twig\Sandbox\SourcePolicyInterface {
public function enableSandbox(Source $source): bool
{
return '1_basic' != $source->getName();
@@ -518,7 +518,7 @@ EOF
public function testSandboxSourcePolicyEnableReturningTrue()
{
$twig = $this->getEnvironment(false, [], self::$templates, [], [], [], [], [], new class() implements \Twig\Sandbox\SourcePolicyInterface {
$twig = $this->getEnvironment(false, [], self::$templates, [], [], [], [], [], new class implements \Twig\Sandbox\SourcePolicyInterface {
public function enableSandbox(Source $source): bool
{
return '1_basic' === $source->getName();
@@ -530,7 +530,7 @@ EOF
public function testSandboxSourcePolicyFalseDoesntOverrideOtherEnables()
{
$twig = $this->getEnvironment(true, [], self::$templates, [], [], [], [], [], new class() implements \Twig\Sandbox\SourcePolicyInterface {
$twig = $this->getEnvironment(true, [], self::$templates, [], [], [], [], [], new class implements \Twig\Sandbox\SourcePolicyInterface {
public function enableSandbox(Source $source): bool
{
return false;
+14
View File
@@ -0,0 +1,14 @@
--TEST--
"invoke" filter
--TEMPLATE--
{% set func = x => 'Hello '~x %}
{{ func|invoke('World') }}
{% set func2 = (x, y) => x+y %}
{{ func2|invoke(3, 2) }}
--DATA--
return []
--CONFIG--
return []
--EXPECT--
Hello World
5
+2
View File
@@ -4,9 +4,11 @@
{{ constant('DATE_W3C') == expect ? 'true' : 'false' }}
{{ constant('ARRAY_AS_PROPS', object) }}
{{ constant('class', object) }}
{{ constant('ARRAY_AS_PROPS', object) ?? 'KO' }}
--DATA--
return ['expect' => DATE_W3C, 'object' => new \ArrayObject(['hi'])]
--EXPECT--
true
2
ArrayObject
2
+2
View File
@@ -67,6 +67,8 @@ class TwigTestFoo implements \Iterator
public $position = 0;
public $array = [1, 2];
public static $foo = 'Foo';
public function bar($param1 = null, $param2 = null)
{
return 'bar'.($param1 ? '_'.$param1 : '').($param2 ? '-'.$param2 : '');
+1 -1
View File
@@ -176,7 +176,7 @@ class FilterTest extends NodeTestCase
private static function createExtension(): AbstractExtension
{
return new class() extends AbstractExtension {
return new class extends AbstractExtension {
public function getFilters(): array
{
return [