The name passed to MacroReferenceExpression is emitted as raw PHP in
compile() via "->{$name}(...)". Callers were expected to validate
the name, but a missing check led to CVE-2026-XXXXX (PHP code injection
via _self / import macro reference): defense-in-depth, validate the
name in the constructor so the class is safe by construction.
This PR was merged into the 3.x branch.
Discussion
----------
Support short-circuiting in null-safe operator chains
This PR adds short-circuiting for null-safe operator chains, using the same rules as PHP, `PropertyAccess`, and the `ExpressionLanguage`.
Previously, only the immediate null-safe access was guarded. With this change, as soon as a `null` is encountered at a null-safe access, the rest of the chain is skipped.
My approach was to move the null check outside of the `getAttribute()` calls so the expression can immediately return `null`, eg:
```twig
foo?.bar.baz
```
Before:
```php
yield $this->env
->getRuntime('Twig\Runtime\EscaperRuntime')
->escape(
CoreExtension::getAttribute(
$this->env,
$this->source,
(
null === (
$_v0 = (
isset($context['foo']) || array_key_exists('foo', $context)
? $context['foo']
: throw new RuntimeError('Variable "foo" does not exist.', 3, $this->source)
)
)
? null
: CoreExtension::getAttribute(
$this->env,
$this->source,
$_v0,
'bar',
[],
'any',
false,
false,
false,
3
)
),
'baz',
[],
'any',
false,
false,
false,
3
),
'html',
null,
true
);
```
Now:
```php
yield $this->env
->getRuntime('Twig\Runtime\EscaperRuntime')
->escape(
(
null === (
$_v0 = (
isset($context['foo']) || array_key_exists('foo', $context)
? $context['foo']
: throw new RuntimeError('Variable "foo" does not exist.', 3, $this->source)
)
)
? null
: CoreExtension::getAttribute(
$this->env,
$this->source,
CoreExtension::getAttribute(
$this->env,
$this->source,
$_v0,
'bar',
[],
'any',
false,
false,
false,
3
),
'baz',
[],
'any',
false,
false,
false,
3
)
),
'html',
null,
true
);
```
Commits
-------
d56e8e2dba Support short-circuiting in null-safe operator chains
This PR was merged into the 3.x branch.
Discussion
----------
Replace template loader mocks by ArrayLoader
Commits
-------
e4e95f5b Replace template loader mocks by ArrayLoader
If a static method cannot be resolved to the calling class, but the calling class has, or inherits, a `__callStatic` handler, this allows the `__callStatic` handler to be used with the calling class, and not the inherited class as would occur with reflection. This allows systems such as Laravel facades to still work.
Fixes https://github.com/twigphp/Twig/issues/3716