Commit Graph

7679 Commits

Author SHA1 Message Date
Fabien Potencier 099fa3471a Document that sequence destructuring consumes one value per pattern slot 2026-08-27 13:07:47 +02:00
Fabien Potencier abbdf82823 Fix the html_attr documentation about iterables in data attributes 2026-08-27 13:07:41 +02:00
Fabien Potencier be220e6fd8 Warn about untrusted input with the default Tempest markdown converter 2026-08-27 13:07:34 +02:00
Fabien Potencier 1205b8b6ca Document that overriding MacroNode::compile() is not supported anymore 2026-08-27 13:07:26 +02:00
Fabien Potencier 7bd052dd91 Merge overlapping CHANGELOG entries for the destructuring fatal error fix 2026-08-27 13:07:19 +02:00
Fabien Potencier 207f873739 Document that include_only keeps global variables available 2026-08-27 13:07:13 +02:00
Fabien Potencier 9a8a76c86d feature #4902 Remove lazy macro import resolution (fabpot)
This PR was squashed before being merged into the 3.x branch.

Discussion
----------

Remove lazy macro import resolution

Reverts the fix for #4879

This removes the unreleased lazy resolution of template-level macro imports, which introduced complex and surprising behavior around template state, blocks, interrupted renders and sandboxing.

Macros called from another template should import their dependencies inside their own body. The documentation now explains this pattern.

Commits
-------

cf971e1a59 Remove lazy macro import resolution
2026-08-27 12:17:14 +02:00
Fabien Potencier cf971e1a59 Remove lazy macro import resolution 2026-08-27 12:17:08 +02:00
Fabien Potencier d850901a18 bug #4900 Honor date formatter prototype calendars (fabpot)
This PR was squashed before being merged into the 3.x branch.

Discussion
----------

Honor date formatter prototype calendars

This fixes calendar selection when an `IntlDateFormatter` prototype is configured.

An explicit calendar now takes precedence, followed by the prototype calendar, with Gregorian used as the final fallback. In particular, the `TRADITIONAL` calendar is no longer mistaken for an absent value because its constant value is zero.

Commits
-------

1de0bfceb4 Honor date formatter prototype calendars
2026-08-27 08:44:30 +02:00
Fabien Potencier 1de0bfceb4 Honor date formatter prototype calendars 2026-08-27 08:44:26 +02:00
Fabien Potencier 3265884e93 bug #4899 Fix Stringable keys for ArrayAccess implementations (fabpot)
This PR was squashed before being merged into the 3.x branch.

Discussion
----------

Fix Stringable keys for ArrayAccess implementations

This restores support for `Stringable` keys on string-keyed `ArrayAccess` implementations such as `ArrayObject` and `ArrayIterator`, while preserving object keys for `SplObjectStorage`.

The object key is attempted first and is converted to a string only when the implementation rejects it. The optimized and strict lookup paths now share the same behavior without duplicate existence checks.

Commits
-------

f3f1649955 Fix Stringable keys for ArrayAccess implementations
2026-08-27 08:42:51 +02:00
Fabien Potencier f3f1649955 Fix Stringable keys for ArrayAccess implementations 2026-08-27 08:42:47 +02:00
Fabien Potencier 62076874e8 bug #4901 Evaluate object destructuring expressions once (fabpot)
This PR was merged into the 3.x branch.

Discussion
----------

Evaluate object destructuring expressions once

This ensures the right-hand expression of an object or mapping destructuring assignment is evaluated exactly once.

All properties are now read from the same resolved value, avoiding repeated side effects and unnecessary work while preserving assignment order and return semantics.

Commits
-------

609376f491 Fix repeated object destructuring evaluation
2026-08-27 07:37:21 +02:00
Fabien Potencier 609376f491 Fix repeated object destructuring evaluation 2026-08-27 07:37:11 +02:00
Fabien Potencier 20c68c7ec2 bug #4896 Restore void return type compatibility for extension points (fabpot)
This PR was squashed before being merged into the 3.x branch.

Discussion
----------

Restore void return type compatibility for extension points

This restores the PHPDoc-only `void` return types on non-final extension points so subclasses written for Twig 3.28 continue to load on Twig 3.29.

Native return types remain on final classes and test methods where they are backward compatible. PHP CS Fixer is configured to preserve the compatible signatures.

Commits
-------

6bbbb49c3e Restore void return type compatibility for extension points
2026-08-27 07:27:42 +02:00
Fabien Potencier 6bbbb49c3e Restore void return type compatibility for extension points 2026-08-27 07:27:31 +02:00
Fabien Potencier 7ec1ed0a2b bug #4898 Reject destructuring patterns containing no variables (fabpot)
This PR was squashed before being merged into the 3.x branch.

Discussion
----------

Reject destructuring patterns containing no variables

This rejects sequence destructuring patterns containing only empty slots, such as `[,]` and `[,,]`.

These patterns previously compiled to an empty PHP list assignment and caused an uncatchable fatal error. They now produce a Twig `SyntaxError` with the template source and line.

Commits
-------

a3a318face Reject destructuring patterns containing no variables
2026-08-27 07:18:53 +02:00
Fabien Potencier a3a318face Reject destructuring patterns containing no variables 2026-08-27 07:18:49 +02:00
Fabien Potencier 885cbdb58f feature #4895 Extract htmlAttrValue() from html_attr for standalone attribute rendering (Kocal)
This PR was squashed before being merged into the 3.x branch.

Discussion
----------

Extract `htmlAttrValue()` from `html_attr` for standalone attribute rendering

The per-value resolution behind `html_attr` is extracted into a new public `HtmlExtension::htmlAttrValue()`, returning the unescaped value or `null` to omit the attribute; `html_attr()` now delegates to it, output unchanged, existing tests untouched. This lets third parties render a single attribute exactly like `html_attr` without a Twig `Environment`, since the resolution is escaper-free. symfony/ux#3820 and symfony/ux#3821 depend on this PR.

The `data-*` branch only tested `is_scalar()`, so a `\Stringable` was JSON-encoded instead of using its string representation; the same object already rendered its string form in `title` or `class`, and `AttributeValueInterface` was already excluded from that branch.

| Value in `data-value` | Before | After |
| --- | --- | --- |
| a `\Stringable` | `data-value="{}"` | `data-value="hello"` |
| a `\Stringable` that is also `JsonSerializable` | `data-value=""01JABC""` | `data-value="01JABC"` |

Commits
-------

9b18e3757d Extract `htmlAttrValue()` from `html_attr` for standalone attribute rendering
2026-08-26 16:47:06 +02:00
Hugo Alliaume 9b18e3757d Extract htmlAttrValue() from html_attr for standalone attribute rendering 2026-08-26 16:46:53 +02:00
Fabien Potencier e50f980542 bug #4894 Fix an empty destructuring pattern triggering a PHP fatal error instead of a SyntaxError (fabpot)
This PR was merged into the 3.x branch.

Discussion
----------

Fix an empty destructuring pattern triggering a PHP fatal error instead of a SyntaxError

Commits
-------

a2b023397e Fix an empty destructuring pattern triggering a PHP fatal error instead of a SyntaxError
2026-08-23 11:01:57 +02:00
Fabien Potencier a2b023397e Fix an empty destructuring pattern triggering a PHP fatal error instead of a SyntaxError 2026-08-23 09:50:46 +02:00
Fabien Potencier cd25fe5b98 feature #4893 Fix array destructuring from a Traversable (iliaal, fabpot)
This PR was merged into the 3.x branch.

Discussion
----------

Fix array destructuring from a Traversable

Sequence destructuring compiled its right-hand side straight into `array_pad()`, which only accepts arrays. Destructuring from any `Traversable` crashed with an uncaught `TypeError` at render time:

```twig
{% do [a, b] = items %}
```

with `items` being e.g. an `ArrayIterator` or a generator:

```
array_pad(): Argument #1 ($array) must be of type array, ArrayIterator given
```

This change compiles the right-hand side through an `iterator_to_array()` coercion when it is a `Traversable`, matching how spread (`[...traversable]`), `merge`, and `slice` already accept Traversables:

```php
[$context["a"], $context["b"]] = array_pad(($_v0 = ($context["items"] ?? null)) instanceof \Traversable ? iterator_to_array($_v0) : $_v0, 2, null);
```

Behavior for arrays and other types is unchanged; scalars still fail as before (now via the same `array_pad` TypeError path).

Commits
-------

e2014eb92a Fix Traversable sequence destructuring semantics
9de1b3db98 Fix array destructuring from a Traversable
2026-08-23 09:46:31 +02:00
Fabien Potencier e2014eb92a Fix Traversable sequence destructuring semantics 2026-08-23 09:40:21 +02:00
Fabien Potencier 1e239fe3ed bug #4892 Register the missing extra callables in MissingExtensionSuggestor (smnandre)
This PR was squashed before being merged into the 3.x branch.

Discussion
----------

Register the missing extra callables in MissingExtensionSuggestor

`Extensions.php` mirrors what each `twig/*-extra` package declares, so `MissingExtensionSuggestor` can answer an unknown name with `try running "composer require twig/string-extra"` rather than a bare `Unknown "slug" filter`.

This PR adds the twelve callables missing from it:
* `html-extra`: `html_attr_merge` and `html_attr_type` filters, `html_cva` and  `html_attr` functions
* `intl-extra`: `format_list` filter, `language_names`, `script_names`, `country_names`, `locale_names`, `currency_names` and `timezone_names` functions
* `string-extra`: `slug`, `plural` and `singular` filters

`ExtensionsTest` checks the catalog both ways:
* every listed name resolves on an `Environment` holding the extension,
* every declared filter, function and tag is listed

Commits
-------

4212ac1303 Test extra extension catalog against local sources
dba79330db Register the missing extra callables in MissingExtensionSuggestor
2026-08-23 09:13:19 +02:00
Fabien Potencier 4212ac1303 Test extra extension catalog against local sources 2026-08-23 09:13:10 +02:00
Simon André dba79330db Register the missing extra callables in MissingExtensionSuggestor 2026-08-23 09:12:58 +02:00
Ilia Alshanetsky 9de1b3db98 Fix array destructuring from a Traversable
Sequence destructuring compiled its right-hand side straight into
array_pad(), which throws "Argument #1 must be of type array" when a
Traversable is provided:

    {% do [a, b] = items %}

with an IteratorAggregate/Generator for items crashed with a TypeError.
Coerce Traversables via iterator_to_array() before padding, matching
the behavior of the spread, merge, and slice operations which already
accept Traversables.
2026-08-22 12:36:24 -04:00
Fabien Potencier 84c5051151 bug #4891 Fix the empty comment "{##}" being lexed as a documentation comment opening (Amoifr)
This PR was squashed before being merged into the 3.x branch.

Discussion
----------

Fix the empty comment "{##}" being lexed as a documentation comment opening

Since #4871, the empty comment `{##}` fails to lex: the source is `{#` immediately followed by the closing `#}`, but the lexer now reads it as the documentation comment opening `{##`, which steals the `#` belonging to the closing tag. The remaining input never contains a closer, so lexing fails with `Unclosed comment`, and in a larger template everything up to the next comment gets swallowed, which surfaces as confusing errors like `Expected endblock for block "form_row_render" (but "submit_row" given)`.

Minimal reproducer:

```php
(new \Twig\Environment(new \Twig\Loader\ArrayLoader()))->createTemplate('{##}')->render();
// Twig\Error\SyntaxError: Unclosed comment at line 1.
```

This is currently breaking the symfony/symfony 8.2 CI on every new run: the Twig bridge form themes use `{##}` as a line-joining trick (e.g. `bootstrap_3_horizontal_layout.html.twig`), so all form layout tests fail to compile ([example run](https://github.com/symfony/symfony/actions/runs/32267973483/job/96117211040), 547 errors).

The fix adds a negative lookahead to the documentation comment opening so that the exact sequence `{##}` keeps lexing as a regular (empty) comment. The lookahead is only built when the comment closing tag starts with `#`, so custom delimiters where the ambiguity cannot exist (and where an empty documentation comment would legitimately match the lookahead) are unaffected. `{###}` (an empty documentation comment) and `{##-#}` keep working.

Commits
-------

c9c3b23a77 Fix the empty comment "{##}" being lexed as a documentation comment opening
2026-08-19 19:01:15 +02:00
Pascal CESCON - Amoifr c9c3b23a77 Fix the empty comment "{##}" being lexed as a documentation comment opening 2026-08-19 19:01:11 +02:00
Fabien Potencier 36b8920932 feature #4871 Attach documentation comments to nodes (fabpot)
This PR was squashed before being merged into the 3.x branch.

Discussion
----------

Attach documentation comments to nodes

Alternatives to #4870

To avoid BC breaks, I have another idea, using `##` as a new syntax, a bit like `/** */` in PHP vs `/* */`.

"Documentation" is attached as metadata to the next relevant node:

```twig
{## The main content displayed on the page #}
{% block content %}
    ...
{% endblock %}
```

Documentation comments can also describe variables declared with the `types` tag:

```twig
{% types {
    ## The unique identifier of the article
    id: 'string',

    ## Whether the article should be highlighted
    featured?: 'boolean',
} %}
```

Node visitors can access this metadata through `Node::getDocumentation()`, allowing IDEs, static analyzers, and documentation generators to consume it without affecting template rendering. Documentation is preserved when visitors or optimizations replace nodes.

Closes #4768
Closes #4870

Commits
-------

6806e30474 Attach documentation comments to nodes
2026-08-19 16:41:26 +02:00
Fabien Potencier 6806e30474 Attach documentation comments to nodes 2026-08-19 16:41:17 +02:00
Fabien Potencier 9036d8c5aa feature #4434 Add the include_only function (fabpot)
This PR was merged into the 3.x branch.

Discussion
----------

Add the include_only function

Commits
-------

dc8f96df3e Add the include_only function to render a template without access to the current context
2026-08-16 10:53:58 +02:00
Fabien Potencier 2bb20e283f feature #4890 Add support for tempest/markdown in markdown-extra (ker0x)
This PR was merged into the 3.x branch.

Discussion
----------

Add support for tempest/markdown in markdown-extra

Adds `TempestMarkdown`, an adapter for [`tempest/markdown`](https://github.com/tempestphp/markdown), alongside the existing `LeagueMarkdown`, `MichelfMarkdown` and `ErusevMarkdown` implementations. It follows the same pattern as the others and accepts a pre-configured `Tempest\Markdown\Markdown` in its constructor, so rules and the highlighter can be customized.

It is also appended as the **last** branch of `DefaultMarkdown`'s discovery chain, so projects that already have another library installed keep resolving to it exactly as before.

### PHP requirement

Every published version of `tempest/markdown` requires PHP `^8.5`, while `twig/markdown-extra` supports `>=8.1`. So:

- it is declared in `require-dev` only;
- a CI step removes it before `composer install` on PHP < 8.5, mirroring the existing conditional step used for `twig-extra-bundle`;
- `FunctionalTest` only adds it to the converter matrix when `Tempest\Markdown\Markdown` exists.

The suite passes both with and without the library installed.

### Test data change

Three cases in `getMarkdownTests()` used Setext headings (`Hello` underlined with `=====`). `tempest/markdown` only implements ATX headings, so those were switched to `# Hello`. Those cases exercise the filter plumbing (`{% apply %}`, indentation stripping, `include()|markdown_to_html`) rather than the Markdown dialect, so no coverage is lost.

Two patterns were also relaxed for the same reason: `<h1[^>]*>` because Tempest emits auto heading ids, and `<p>…\s*</p>` because it keeps the source's trailing newline inside the final paragraph. Both remain accurate for the other converters.

These differences, plus the fact that front matter is parsed out rather than rendered, are documented in a note in `doc/filters/markdown_to_html.rst`.

### Unrelated one-liner

The last commit also adds `.php-cs-fixer.cache` to `.gitignore` — it is generated by the project's own `php-cs-fixer` dev dependency and was showing up as untracked. Happy to split it out if you'd rather keep this PR to a single concern.

Commits
-------

aa17f59877 Add support for tempest/markdown in markdown-extra
2026-08-13 10:02:40 +02:00
Romain Monteil aa17f59877 Add support for tempest/markdown in markdown-extra 2026-08-12 13:02:21 +02:00
Fabien Potencier dc8f96df3e Add the include_only function to render a template without access to the current context 2026-08-11 20:58:31 +02:00
Fabien Potencier 6fafa104c4 tidy #4883 Clarify duplicate macro deprecation message (fabpot)
This PR was merged into the 3.x branch.

Discussion
----------

Clarify duplicate macro deprecation message

Commits
-------

8ca4866a95 Clarify duplicate macro deprecation message
2026-08-03 21:22:23 +02:00
Fabien Potencier e1bc2814d5 tidy #4882 Deduplicate template error handling (fabpot)
This PR was merged into the 3.x branch.

Discussion
----------

Deduplicate template error handling

Commits
-------

e62abbf528 Deduplicate template error handling
2026-08-03 18:11:07 +02:00
Fabien Potencier 8ca4866a95 Clarify duplicate macro deprecation message 2026-08-03 17:35:03 +02:00
Fabien Potencier e62abbf528 Deduplicate template error handling 2026-08-03 17:28:46 +02:00
Fabien Potencier 2a1df4e64f feature #4881 Nested macro imports (fabpot)
This PR was squashed before being merged into the 3.x branch.

Discussion
----------

Nested macro imports

Closes #4879
Replaces #4880

Commits
-------

b53e100444 Nested macro imports
2026-08-03 10:30:38 +02:00
Fabien Potencier b53e100444 Nested macro imports 2026-08-03 10:30:32 +02:00
Fabien Potencier 7793b6153f feature #4878 Deprecate using parentheses when testing a macro with the defined test (fabpot)
This PR was squashed before being merged into the 3.x branch.

Discussion
----------

Deprecate using parentheses when testing a macro with the defined test

Commits
-------

34d9c67d38 Deprecate using parentheses when testing a macro with the defined test
2026-07-31 12:09:26 +02:00
Fabien Potencier 34d9c67d38 Deprecate using parentheses when testing a macro with the defined test 2026-07-31 12:09:23 +02:00
Fabien Potencier 6dfc7961a2 feature #4851 Redesign macro calls and argument handling (fabpot)
This PR was squashed before being merged into the 3.x branch.

Discussion
----------

Redesign macro calls and argument handling

This is my attempt to make macros "better". It uses modern PHP features that didn't exist when I designed macros a long time ago.

The first objective is to close the gap between their behavior and the behavior of Twig callables: functions, filters, and tests.

Here are some important changes:

* Calling a macro without passing a value for an argument that has no default value is deprecated; it is currently silently passed as `null`.
* Passing extra positional arguments or unknown named arguments to a macro without an explicit variadic argument is deprecated; these arguments are currently silently accepted through the implicit `varargs` variable.
* Explicit variadic macro arguments are now supported with `...name`.
* Macros are compiled as closures stored in the macro registry, instead of public generated `macro_*` methods.

The refactor introduces `TwigMacro` and `MacroArgument` to represent template-defined macros with an explicit signature, similar to the existing Twig callable model.

Commits
-------

d7f8b4eb1c Redesign macro calls and argument handling
2026-07-30 13:53:27 +02:00
Fabien Potencier d7f8b4eb1c Redesign macro calls and argument handling 2026-07-30 13:53:24 +02:00
Fabien Potencier e315d6d396 feature #4854 Make the sandbox a first-class citizen with a dedicated Sandbox class (fabpot)
This PR was squashed before being merged into the 3.x branch.

Discussion
----------

Make the sandbox a first-class citizen with a dedicated Sandbox class

I've been thinking about making the sabdbox feature as a first class citizen for years. With all the work that has been done recently on security issues, I spent some time on it again. Here is the result.

The main ideas:

* Currently, the sandbox is thigtly coupled to the "main" environment: `SandboxExtension` is registered on the environmen directly, so it instruments all compiled template, and adds runtime checks to all renders, trusted or not. As recommended in the docs, you should have a dedicated environment for sandboxes, different from the main one, but it's not really "enforced" nor natural to do.
* As a consequence, we store some state via `enableSandbox()`/`disableSandbox()` with try/finally patterns scattered across the codebase to support rendering sandboxed and non-sandboxed templates from a environment.
* When using one environment, a sandboxed template can `include` anything the loader can load, sees every application global, and inherits all extensions, this is a footgun (again, already not recommended in the docs).
* There are too maybe "knobs": global mode, `enableSandbox()`, `{% include(..., sandboxed: true) %}`, and `{% sandbox %}`.

The new `Twig\Sandbox\Sandbox` class renders untrusted templates through a dedicated, always-sandboxed environment crafted by the developer. Taht way, there is no state to toggle and nothing leaks between the main environment and the sandbox, in either direction.

Commits
-------

b762bc94b9 Make the sandbox a first-class citizen with a dedicated Sandbox class
2026-07-30 12:05:54 +02:00
Fabien Potencier b762bc94b9 Make the sandbox a first-class citizen with a dedicated Sandbox class 2026-07-30 12:05:47 +02:00
Fabien Potencier 5bbbe0d8d6 feature #4877 Deprecate macro calls without parentheses (fabpot)
This PR was merged into the 3.x branch.

Discussion
----------

Deprecate macro calls without parentheses

Commits
-------

ad305b414e Deprecate macro calls without parentheses
2026-07-28 16:24:59 +02:00
Fabien Potencier ad305b414e Deprecate macro calls without parentheses 2026-07-28 16:13:23 +02:00