This PR was merged into the 8.1 branch.
Discussion
----------
[Contracts] Add `ContainerAwareInterface`
| Q | A
| ------------- | ---
| Branch? | 8.1
| Bug fix? | no
| New feature? | yes
| Deprecations? | no
| Issues | -
| License | MIT
Related to https://github.com/symfony/symfony/pull/63650.
This PR adds a `ContainerAwareInterface` to `symfony/service-contracts`, providing a standard way for objects to expose their service container.
The immediate use case is the Messenger component: parallel workers need to bootstrap the application and access the container to resolve the message bus. Without a contract, there's no reliable way to get the container from the console `Application` object in a decoupled manner.
The interface is intentionally minimal — a single `getContainer(): ContainerInterface` method — and `FrameworkBundle\Console\Application` implements it, booting the kernel if needed.
Commits
-------
8c158e05207 [Contracts] Add `ContainerAwareInterface`
This PR was merged into the 8.1 branch.
Discussion
----------
[VarExporter] Add `DeepCloner` for COW-friendly deep cloning
| Q | A
| ------------- | ---
| Branch? | 8.1
| Bug fix? | no
| New feature? | yes
| Deprecations? | no
| Issues | 🐮
| License | MIT
The traditional way to deep-clone a PHP value is `unserialize(serialize($value))`. This works but breaks copy-on-write: every string and every scalar-only array is reallocated, even when the clone is never mutated.
This PR adds `DeepCloner`, which reuses the VarExporter graph-analysis infrastructure (`Exporter::prepare()`, `Registry`, `Hydrator`) to reconstruct a fresh object graph without serializing to string. Strings and scalar-only arrays are shared via PHP's copy-on-write mechanism, resulting in significant memory savings and better performance.
### API
```php
// One-shot deep clone
$clone = DeepCloner::deepClone($value);
// Repeated cloning (amortizes graph analysis)
$cloner = new DeepCloner($prototype);
$clone1 = $cloner->clone();
$clone2 = $cloner->clone();
// Check if cloning is needed (scalars, enums, scalar-only arrays)
$cloner->isStaticValue(); // true = no cloning needed
// Clone as a compatible class (e.g. cast Definition → ChildDefinition)
$child = $cloner->cloneAs(ChildDefinition::class);
```
The class is ``@template` T` so static analysis tools can infer the return type from the input.
### Benchmarks
10 000 iterations, clone-only (amortized `new DeepCloner` cost excluded):
| Case | `clone()` | `unserialize(serialize())` | Ratio |
|---|---|---|---|
| simple stdClass | 3 ms | 5.3 ms | **0.56x** |
| stdClass + 10 KiB string | 2.7 ms | 10.2 ms | **0.27x** (4x faster) |
| 100 objects, 4 props each | 152 ms | 604 ms | **0.25x** (4x faster) |
| 50 objects, 20 props each | 83 ms | 1212 ms | **0.07x** (15x faster) |
| 10 levels deep nesting | 37 ms | 47 ms | **0.79x** |
| DateTime | 22 ms | 29 ms | **0.76x** |
| mixed array (nested objects) | 8.3 ms | 9.6 ms | **0.86x** |
| large (~12 MiB strings/arrays) | 0.2 ms | 2429 ms | **~0x** (12 000x faster) |
`clone()` is faster than `unserialize(serialize())` in every case. The benefit grows with:
- **COW-shareable content** — strings and scalar-only arrays are never copied, just shared;
- **Number of properties** — the clone-and-patch strategy clones original objects and only patches object-reference properties, skipping all scalar hydration.
### How it works
1. **Graph analysis** (`new DeepCloner`): `Exporter::prepare()` walks the value graph once, identifying objects, references, and property scopes. Properties are stored in a pivoted format (`$scope → $name → [$id → $value]`) that deduplicates property names across objects. A resolve map pre-identifies which properties contain object references.
2. **Clone-and-patch** (`clone()`): When all objects in the graph support safe cloning (no `__clone`, `__wakeup`, or `__unserialize`), original objects are kept and `clone`d on each call. PHP's native clone COW-shares all scalar properties instantly. Only properties flagged in the resolve map — those holding object references — are resolved and hydrated via scope-bound closures. For 50 objects × 20 properties where only ~2 hold object references, this means patching ~100 properties instead of hydrating 1000.
3. **Fallback**: For object graphs containing `__clone`/`__wakeup`/`__unserialize`/internal classes, full hydration is used (create empty objects + set all properties).
### Adoption
Replaced `unserialize(serialize())` in:
- **DependencyInjection**: `ResolveDecoratorStackPass`, `ResolveInstanceofConditionalsPass` (using `cloneAs()` to replace the `serialize()`+byte-manipulation hack for casting `Definition` → `ChildDefinition`), `ContentLoaderTrait`, `ServicesConfigurator`, `PrototypeConfigurator`
- **FrameworkBundle**: `ContainerBuilderDebugDumpPass`
- **Form**: `SessionDataStorage`
Refactored **ArrayAdapter** (`Cache`) to use `DeepCloner` internally instead of `serialize()`/`unserialize()` for its `storeSerialized` mode, simplifying the `freeze()`/`unfreeze()` logic while preserving `getValues()` compatibility.
### Compact serialization
`DeepCloner` instances are themselves serializable with a compact `__serialize()`/`__unserialize()` representation.
Three optimizations make the serialized form smaller than `serialize($value)` in most cases:
1. **Class name deduplication** — each unique class name is stored once in a `$classes` list; per-object entries use integer indices instead of repeating the FQCN. When all objects share one class (common case), `$objectMeta` is stored as a single integer count.
2. **Reference compaction** — internal `Reference` objects (used to track object identity and hard PHP references) are replaced with plain integer ids. For `$properties`, the `$resolve` map provides out-of-band position tracking, so References are safely replaced with their int id and restored on unserialize. For `$prepared`, a parallel `$mask` structure records which positions hold References, avoiding any in-band signaling that could collide with user data. For the common single-root-object case, the root reference is stored as a plain integer with no mask needed.
3. **Property name deduplication** — the pivoted property storage (`$scope → $name → [$id → $value]`) stores each property name once regardless of how many objects share it.
Serialized payload size comparison:
| | `serialize($value)` | `serialize(DeepCloner)` | Ratio |
|---|---|---|---|
| 100 objs, 4 long prop names | 19,423 | 6,922 | **0.36x** |
| 100 objs, 4 short prop names | 8,323 | 6,811 | **0.82x** |
| 50 objs × 20 props | 34,137 | 12,480 | **0.37x** |
| 100 typed objs × 15 props | 46,748 | 22,400 | **0.48x** |
### VarExporter infrastructure improvements
While building `DeepCloner`, several performance improvements were made to the shared infrastructure:
- **`Exporter`**: Added per-class caches for property scope maps (`$scopeMaps`), array-cast prototypes (`$protos`), and method existence checks (`$classInfo` for `__serialize`/`__unserialize`/`__wakeup`/`__sleep`). These avoid repeated reflection walks and `method_exists()` calls when processing multiple objects of the same class.
- **`Hydrator`**: `getHydrator()`, `getSimpleHydrator()`, and `getPropertyScopes()` now reuse `Registry::$reflectors` instead of creating duplicate `ReflectionClass` instances, sharing a single cache across the component.
Also updated the component's `composer.json` description and `README.md` to reflect the current scope of the component.
Commits
-------
fd3731a406f [VarExporter] Add `DeepCloner` to deep-clone PHP values while preserving copy-on-write benefits
* 8.0:
[JsonStreamer] Fix lazy instantiation for internal PHP classes
[Config] Fix invalid array-shape
[Dotenv] Fix double-unescaping of backslashes during deferred variable resolution
[Process] Throw InvalidArgumentException when env block exceeds Windows limit
[Dotenv] Fix escaped dollar signs lost during deferred variable resolution
[HttpKernel] Set propertyPath on MapUploadedFile validation violations
Bump Symfony version to 8.0.8
Update VERSION for 8.0.7
Update CHANGELOG for 8.0.7
Bump Symfony version to 7.4.8
Update VERSION for 7.4.7
Update CHANGELOG for 7.4.7
Bump Symfony version to 6.4.36
Update VERSION for 6.4.35
Update CONTRIBUTORS for 6.4.35
Update CHANGELOG for 6.4.35
* 7.4:
[JsonStreamer] Fix lazy instantiation for internal PHP classes
[Config] Fix invalid array-shape
[Dotenv] Fix double-unescaping of backslashes during deferred variable resolution
[Process] Throw InvalidArgumentException when env block exceeds Windows limit
[Dotenv] Fix escaped dollar signs lost during deferred variable resolution
[HttpKernel] Set propertyPath on MapUploadedFile validation violations
Bump Symfony version to 7.4.8
Update VERSION for 7.4.7
Update CHANGELOG for 7.4.7
Bump Symfony version to 6.4.36
Update VERSION for 6.4.35
Update CONTRIBUTORS for 6.4.35
Update CHANGELOG for 6.4.35
* 6.4:
[Dotenv] Fix double-unescaping of backslashes during deferred variable resolution
[Dotenv] Fix escaped dollar signs lost during deferred variable resolution
Bump Symfony version to 6.4.36
Update VERSION for 6.4.35
Update CONTRIBUTORS for 6.4.35
Update CHANGELOG for 6.4.35
This PR was merged into the 8.1 branch.
Discussion
----------
[HttpKernel] Adding new `#[MapRequestHeader]` attribute and resolver
| Q | A
| ------------- | ---
| Branch? | 8.1
| Bug fix? | no
| New feature? | yes
| Deprecations? | no
| Tickets | #51342
| License | MIT
| Doc PR | #20541
This adds a `#[MapRequestHeader]` attribute to map HTTP request headers to controller arguments, similar to how `#[MapQueryParameter]` works for query parameters.
```php
#[Route('/')]
public function index(
#[MapRequestHeader] string $accept,
#[MapRequestHeader] array $acceptLanguage,
#[MapRequestHeader('user-agent')] string $userAgent,
#[MapRequestHeader] ?string $xCustom,
#[MapRequestHeader] AcceptHeader $acceptEncoding,
): Response {
// ...
}
```
Supported types are string, array and AcceptHeader.
When the header name is not explicitly provided, the argument name is converted from camelCase to kebab-case (e.g. `$acceptLanguage` for `accept-language`).
When typed as array, the arguments received all corresponding headers.
Missing required headers throw an HttpException with a configurable status code (defaults to 400).
Commits
-------
a720fbafcce [HttpKernel] Add `#[MapRequestHeader]` attribute and resolver
This PR was merged into the 8.1 branch.
Discussion
----------
[JsonStreamer] Handle value objects
| Q | A
| ------------- | ---
| Branch? | 8.1
| Bug fix? | no
| New feature? | yes
| Deprecations? | yes
| Issues |
| License | MIT
Introduce `ValueObjectTransformerInterface`, a generic mechanism for **handling value objects**: objects that should be serialized to/from a scalar JSON value rather than being traversed property by property.
Until now, `DateTimeInterface` was handled through a dedicated `DateTimeNode` data model node.
This special-casing approach doesn't scale: any new value object type (like `Money`, `Uuid`, any custom domain type) would require adding another dedicated node class, another metadata loader, and wiring them everywhere.
Therefore, this PR adds a new `ValueObjectTransformerInterface` that allow defining how any object type maps to/from a scalar value:
```php
use Symfony\Component\JsonStreamer\Transformer\ValueObjectTransformerInterface;
use Symfony\Component\TypeInfo\Type;
use Symfony\Component\TypeInfo\Type\BuiltinType;
/**
* `@implements` ValueObjectTransformerInterface<Money, string>
*/
class MoneyValueObjectTransformer implements ValueObjectTransformerInterface
{
public function transform(object $object, array $options = []): int|float|string|bool|null
{
return $object->amount.' '.$object->currency; // Money(100, 'EUR') -> "100 EUR"
}
public function reverseTransform(int|float|string|bool|null $scalar, array $options = []): object
{
[$amount, $currency] = explode(' ', $scalar);
return new Money((int) $amount, $currency); // "100 EUR" -> Money(100, 'EUR')
}
public static function getStreamValueType(): BuiltinType
{
return Type::string();
}
public static function getValueObjectClassName(): string
{
return Money::class;
}
}
```
The generated PHP code then calls the transformer instead of traversing the object's properties:
```php
// Write: "100 EUR"
$writer->write(new Money(100, 'EUR'), Type::object(Money::class));
// Read: "100 EUR" -> Money(100, 'EUR')
$reader->read('"100 EUR"', Type::object(Money::class));
```
To do so:
- Added a `ValueObjectTransformerInterface` which is the contract for any value object transformer
- `ValueTransformerInterface` was renamed to `PropertyValueTransformerInterface` to avoid confusion with `ValueObjectTransformerInterface`
- `DateTimeInterface` value transformers have been reimplemented in `DateTimeValueObjectTransformer`
- A `TransformerPass` compiler pass was created to collect tagged `json_streamer.property_transformer` and `json_streamer.value_object_transformer` services into a single `ServiceLocator` shared by the reader, writer, and cache warmer
- `DateTimeNode` was removed and replaced by a regular `ObjectNode` (+ value object transformer lookup)
- `$valueTransformers` renamed to `$transformers` throughout (the container now holds both property transformers and value object transformers)
- Both `PhpGenerator` classes now receive a `ContainerInterface` $transformers and use it to detect value objects during code generation via class hierarchy walking
Commits
-------
8cf1714ea1c [JsonStreamer] Handle value objects
This PR was merged into the 8.1 branch.
Discussion
----------
[Uid] Add Uuid47Transformer support for UUIDv7/v4 conversion
| Q | A
| ------------- | ---
| Branch? | 8.1
| Bug fix? | no
| New feature? | yes
| Deprecations? | no
| Issues |
| License | MIT
This PR adds `Uuid47Transformer` to the Uid component, a converter between UUIDv7 and UUIDv4 inspired by the [uuid47 Go implementation](https://github.com/n2p5/uuid47), itself derived from the [reference C implementation](https://github.com/stateless-me/uuidv47).
It allows storing time-ordered UUIDv7 in databases while emitting UUIDv4-looking identifiers at API boundaries, hiding timing information from external consumers. The 48-bit timestamp of a UUIDv7 is XOR-masked with a keyed SipHash-2-4 digest (via `sodium_crypto_shorthash`) derived from the UUID's own random bits, producing a valid UUIDv4. The transformation is reversible with the same secret.
The implementation is 32-bit safe; the XOR masking operates at the byte level without any 64-bit integer arithmetic.
When the Uid component is used with FrameworkBundle, `Uuid47Transformer` is registered as a service using `kernel.secret` for the key.
(fabbot failure is a false-positive)
Commits
-------
9111f9bacce [Uid] Add Uuid47Transformer support for UUIDv7/v4 conversion
Responses without an explicit freshness lifetime (no max-age, s-maxage
or Expires header) and where heuristic freshness did not apply were
previously stored with no expiration date, resulting in items that
never self-expired.
This change ensures every cached response now receives a finite usable
TTL (max-age + stale-while-revalidate + stale-if-error) and is
guaranteed to self-expire after that lifetime:
- Responses with a computed usable TTL of zero are no longer stored
- A safety buffer is always added when a positive TTL exists
* 8.0:
[HttpFoundation] Fix session cookie_lifetime not applied in mock session storage
[Validator] Fix test
[Serializer] Fix denormalization of magic `__set` properties
[Config] Fix NodeDefinition template to be covariant
Add 'sms' to hostless schemes
[Validator] Fix required options check when extending a constraint with a simplified constructor
[Validator] Skip ExpressionLanguage requirement in When constraint for closure expressions
[Cache] Add timeout and slot eviction to LockRegistry stampede prevention
[Console] Fix OUTPUT_RAW corrupting binary content on Windows
[Form] Fix session data contamination by non-serializable objects in form flow
[Mime] Use shell_exec() instead of passthru() in FileBinaryMimeTypeGuesser
[HttpClient] Fix streaming from CachingHttpClient
[DoctrineBridge] Rename `_schema_subscriber_check` table to `schema_subscriber_check_` for Oracle compatibility
[HttpClient] Fix CachingHttpClient compatibility with decorator clients on 304 responses
[FrameworkBundle] Fix stale container after reboot in KernelTestCase
[Form] Fix duplicate validation errors when ValidatorExtension is instantiated multiple times
* 7.4:
[HttpFoundation] Fix session cookie_lifetime not applied in mock session storage
[Validator] Fix test
[Serializer] Fix denormalization of magic `__set` properties
[Config] Fix NodeDefinition template to be covariant
Add 'sms' to hostless schemes
[Validator] Fix required options check when extending a constraint with a simplified constructor
[Validator] Skip ExpressionLanguage requirement in When constraint for closure expressions
[Cache] Add timeout and slot eviction to LockRegistry stampede prevention
[Console] Fix OUTPUT_RAW corrupting binary content on Windows
[Form] Fix session data contamination by non-serializable objects in form flow
[Mime] Use shell_exec() instead of passthru() in FileBinaryMimeTypeGuesser
[HttpClient] Fix streaming from CachingHttpClient
[DoctrineBridge] Rename `_schema_subscriber_check` table to `schema_subscriber_check_` for Oracle compatibility
[HttpClient] Fix CachingHttpClient compatibility with decorator clients on 304 responses
[FrameworkBundle] Fix stale container after reboot in KernelTestCase
[Form] Fix duplicate validation errors when ValidatorExtension is instantiated multiple times
* 6.4:
[FrameworkBundle] Fix stale container after reboot in KernelTestCase
[Form] Fix duplicate validation errors when ValidatorExtension is instantiated multiple times
* 8.0:
[HttpKernel] Fix int-to-float coercion for JSON #[MapRequestPayload] with pre-parsed array data
[Messenger] Flush batch handlers after inactivity timeout when worker is busy
[PhpUnitBridge] fix tests
[TypeInfo] Fix StringTypeResolver calling Type::enum() on interfaces extending BackedEnum
[FrameworkBundle] Remove dead code related to removed XML configuration
[Mailer] [Mailjet] Fix inline attachments with custom Content-ID.
[RateLimiter] Ensure hit count is always positive
This PR was merged into the 8.1 branch.
Discussion
----------
[HttpFoundation] Deprecate setting public properties of `Request` and `Response` objects directly
| Q | A
| ------------- | ---
| Branch? | 8.1
| Bug fix? | no
| New feature? | yes
| Deprecations? | yes
| Issues | -
| License | MIT
PHP 8.4 property hooks allow adding a set hook to existing properties without breaking the public API. This PR leverages that to deprecate external direct assignments to Request and Response properties.
The goal is to make our beloved `Request` and `Response` objects safer by using `public private(set)` in 9.0.
That's only for public properties, which are the ones that hold value objects (`ParameterBag`/`HeaderBag`) which shouldn't be reassigned at will.
Commits
-------
8b2209e3972 [HttpFoundation] Deprecate setting public properties of `Request` and `Response` objects directly
This PR was squashed before being merged into the 8.1 branch.
Discussion
----------
[Console] Replace executeCommand() by runCommand() when testing commands
| Q | A
| ------------- | ---
| Branch? | 8.1
| Bug fix? | no
| New feature? | yes
| Deprecations? | no
| Issues | -
| License | MIT
Command testing suffered from two problems:
```php
self::bootKernel();
$application = new Application(self::$kernel);
$command = $application->find('app:create-user');
$commandTester = new CommandTester($command);
$commandTester->execute('email' => 'foo@bar.com');
```
Problem 1: setup is too verbose
Problem 2: it doesn't return a result object with useful utilities (`ExecutionResult`)
In 8.1 we added two methods that each solve one of these problems:
**(1)** `KernelTestCase::executeCommand()` removes the setup verbosity, but returns a `CommandTester`, forcing users back into the legacy stateful API:
```php
$tester = static::executeCommand('app:create-user', ['email' => 'foo@bar.com']);
$tester->assertCommandIsSuccessful();
$this->assertStringContainsString('User created', $tester->getDisplay());
```
**(2)** `CommandTester::run()` returns a useful object (`ExecutionResult`) but requires verbose kernel/application/command setup:
```php
$application = new Application(static::getContainer()->get('kernel'));
$command = $application->find('app:create-user');
$commandTester = new CommandTester($command);
$result = $commandTester->run(['email' => 'foo@bar.com']);
```
This PR replaces `executeCommand()` with `runCommand()` to combine both solutions: simple to call and returns `ExecutionResult`:
```php
$result = static::runCommand('app:create-user', ['email' => 'foo@bar.com']);
$this->assertIsSuccessful($result);
$this->assertStringContainsString('User created', $result->getOutput());
```
Since `executeCommand()` was added in 8.1 (unreleased), no deprecation is needed for the rename.
Commits
-------
fe2df99a704 [Console] Replace executeCommand() by runCommand() when testing commands
* 8.0: (26 commits)
Fix merge
[RateLimiter] Fix retryAfter when consuming exactly all remaining tokens in FixedWindow and TokenBucket
[RateLimiter] Fix retryAfter value on last token consume (SlidingWindow)
[RateLimiter] Fix reservations outside the second fixed window
[Filesystem] makePathRelative with existing files, remove ending /
[Config][Routing] Fix exclude option being ignored for non-glob and PSR-4 resources
[Serializer][Validator] Fix propertyPath in ConstraintViolationListNormalizer with MetadataAwareNameConverter
[Messenger][Amqp] Don't use retry routing key when sending to failure transport
[Messenger] Fix re-sending failed messages to a different failure transport
[DependencyInjection] Fix #[AsTaggedItem] discovery through multi-level decoration chains
[Config] Fix ArrayShapeGenerator required keys with deep merging
[Validator] Add a guard when `Parser::IGNORE_UNKNOWN_VARIABLES` is not defined
[Validator] Correctly handle null `allowedVariables` in `ExpressionSyntaxValidator`
[DependencyInjection] Fix PriorityTaggedServiceTrait not discovering #[AsTaggedItem] on decorated services
[Mailer] Clarify the purpose of SentMessage's "message id" concept
[ObjectMapper] fix nested mapping with class-level transform
[TwigBridge] Fix Bootstrap 4 form error layout
[Form] Fix merging POST params and files when collection entries have mismatched indices
[Validator] Fix type error for non-array items when Unique::fields is set
[HttpKernel] Fix default locale ignored when Accept-Language has no enabled-locale match
...
* 7.4: (25 commits)
[RateLimiter] Fix retryAfter when consuming exactly all remaining tokens in FixedWindow and TokenBucket
[RateLimiter] Fix retryAfter value on last token consume (SlidingWindow)
[RateLimiter] Fix reservations outside the second fixed window
[Filesystem] makePathRelative with existing files, remove ending /
[Config][Routing] Fix exclude option being ignored for non-glob and PSR-4 resources
[Serializer][Validator] Fix propertyPath in ConstraintViolationListNormalizer with MetadataAwareNameConverter
[Messenger][Amqp] Don't use retry routing key when sending to failure transport
[Messenger] Fix re-sending failed messages to a different failure transport
[DependencyInjection] Fix #[AsTaggedItem] discovery through multi-level decoration chains
[Config] Fix ArrayShapeGenerator required keys with deep merging
[Validator] Add a guard when `Parser::IGNORE_UNKNOWN_VARIABLES` is not defined
[Validator] Correctly handle null `allowedVariables` in `ExpressionSyntaxValidator`
[DependencyInjection] Fix PriorityTaggedServiceTrait not discovering #[AsTaggedItem] on decorated services
[Mailer] Clarify the purpose of SentMessage's "message id" concept
[ObjectMapper] fix nested mapping with class-level transform
[TwigBridge] Fix Bootstrap 4 form error layout
[Form] Fix merging POST params and files when collection entries have mismatched indices
[Validator] Fix type error for non-array items when Unique::fields is set
[HttpKernel] Fix default locale ignored when Accept-Language has no enabled-locale match
[FrameworkBundle] Make `ConfigDebugCommand` use its container to resolve env vars
...
* 6.4:
[RateLimiter] Fix retryAfter when consuming exactly all remaining tokens in FixedWindow and TokenBucket
[RateLimiter] Fix retryAfter value on last token consume (SlidingWindow)
[RateLimiter] Fix reservations outside the second fixed window
[Filesystem] makePathRelative with existing files, remove ending /
[Config][Routing] Fix exclude option being ignored for non-glob and PSR-4 resources
[Serializer][Validator] Fix propertyPath in ConstraintViolationListNormalizer with MetadataAwareNameConverter
[Messenger][Amqp] Don't use retry routing key when sending to failure transport
[Messenger] Fix re-sending failed messages to a different failure transport
[DependencyInjection] Fix #[AsTaggedItem] discovery through multi-level decoration chains
[DependencyInjection] Fix PriorityTaggedServiceTrait not discovering #[AsTaggedItem] on decorated services
[Mailer] Clarify the purpose of SentMessage's "message id" concept
[TwigBridge] Fix Bootstrap 4 form error layout
[Form] Fix merging POST params and files when collection entries have mismatched indices
[Validator] Fix type error for non-array items when Unique::fields is set
[HttpKernel] Fix default locale ignored when Accept-Language has no enabled-locale match
[FrameworkBundle] Make `ConfigDebugCommand` use its container to resolve env vars
[Console] Fix various completion edge cases
[Messenger][AmazonSqs] Add test for default queue_name when not set in DSN path or options
This PR was merged into the 8.1 branch.
Discussion
----------
[Messenger] Route decode failures through failure handling
| Q | A
| ------------- | ---
| Branch? | 8.1
| Bug fix? | no
| New feature? | yes
| Deprecations? | no
| Issues | Fix#44117, also related to #39622
| License | MIT
Previously, when a transport could not decode a message, `MessageDecodingFailedException` was thrown inside receivers' `get()`, causing the message to be deleted from the queue with no retry or DLQ routing.
This PR routes decode failures through the normal failure-handling path instead of silently losing messages.
This changes a bit the contracts of serializers: they should now return `Envelope<MessageDecodingFailedException>` on decode failure, wrapping the raw encoded envelope. Throwing is still supported for BC with custom serializers - receivers catch and wrap it as a fallback.
A new `DecodeFailedMessageMiddleware` sits early in the default middleware stack (before `failed_message_processing_middleware`). When it encounters an envelope whose message is a `MessageDecodingFailedException`, it:
1. Determines the original transport name to look up the correct serializer.
2. Re-decodes the raw payload; if decoding still fails, the `MessageDecodingFailedException` is thrown so that standard retry/DLQ handling kicks in.
3. If decoding succeeds (e.g. after a deployment that adds the missing class), merges all stamps from the wrapping envelope onto the freshly decoded one and continues through the stack normally.
Receivers no longer delete-then-throw. Instead they yield the wrapped envelope with the same stamps as a successful decode, so `ack()`/`reject()` work correctly downstream. The old catch-and-throw path is kept only as a BC fallback for custom serializers that still throw.
A message that cannot be decoded is therefore no longer lost. It travels through the bus as a `MessageDecodingFailedException`, hits the failure/retry infrastructure like any other failed message, and is automatically retried (triggering `DecodeFailedMessageMiddleware` again) once the serializer or class definition is fixed.
Commits
-------
37c7e90ee72 [Messenger] Route decode failures through failure handling
This PR was merged into the 8.1 branch.
Discussion
----------
[Console][WebProfilerBundle] Trace argument value resolvers
| Q | A
| ------------- | ---
| Branch? | 8.1
| Bug fix? | no
| New feature? | yes
| Deprecations? | no
| Issues | -
| License | MIT
Comes in handy to spot heavy resolvers
<img width="971" height="749" alt="Screenshot 2026-02-20 at 18 36 25" src="https://github.com/user-attachments/assets/8f184189-0419-4652-ade1-33f47cbf1429" />
Commits
-------
85f83722454 [Console][WebProfilerBundle] Trace value resolvers in profiler