Commit Graph

7405 Commits

Author SHA1 Message Date
Fabien Potencier a6769aefb3 Prepare the 3.24.0 release v3.24.0 2026-03-17 22:31:11 +01:00
Fabien Potencier 8abec84013 minor #4784 Add two tests for error conditions in #3930 (mpdude)
This PR was merged into the 3.x branch.

Discussion
----------

Add two tests for error conditions in #3930

Here are two test cases for potential errors in `html_attr_merge`, asked for by `@fabpot` in https://github.com/twigphp/Twig/pull/3930#pullrequestreview-3956645279.

Commits
-------

25bfb5957c Add two tests for error conditions in #3930
2026-03-17 11:29:37 +01:00
Fabien Potencier 2fcc93954d Fix CS 2026-03-17 08:26:25 +01:00
Matthias Pigulla 25bfb5957c Add two tests for error conditions in #3930 2026-03-17 08:24:08 +01:00
Fabien Potencier 8b93364bf6 feature #3930 Add an html_attr function to make outputting HTML attributes easier (mpdude, polarbirke)
This PR was squashed before being merged into the 3.x branch.

Discussion
----------

Add an `html_attr` function to make outputting HTML attributes easier

**Updated:** This description has been updated to reflect changes from the discussion up to https://github.com/twigphp/Twig/pull/3930#issuecomment-3870445987.

This PR suggests adding an `html_attr` function and two filters `html_attr_merge` and `html_attr_type`. Together, they are intended to make it easier to collect HTML attributes in arrays, in order to pass them in Twig to included templates or macros, and to ultimately print such attribute sets as.

`html_attr_merge` can be used to either merge such arrays over default values, or to override (say, inside a macro) particular values in a given attribute array. As described in #3907, it favors overwriting simple (scalar) attribute values and appending to multi-valued attributes over all the other operations one could conceive (like, for example, replacing a list of two CSS `class` names with two other ones). This is a design decision to keep the API simple and optimized for the primary use case that I see. But, since we're mostly dealing with arrays after all, users are free to do in parallel any other kind of array wrangling they see fit.

So, this PR is _not_ trying to design a full-fledged, object-oriented API with all the necessary methods to add, replace, remove attributes; to add, change or toggle elements in "list" style attributes like `class`; to provide extension points for custom (arbitrary) attributes or to provide a fluent API to do all that from within PHP code. See https://github.com/symfony/ux/issues/3269 for a Symfony UX component RFC that does that.

A little bit of special case handling is present for `aria-*`, `data-*` and inline CSS `style` attributes. But apart from that, there is no special knowledge about the attributes defined in HTML, ARIA or other standards, nor about the structure and sementic of attribute values. The approach works in a generic way, so it should be possible to use it for many custom attributes as well.

In order to support "list" style attributes like `class`, `aria-labelledby` or `srcset` that may come in different flavors, users have to be disciplined and consistently use iterables (arrays) to represent such attribute values, possibly assisted by the `html_attr_type` filter (see below).

When printing attributes, names and values are escaped. For names, the `html_attr_relaxed` strategy (#4743) is used.

#### Motivation and practical examples

I have seen repeating patterns when dealing with HTML attributes in Twig templates and macros. Typical examples can be found in Symfony's form theme, where an `attr` variable is present in various blocks.

https://github.com/symfony/symfony/blob/4a5d8cf03e1e31d1a7591921c6fa1fe7ec1c2015/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig#L453-L458

```twig
{%- block widget_attributes -%}
    id="{{ id }}" name="{{ full_name }}"
    {%- if disabled %} disabled="disabled"{% endif -%}
    {%- if required %} required="required"{% endif -%}
    {{ block('attributes') }}
{%- endblock widget_attributes -%}
```

Could be along the lines of:

`{{ html_attr(attr, { id, name: full_name, disabled: disabled ? true : false, required : required ? true : false }) }}`.

If `disabled` and `required` were guaranteed to be booleans (I haven't checked), even better:

`{{ html_attr(attr, { id, name: full_name, disabled, required }) }}`

https://github.com/symfony/symfony/blob/4a5d8cf03e1e31d1a7591921c6fa1fe7ec1c2015/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig#L347-L360

```twig
    {%- set attr = {} -%}
    {%- set aria_describedby = [] -%}
    {%- if help -%}
        {%- set aria_describedby = aria_describedby|merge([id ~ '_help']) -%}
    {%- endif -%}
    {%- if errors|length > 0 -%}
        {%- set aria_describedby = aria_describedby|merge(errors|map((_, index) => id ~ '_error' ~ (index + 1))) -%}
    {%- endif -%}
    {%- if aria_describedby|length > 0 -%}
        {%- set attr = attr|merge({'aria-describedby': aria_describedby|join(' ')}) -%}
    {%- endif -%}
    {%- if errors|length > 0 -%}
        {%- set attr = attr|merge({'aria-invalid': 'true'}) -%}
    {%- endif -%}
```

Could be:

```twig
  {%- set attr = {}|html_attr_merge(
    help ? { 'aria-describedby': [id ~ '_help'] },
    errors|length ? { 'aria-invalid': true, 'aria-describedby': errors|map((_, index) => id ~ '_error' ~ (index + 1)) }
  ) -%}
```

https://github.com/symfony/symfony/blob/4a5d8cf03e1e31d1a7591921c6fa1fe7ec1c2015/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig#L470-L481

```twig
{% block attributes -%}
    {%- for attrname, attrvalue in attr -%}
        {{- ' ' -}}
        {%- if attrname in ['placeholder', 'title'] -%}
            {{- attrname }}="{{ translation_domain is same as(false) or attrvalue is null ? attrvalue : attrvalue|trans(attr_translation_parameters, translation_domain) }}"
        {%- elseif attrvalue is same as(true) -%}
            {{- attrname }}="{{ attrname }}"
        {%- elseif attrvalue is not same as(false) -%}
            {{- attrname }}="{{ attrvalue }}"
        {%- endif -%}
    {%- endfor -%}
{%- endblock attributes -%}
```

This should basically be the same as `{{ html_attr(attr) }}`, ignoring edge cases for `null` values. Handling of the `translation_domain` might require a preceding `html_attr_merge` step to replace values with translations.

Finally,

```twig
    {% set id = 'id value' %}
    {% set href = 'href value' %}
    {% set disabled = true %}

    <div {{ html_attr(
        { id, href },
        disabled ? { 'aria-disabled': 'true' },
        not disabled ? { 'aria-enabled' : true },
        { class: ['zero', 'first'] },
        { class: ['second'] },
        true ? { class: 'third' },
        { style: { color: 'red' } },
        { style: { 'background-color': 'green' } },
        { style: { color: 'blue' } },
        { 'data-test': 'some value' },
        { 'data-test': 'other value', 'data-bar': 'baz' }},
        { 'dangerous=yes foo' : 'xss' },
        { style: ['text-decoration: underline'] },
    ) }}></div>
```

will generate HTML markup:

```
<div id="id value" href="href value" aria-disabled="true" class="zero first second third" style="color: red; background-color: green; color: blue; text-decoration: underline;" data-test="other value" data-bar="baz" dangerous&#x3D;yes&#x20;foo="xss"></div>
```

#### Details on `html_attr_merge`

This filter merges an `attr` style array with one or several other arrays given as arguments. All of those arrays should reasonably be mappings, i. e. use keys that denote attribute names, and not sequences or lists with numeric keys.

Empty arrays, empty strings or false values in the argument list will be ignored, which can be used to conditionally include values in the merge list like so:

```twig
{% set attr = attr|html_attr_merge(
  condition ? { attrname: "attrvalue", other: "value" }
) %}
```

The merging of attribute values is similar to PHP's `array_merge` function. Latter (right) values generally override former (left) values, as follows:

When two values are to be merged and both are either scalars or objects, the latter (right) value overrides the previous (left) one.

When both values are iterables, `array_merge`/spread operator behavior is used: Numeric indices will be appended, whereas non-numeric ones will be replaced. This can be used to override designated elements in sets like CSS classes:

```twig
{% set attr = { class: ['foo', 'bar'] }|html_attr_merge(
  { class: { importance: 'normal' } },
  critical ? { class: { importance: 'high' } }
) %}
```

To provide more flexibility with regards to different merging strategies, the `MergeInterface` is provided as a flex point for advanced use cases of power users.

* When an attribute value that represents a "right hand side" value has to be merged and implements `MergeInterface`, its `mergeInto()` method will be passed the previous (left hand side) value. That method will return the merge result.

* Otherwise, when the "left hand side" value implements `MergeInterface`, `appendFrom()` will be passed the new (right hand side) value. Again, that method returns the merge result.

Other combinations of values are rejected, an exception is thrown. This is a design decision to clearly and early notify users of combinations that might have unclear or unpredictable results, like merging a string like `'foo'` with an array like `['bar', 'baz']` for a `class` attribute – should this override, since one value is a scalar, or append, since the other one is an array?

#### Details on `html_attr`

The `html_attr()` function prints attribute-arrays as HTML. It will perform appropriate escaping of attribute names and values.

One or several attribute arrays can be passed to `html_attr`, and `html_attr_merge` will be used first to merge those.

In general, scalar attribute values (including the empty string `''`) will be printed as-is. For booleans and `null` values, the following extra rules apply:

* For `aria-*`, boolean `true` and `false` will be coalesced to `"true"` and `"false"`, respectively.
* For `data-*`, boolean `true` will be coalesced to `"true"`, and non-scalar values will be JSON-encoded
* Otherwise, `false` and `null` attribute values will always omit printing of the attribute.
* `true` values will print the attribute as `attributeName=""`. This is equivalent to printing `<... attributeName>`, but is X(HT)ML compliant. The user-agent will fill in the attribute's _empty default value_.

These rules derive from the comparison provided at https://github.com/symfony/ux/issues/3269#issuecomment-3708588342 that shows how React and Vue as front-end frameworks behave in the same situation.

For values that implement `AttributeValueInterface`, its `getValue(): ?string` method will be called first. The attribute will be omitted for a `null` return value, otherwise printed with the returned string.

This interface could be used to provide (outside the scope of this PR or even outside Twig itself) classes that could e. g. help building more complex attribute values, like for image `srcset`. But the primary reason for adding it was to be able to deal with attributes values that are lists or hashes and need to be printed in different ways:

* attributes like `class` or `aria-labelledby` use [space-separated tokens](https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#space-separated-tokens) as their value
* `srcset` or `sizes` for `<img>` use [comma-separated tokens](https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#comma-separated-tokens)
* other standards/extensions may have even other concepts

`html_attr` will generally print array values as a space-separated list of values.

A special case is the attribute name `style` if its value is an array. It will be converted to inline CSS. Values with numeric keys will be printed followed by a `;`. Non-numeric keys will print a pattern of `key: value;`.

#### Details on `html_attr_type`

The `html_attr_type` filter can be used to convert an array passed into it into implementations of the mentioned interfaces in a few predefined ways. It takes a single argument indicating the type to use – similar to the `escape` filter in Twig that knows about `html`, `js`, `css` and a few more.

* `sst` means `space separated tokens`
* `cst` means `comma separated tokens`
* `style` means "inline CSS", for completeness

So, the following will construct an `attr` array for an `img` tag, where `sizes` needs to be printed separated by commas.

```twig
{% set attr = {
  srcset: ['small.jpg 480w']|html_attr_type('cst'),
  alt: 'A cute kitten'
} %}
{# amend the srcset #}
{% set attr = attr|html_attr_merge({ srcset: ['medium.jpg 800w', 'large.jpg 1200w'] }) %}
<img {{ html_attr(attr) />
```

This works because the `SeparatedTokenList` that is used for `sst` and `cst` implements merge behavior where a given value can be extended by merging arrays.

#### Design considerations

Exposing behavior for different attribute types through these interfaces may not be the 100% perfect, nice, automagic solution. _But_ it has the big advantage that we are not committing ourselves to a particular list of attributes for which standards-specific knowledge would have to be put in the code. I would consider that a maintenance nightmare, since it would require us to decide which attribute/special case to support and which not. Every single change in that list would be BC-breaking.

The two interfaces put control over that in the hands of power users or extension authors. Arbitrary ways could be conceived to create instances of these interfaces.

The `html_attr_type` filter provides built-in access to the two types I see relevant in the HTML 5 standard, the space and comma separated token lists.

The built-in default conversion of arrays to space-separated token lists should further reduce visibility of that problem for average template authors, who can hopefully ignore the problem most of the time.

Closes #3907, which outlined the initial idea.

#### TODO:

- [x] Get initial feedback
- [x] Add tests
- [x] Concept for attributes that employ [comma-separated tokens](https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#comma-separated-tokens)
- [x] Add documentation
- [x] Add docblocks and type hints
- [x] Make a decision: [Escape attribute names or not?](https://github.com/twigphp/Twig/pull/3930#issuecomment-3743429100)

Co-authored-by: `@polarbirke`

Commits
-------

42c12fa720 Add an `html_attr` function to make outputting HTML attributes easier
2026-03-17 07:39:37 +01:00
Matthias Pigulla 42c12fa720 Add an html_attr function to make outputting HTML attributes easier 2026-03-17 07:39:33 +01:00
Fabien Potencier 9c85915a73 minor #4779 fix MatchesBinary namespace in changelog (xabbuh)
This PR was merged into the 3.x branch.

Discussion
----------

fix MatchesBinary namespace in changelog

Commits
-------

442bcd4625 fix MatchesBinary namespace in changelog
2026-02-26 10:46:59 +01:00
Christian Flothmann 442bcd4625 fix MatchesBinary namespace in changelog 2026-02-26 09:47:50 +01:00
Fabien Potencier e56cdfdded bug #4778 Fix null coalescing operator with imported macros (fabpot)
This PR was merged into the 3.x branch.

Discussion
----------

Fix null coalescing operator with imported macros

Closes #4776

When using the null coalescing operator with a macro imported via the `from` tag, the `TemplateVariable` node inside `MacroReferenceExpression` was deep-cloned, causing the clone to generate a different `$macros` key than the one assigned by `AssignTemplateVariable`. This resulted in a `Call to a member function hasMacro() on null` error.

Commits
-------

efa004caab Fix null coalescing operator with imported macros
2026-02-25 10:38:36 +01:00
Fabien Potencier faa7e877b8 feature #4775 Add getOperatorTokens() to ExpressionParserInterface to separate operator token registration from parser identity (fabpot)
This PR was merged into the 3.x branch.

Discussion
----------

Add getOperatorTokens() to ExpressionParserInterface to separate operator token registration from parser identity

Closes #4767
Closes #4774

Commits
-------

e5eb95d0d7 Add getOperatorTokens() to ExpressionParserInterface to separate operator token registration from parser identity
2026-02-25 09:37:04 +01:00
Fabien Potencier efa004caab Fix null coalescing operator with imported macros 2026-02-25 08:38:54 +01:00
Fabien Potencier 86e4384bf6 minor #4777 fix documentation typos for singular filter arguments (ArnaudLigny)
This PR was merged into the 3.x branch.

Discussion
----------

fix documentation typos for singular filter arguments

This pull request makes a minor update to the documentation for the `singular` filter in `doc/filters/singular.rst`. The change clarifies that the `all` argument returns all possible singulars, not plurals, and fixes the formatting of the links at the end of the file.

Commits
-------

734720e5e7 fix documentation typos for singular filter arguments
2026-02-25 07:16:50 +01:00
Arnaud Ligny 734720e5e7 fix documentation typos for singular filter arguments 2026-02-25 01:28:45 +01:00
Fabien Potencier e5eb95d0d7 Add getOperatorTokens() to ExpressionParserInterface to separate operator token registration from parser identity 2026-02-24 21:13:06 +01:00
Fabien Potencier 2ec5479d4c bug #4774 Ensure filters/attributes aren't mistaken for operators (brandonkelly)
This PR was merged into the 3.x branch.

Discussion
----------

Ensure filters/attributes aren't mistaken for operators

Updates the regex in `Lexer::getOperatorRegex()` to account for filters/attributes that have a space between their `|`/`.` operator and the filter/attribute name, to ensure they aren’t mistaken for operators.

A test is included that checks the following template.

```twig
{{ 'foo'|and }}
{{ 'bar' | and }}
{{ foo.and }}
{{ bar . and }}
{{ foo and bar }}
```

(Only the `and` in the last tag should be considered an operator.)

Fixes #4767

Commits
-------

a9ac993938 Ensure filters/attributes aren't mistaken for operators
2026-02-24 21:10:30 +01:00
brandonkelly a9ac993938 Ensure filters/attributes aren't mistaken for operators 2026-02-24 21:10:18 +01:00
Fabien Potencier 206ad9f1a8 minor #4772 Enforce more precise type on ListExpression (fabpot)
This PR was merged into the 3.x branch.

Discussion
----------

Enforce more precise type on ListExpression

Commits
-------

dcfc419a25 Enforce more precise type on ListExpression
2026-02-23 15:06:40 +01:00
Fabien Potencier dcfc419a25 Enforce more precise type on ListExpression 2026-02-23 13:36:14 +01:00
Fabien Potencier f8fb235f47 feature #4771 Deprecate passing non AbstractExpression nodes to MatchesBinary (fabpot)
This PR was merged into the 3.x branch.

Discussion
----------

Deprecate passing non AbstractExpression nodes to MatchesBinary

Commits
-------

379fb2faca Deprecate passing non AbstractExpression nodes to MatchesBinary
2026-02-23 13:06:23 +01:00
Fabien Potencier 379fb2faca Deprecate passing non AbstractExpression nodes to MatchesBinary 2026-02-23 11:40:51 +01:00
Fabien Potencier b8ff82d968 feature #4769 Deprecate passing a non-AbstractExpression node to Parser::setParent() (fabpot)
This PR was merged into the 3.x branch.

Discussion
----------

Deprecate passing a non-AbstractExpression node to Parser::setParent()

Commits
-------

54d5c004b4 Deprecate passing a non-AbstractExpression node to Parser::setParent()
2026-02-23 11:11:09 +01:00
Fabien Potencier 54d5c004b4 Deprecate passing a non-AbstractExpression node to Parser::setParent() 2026-02-22 15:28:59 +01:00
Fabien Potencier 0319c822d1 Update CHANGELOG 2026-02-08 19:02:26 +01:00
Fabien Potencier 751a187f07 feature #4748 Support short-circuiting in null-safe operator chains (HypeMC)
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
2026-02-08 19:02:03 +01:00
Fabien Potencier afe8e19ecb feature #4743 Add html_attr_relaxed escaping strategy (mpdude)
This PR was squashed before being merged into the 3.x branch.

Discussion
----------

Add `html_attr_relaxed` escaping strategy

This adds `html_attr_relaxed`, a relaxed variant of the `html_attr` escaping strategy. The difference is that `html_attr_relaxed` does not escape the `:`, `@`, `[` and `]` characters. These are used by some front-end frameworks in attribute names to wire special handling/value binding. See https://v2.vuejs.org/v2/guide/syntax.html#v-bind-Shorthand for an example.

The HTML 5 spec does not exclude all those characters from attribute names ([html.spec.whatwg.org/multipage/syntax.html#attributes-2](https://html.spec.whatwg.org/multipage/syntax.html#attributes-2)).

However, at least XML processors will treat the colon as the XML namespace separator.

HTML 5 allows XML only on SVG and MathML elements, and only for pre-defined namespace-prefixes ([developer.mozilla.org/en-US/docs/Web/API/Attr/localName#:~:text=That means that the local,different from the qualified name](https://developer.mozilla.org/en-US/docs/Web/API/Attr/localName#:~:text=That%20means%20that%20the%20local,different%20from%20the%20qualified%20name)). For other something: prefixes, these will simply be passed on as part of the local attribute name.

According to [engine.sygnal.com/research/html5-attribute-names](https://engine.sygnal.com/research/html5-attribute-names), all current browser implementations handle at least the colon fine, and the aforementioned Vue.js documentation suggests that this is also the case for @.

Note also that Symfony UX only conditionally escapes attribute names, and it has `:` and `@` in its safe list:
https://github.com/symfony/ux/blob/c9a3e66b8ac53e870097e8a828913e57204398e7/src/TwigComponent/src/ComponentAttributes.php#L82

Closes #3614.

Commits
-------

04aa3df49f Add `html_attr_relaxed` escaping strategy
2026-02-08 18:59:10 +01:00
Matthias Pigulla 04aa3df49f Add html_attr_relaxed escaping strategy 2026-02-08 18:59:07 +01:00
Fabien Potencier df893829f2 feature #4759 Add support for renaming variables in object destructuring (fabpot)
This PR was merged into the 3.x branch.

Discussion
----------

Add support for renaming variables in object destructuring

Closes #4747

Commits
-------

3cc1b5233c Add support for renaming variables in object destructuring
2026-02-07 14:22:40 +01:00
Fabien Potencier 5fabdd0e6c minor #4764 re-add mixed return type (xabbuh)
This PR was merged into the 3.x branch.

Discussion
----------

re-add mixed return type

re-doing #4582, see https://github.com/symfony/symfony/actions/runs/21779345389/job/62840897500#step:8:3635

Commits
-------

3a903664a8 re-add mixed return type
2026-02-07 13:53:57 +01:00
Christian Flothmann 3a903664a8 re-add mixed return type 2026-02-07 13:41:32 +01:00
Fabien Potencier 3e213d14f3 minor #4625 [Doc] Fix intro for operator precedence table ? (hellomedia)
This PR was merged into the 3.x branch.

Discussion
----------

[Doc] Fix intro for operator precedence table ?

The table introductory paragraph says operator with lowest precedence are listed first ( I assume it means "listed first in the operator table" ).

However, the table starts with a precedence of 500, which seems to be the highest.

Am I completely misreading this ?

Commits
-------

26a12f73f2 Fix intro for operator precedence table ?
2026-02-07 10:12:40 +01:00
Fabien Potencier f502c9ed60 minor #4745 Update .gitattributes to remove splitsh.json (williamdes)
This PR was merged into the 3.x branch.

Discussion
----------

Update .gitattributes to remove splitsh.json

- Updated .gitattributes to remove splitsh.json

Commits
-------

79aecfbae0 Update .gitattributes to remove splitsh.json
2026-02-07 10:09:51 +01:00
Fabien Potencier 44093544e6 Use stable versions of tools 2026-02-07 09:12:49 +01:00
Fabien Potencier 2c2d2fd435 minor #4762 Fix CS (fabpot)
This PR was merged into the 3.x branch.

Discussion
----------

Fix CS

Commits
-------

861215c507 Fix CS
2026-02-07 09:10:10 +01:00
Fabien Potencier 861215c507 Fix CS 2026-02-07 09:07:38 +01:00
HypeMC d56e8e2dba Support short-circuiting in null-safe operator chains 2026-02-06 22:36:57 +01:00
Fabien Potencier eb516c9740 minor #4755 Add a not about the return value of destructuring (fabpot)
This PR was merged into the 3.x branch.

Discussion
----------

Add a not about the return value of destructuring

Commits
-------

c38868cdd0 Add a not about the return value of destructuring
2026-02-06 22:15:10 +01:00
Fabien Potencier 3cc1b5233c Add support for renaming variables in object destructuring 2026-02-06 22:12:59 +01:00
Fabien Potencier 09e6bb5a91 Bump version 2026-02-06 22:12:49 +01:00
Fabien Potencier c38868cdd0 Add a not about the return value of destructuring 2026-02-04 22:36:00 +01:00
Fabien Potencier 87848155a5 minor #4749 Fix null-safe operator test (HypeMC)
This PR was merged into the 3.x branch.

Discussion
----------

Fix null-safe operator test

Noticed this while working on #4748.

Without `strict_variables`, the tests always pass, even when the null-safe operator is not used.

Commits
-------

a16ac6bd35 Fix null-safe operator test
2026-01-27 11:37:59 +01:00
HypeMC a16ac6bd35 Fix null-safe operator test 2026-01-25 04:57:19 +01:00
Fabien Potencier 111b867703 Bump version 2026-01-23 22:27:43 +01:00
Fabien Potencier a64dc5d2cc Prepare the release v3.23.0 2026-01-23 22:00:41 +01:00
Fabien Potencier b0c42a9739 Update CHANGELOG 2026-01-23 22:00:23 +01:00
Fabien Potencier b609ae9d0d feature #4744 Add support for object and mapping destructuring (fabpot)
This PR was merged into the 3.x branch.

Discussion
----------

Add support for object and mapping destructuring

Refs https://github.com/twigphp/Twig/issues/3399

Commits
-------

8a0f8acbdf Add support for object and mapping destructuring
2026-01-23 12:57:17 +01:00
William Desportes 79aecfbae0 Update .gitattributes to remove splitsh.json 2026-01-22 09:55:32 +00:00
Fabien Potencier 8a0f8acbdf Add support for object and mapping destructuring 2026-01-21 13:21:14 +01:00
Fabien Potencier 76c404ec67 Rename classes 2026-01-21 13:14:16 +01:00
Fabien Potencier d784400fb2 feature #4742 Assignment operator array destructuring (fabpot)
This PR was squashed before being merged into the 3.x branch.

Discussion
----------

Assignment operator array destructuring

Follow-up to the introduction of the new = operator, array destructuring.
Refs #3399

Commits
-------

bb99af3b39 Assignment operator array destructuring
2026-01-21 08:46:31 +01:00
Fabien Potencier bb99af3b39 Assignment operator array destructuring 2026-01-21 08:46:28 +01:00