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

Sylvain Combraque
Contributed by Sylvain Combraque in #62320

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.

Symfony 8.1 profiler includes a feature to copy requests as cURL so you can replay them

A More Accessible Web Debug Toolbar

Martin Gilbert
Contributed by Martin Gilbert in #63943

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

Michaël Thieulin
Contributed by Michaël Thieulin in #63905

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

yoann aparici
Contributed by yoann aparici in #63135

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

Santiago San Martin
Contributed by Santiago San Martin in #62875

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

HypeMC
Contributed by HypeMC in #62909

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

Alex Rock
Contributed by Alex Rock in #60008

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

Mohammad Eftekhari
Contributed by Mohammad Eftekhari in #62572

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);
Published in #Living on the edge