• 6 min read

Symfony 8.1 DX Wins: Copy as cURL, Sorted Debug Commands, and Better Test Helpers

Symfony 8.1 ships small developer experience upgrades a profiler Copy as cURL button, debug:router --sort, flash assertions, and mockable services.

Featured image for "Symfony 8.1 DX Wins: Copy as cURL, Sorted Debug Commands, and Better Test Helpers"

Big framework releases get headlines for their big features. Symfony 8.1 has plenty of those, from HTTP-less applications to method-based commands. But the changes I notice most in a normal week are the small ones: the button that saves me five minutes of header copying, or the assertion that replaces six lines of test boilerplate.

The Symfony team collected a batch of these in New in Symfony 8.1: DX Improvements (Part 1), published June 10, 2026. This post walks through the ones I think PHP developers will use daily, with the code from that article and a few notes on where each fits in a real project. Symfony 8.1.0 itself was announced as about to ship in the curated features post on May 29, 2026, and the project’s backward compatibility promise applies, so upgrading from 8.0 should not require code changes.

Copy requests as cURL from the profiler

You hit a bug in a form post. You want to replay it, tweak one header, or hand it to a teammate. The profiler already shows every detail of the request, but turning that into a curl command meant copying headers, cookies, and the body by hand.

Symfony 8.1 adds a “Copy as cURL” button to the Request/Response tab of the profiler. According to the Symfony post, the generated command includes the HTTP method, full URL, request headers, cookies, and the body for write requests. Paste it into a terminal and you replay the same request.

This is the kind of feature that changes habits. Instead of describing a bug in a ticket (“POST to /checkout with a session cookie and these fields”), you can attach a command that reproduces it. The feature was contributed by Sylvain Combraque in PR #62320.

One practical caution: the command includes cookies, so treat it like a credential if the session is a real one. Do not paste a production session cookie into a public issue.

A keyboard-accessible Web Debug Toolbar

The Web Debug Toolbar used to reveal its panels only on mouse hover, which left keyboard and screen reader users out. In 8.1, the toolbar is keyboard-navigable and carries WAI-ARIA semantics. Per the Symfony post, you 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, and items expose descriptive labels plus the toolbar and dialog roles.

If you build tools for a team that includes developers who rely on assistive technology, this matters. It was contributed by Martin Gilbert in PR #63943.

Sort routes in debug:router

debug:router lists routes in the order Symfony evaluates them. That order is correct for matching, but it is a poor order for finding a route in a large app. Piping to sort worked, but broke the clickable links to route definitions that the command output provides.

Symfony 8.1 adds a --sort option. It accepts name, path, method, scheme, or host:

php bin/console debug:router --sort=path

# case-insensitive, and works with every output format
php bin/console debug:router --sort=name --format=json

Sorting by path is handy when you are hunting for every route under /admin, and sorting by name helps when you are auditing naming conventions. Credit goes to Michaël Thieulin for PR #63905.

Sort scheduled messages by next run

The Scheduler component gets a similar treatment. debug:scheduler lists recurring messages in definition order. With many tasks, that makes it hard to see which one fires next or which ones collide.

php bin/console debug:scheduler --sort

With --sort, recurring messages are ordered by next run date, so the soonest task comes first. If you run a PHP Architect style content pipeline with a dozen scheduled jobs (newsletter sends, feed imports, cache warmers), one glance tells you what runs in the next five minutes. This came from yoann aparici in PR #63135.

Consume transports by regular expression

messenger:consume wants exact transport names. If you have one transport per scheduler, or a family of similarly named queues, listing them all gets old. In 8.1 the transport names argument also accepts regular expressions, matched against configured transport names:

# consume all transports whose name starts with "scheduler_"
php bin/console messenger:consume 'scheduler_.*'

# (?i) makes the match case-insensitive
php bin/console messenger:consume '(?i)async.*'

# mix patterns and explicit names
php bin/console messenger:consume 'scheduler_.*' async

Two details from the article are worth remembering. Each pattern is anchored, so it must match the full transport name. And regular expressions are only supported when the command runs non-interactively, so use them in your supervisor or systemd unit rather than at an interactive prompt. Matched transports are consumed in their configuration order. Santiago San Martin contributed this in PR #62875.

Mock non-shared services in tests

In functional tests, you can swap a service for a test double with getContainer()->set(). That only worked for shared services. Non-shared services, which the container builds fresh on every request, could not be replaced.

Symfony 8.1 lifts the restriction. Because a non-shared service returns a new instance each time, you pass a closure that acts as a factory. The closure runs every time the service is fetched:

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);
        });

        // ...
    }
}

The service ID and PaymentGateway class here come from the Symfony example, so substitute your own. This was contributed by HypeMC in PR #62909.

Assert flash messages in one line

Checking that a controller set a flash message used to take several steps: get the request, make sure it has a session, fetch the flash bag, inspect the messages. Symfony 8.1 collapses that into assertSessionHasFlashMessage():

// 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.');

For a PHPTek registration flow, a functional test might submit the form and then assert the success flash with the confirmation text in a single call. Alex Rock contributed this in PR #60008.

Deterministic time in Messenger stamps

DelayStamp and RedeliveryStamp used PHP’s native time() and DateTimeImmutable, so any logic depending on their values was hard to test deterministically. Symfony 8.1 moves both to the Clock component. In tests, you freeze time with MockClock:

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);

If you have retry logic that branches on how long ago a message was first delivered, this is the change that lets you write a test without sleep(). Mohammad Eftekhari contributed it in PR #62572.

Should you upgrade for these?

None of these changes alone justifies a framework upgrade. Together, they shave friction from debugging, operations, and testing, which is where most of a developer’s week goes. The curated post notes that the backward compatibility promise holds, so the risk is low. As always, run your test suite and read the upgrade notes for your own dependencies before you bump a production app.

A sensible order if you adopt 8.1 gradually: start with the commands (--sort on both debug commands costs nothing to try), then move to the profiler workflow, and finally refactor tests to use the new assertion and the non-shared service mocking as you touch them.

There is also a second batch. The curated list links DX Improvements (Part 2), which covers direct request header mapping, controller-aware security expressions, configurable webhooks, and streamlined bundle compiler passes. I will cover that in a later post.

Sources