Every Symfony release ships dozens of small developer experience (DX) improvements that make day-to-day work more pleasant. This article highlights some of those improvements in Symfony 8.1.
Copy Requests as cURL Commands
When debugging an issue, you often need to replay a request outside the browser to
tweak it or share it with a colleague. The profiler already shows every detail of
the request, but rebuilding a curl command by hand from its headers, cookies,
and body is tedious and error-prone.
Symfony 8.1 adds a "Copy as cURL" button to the Request/Response tab of the profiler. It generates a ready-to-run command that includes the HTTP method, full URL, request headers, cookies, and body for write requests. Paste it into your terminal to reproduce the exact same request in seconds.
A More Accessible Web Debug Toolbar
The Web Debug Toolbar revealed its panels only on mouse hover, making the information within them inaccessible to keyboard and screen reader users.
Symfony 8.1 makes the toolbar fully keyboard-navigable and adds the WAI-ARIA
semantics that assistive technologies rely on. You can now move between items with
the arrow keys, open a panel with the Down arrow, and close it with Escape. Panels
also open on keyboard focus, not just on hover, and each item exposes a descriptive
label and the proper toolbar and dialog roles.
Sorting Routes in the debug:router Command
The debug:router command lists routes in the order Symfony evaluates them,
making it hard to locate a specific route in a large application. Sorting the list
meant piping the output through tools like sort, which broke the clickable
links to the route definitions.
Symfony 8.1 adds a --sort option to sort routes by any column: name,
path, method, scheme, or host:
1 2 3 4
$ php bin/console debug:router --sort=path
# the option is case-insensitive and works for all output formats
$ php bin/console debug:router --sort=name --format=json
Sorting Scheduled Messages by Next Run Date
When you have many scheduled tasks, the debug:scheduler command lists them in
the order they were defined, making it hard to tell which one runs next or spot
tasks that fire at the same time.
Symfony 8.1 adds a --sort option that orders recurring messages by their next
run date, so the task that runs soonest appears first:
1
$ php bin/console debug:scheduler --sort
Matching Transport Names with Regular Expressions
The messenger:consume command expects you to list the transports to consume
by their exact names. In applications with many similarly named transports (for
example, one transport per scheduler), spelling out every name is tedious.
In Symfony 8.1, the transport names argument also accepts regular expressions, which are matched against configured transport names:
1 2 3 4 5 6 7 8
# consume all transports whose name starts with "scheduler_"
$ php bin/console messenger:consume 'scheduler_.*'
# use the (?i) flag for case-insensitive matching
$ php bin/console messenger:consume '(?i)async.*'
# you can still mix regular expressions and explicit names
$ php bin/console messenger:consume 'scheduler_.*' async
Each pattern is anchored (it must match the full transport name), and matched transports are consumed in their configuration order. Regular expressions are only supported when the command runs non-interactively.
Mocking Non-Shared Services in Tests
In functional tests, you can replace a service with a test double by calling
getContainer()->set(). Until now this only worked for shared services;
non-shared services, which are created fresh every time they are requested, could
not be replaced.
Symfony 8.1 lifts that restriction. Because a non-shared service returns a new instance on each request, you replace it with a closure that acts as a factory instead of a single object. The closure runs every time the service is fetched:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
class OrderProcessorTest extends KernelTestCase
{
public function testProcess(): void
{
$container = static::getContainer();
$container->set('app.payment_gateway', function () {
return $this->createMock(PaymentGateway::class);
});
// ...
}
}
Asserting Flash Messages in Tests
Checking that a controller added a flash message used to require several steps: fetching the request, making sure it has a session, retrieving the flash bag, and inspecting its messages.
Symfony 8.1 replaces all of that with a single assertion called
assertSessionHasFlashMessage():
1 2 3 4 5
// assert that a flash message of the given type exists
$this->assertSessionHasFlashMessage('success');
// optionally, assert its exact content too
$this->assertSessionHasFlashMessage('success', 'Your changes were saved.');
Controlling Time in Messenger Stamps
The DelayStamp and RedeliveryStamp classes computed time using PHP's native
time() function and DateTimeImmutable objects, making any logic that
depends on their values impossible to test deterministically.
Symfony 8.1 updates both stamps to rely on the Clock component instead. In your
tests, you can now freeze time with MockClock and get fully predictable delays
and redelivery timestamps:
1 2 3 4 5 6 7 8
use Symfony\Component\Clock\Clock;
use Symfony\Component\Clock\MockClock;
use Symfony\Component\Messenger\Stamp\RedeliveryStamp;
Clock::set(new MockClock('2026-01-01 00:00:00'));
// the stamp now reads the current time from the frozen clock
$stamp = new RedeliveryStamp(retryCount: 1);