Home / Symfony / New in Symfony 8.1: DX Improvements (Part 2)

New in Symfony 8.1: DX Improvements (Part 2)

Symfony 8.1 includes many small features and improvements across different
components. This is the second article in the series that highlights some of
the most useful DX (developer experience) improvements.

Map Request Headers to Controller Arguments


Steven RENAUX
Contributed by
Steven RENAUX
in
#51379

Symfony provides several attributes, such as #[MapQueryParameter] and
#[MapRequestPayload], to map parts of the HTTP request to controller
arguments. However, there was no equivalent for HTTP headers, so you had to
inject the entire Request object just to read one of them.

In Symfony 8.1 we’re introducing a new #[MapRequestHeader] attribute to
map request headers directly to controller arguments:

use SymfonyComponentHttpFoundationAcceptHeader;
use SymfonyComponentHttpFoundationResponse;
use SymfonyComponentHttpKernelAttributeMapRequestHeader;

class DashboardController extends AbstractController
{
    public function index(
        // argument names are converted from camelCase to the
        // kebab-case header name (in this case: accept-language)
        #[MapRequestHeader] string $acceptLanguage,

        // use the 'name' option when the header name cannot be
        // derived from the argument name
        #[MapRequestHeader(name: 'x-request-id')] ?string $requestId,

        // type-hint the argument as array to get all the header values
        #[MapRequestHeader] array $accept,

        // type-hint it as AcceptHeader to get a parsed object that
        // allows sorting and filtering values by their quality
        #[MapRequestHeader] AcceptHeader $acceptEncoding,
    ): Response {
        // ...
    }
}

If a required header is missing, Symfony returns a 400 Bad Request
response. You can customize this status code with the
validationFailedStatusCode option of the attribute.

Use the Controller Instance in Security Expressions


Valtteri R
Contributed by
Valtteri R
in
#63201

The #[IsGranted] attribute accepts an expression as its subject
option. Inside that expression, you could only use the request and
args variables. This was an issue for invokable classes that store their
state in properties (e.g. live components), because there was no way to pass
those properties as the subject.

In Symfony 8.1, subject expressions can also use a new this variable,
which refers to the controller instance itself:

use SymfonyComponentExpressionLanguageExpression;
use SymfonyComponentSecurityHttpAttributeIsGranted;
use SymfonyUXLiveComponentAttributeAsLiveComponent;
use SymfonyUXLiveComponentAttributeLiveProp;

#[AsLiveComponent]
class PostComponent
{
    #[LiveProp]
    public Post $post;

    // 'this' refers to this PostComponent instance, so you can use
    // any of its properties and methods as the subject
    #[IsGranted('POST_EDIT', subject: new Expression('this.post'))]
    public function __invoke(): void
    {
        // ...
    }
}

CSP-Compliant dump() Output


Pascal CESCON
Contributed by
Pascal CESCON
in
#63762
and #64087

The dump() function renders its HTML output using inline <script> and
<style> tags. If your application enforces a strict Content Security Policy,
browsers block those tags unless they include a valid nonce. That’s why, until
now, the web debug toolbar disabled CSP entirely for any page that displayed a dump.

In Symfony 8.1, the HtmlDumper class provides a new setNonce() method
to add a CSP nonce to all the tags it generates:

use SymfonyComponentVarDumperDumperHtmlDumper;

$dumper = new HtmlDumper();
$dumper->setNonce($request->attributes->get('_csp_nonce'));

// in long-running workers, where each request has its own nonce,
// pass a closure to resolve the nonce lazily
$dumper->setNonce(static fn (): string => $nonceProvider->current());

In addition, when using the web profiler in the dev environment, Symfony
now forwards its own CSP nonces to the dumped contents automatically. Your
Content Security Policy remains fully enabled while dumps display correctly,
and you don’t need to change anything in your application.

Custom Marshallers per Cache Pool


Mark van Duijker
Contributed by
Mark van Duijker
in
#63356

Cache marshallers transform cache values before storing them (e.g. to
serialize, compress, or encrypt them). Previously, you could only replace the
default marshaller globally, affecting all cache pools at once.

In Symfony 8.1, cache pools support a new marshaller option, so you can
mix different strategies in the same application:

# config/packages/cache.yaml
framework:
    cache:
        pools:
            # encrypt the contents of this pool
            cache.tokens:
                adapter: cache.adapter.redis
                marshaller: 'app.sodium_marshaller'
            # compress the contents of this pool
            cache.large_data:
                adapter: cache.adapter.filesystem
                marshaller: 'app.deflate_marshaller'

services:
    app.sodium_marshaller:
        class: SymfonyComponentCacheMarshallerSodiumMarshaller
        arguments:
            - ['%env(base64:CACHE_DECRYPTION_KEY)%']
            - '@cache.default_marshaller'

    app.deflate_marshaller:
        class: SymfonyComponentCacheMarshallerDeflateMarshaller
        arguments: ['@cache.default_marshaller']

Configurable Webhook Headers and Signing Algorithm


Louis-Arnaud
Contributed by
Louis-Arnaud
in
#63520

When sending webhooks with the Webhook component, Symfony transmits the
event metadata in the Webhook-Event, Webhook-Id and
Webhook-Signature headers, and signs the payload with HMAC-SHA256. These
values were hardcoded, which was a problem when the receiving endpoints
expected different header names or signing algorithms.

In Symfony 8.1 you can configure all of them:

# config/packages/webhook.yaml
framework:
    webhook:
        event_header_name: 'X-Acme-Event'
        id_header_name: 'X-Acme-Id'
        signature_header_name: 'X-Acme-Signature'
        signing_algorithm: 'sha512'

Safer Lock Stores on Shared Servers


Nicolas Grekas
Contributed by
Nicolas Grekas
in
#63263

By default, the Lock component uses a semaphore store when available,
or a flock store otherwise. Both rely on resources shared across the
entire machine (system semaphores and files in the temporary directory), so
two applications running on the same server could collide if they used the
same lock names.

Starting in Symfony 8.1, both stores are scoped by the
kernel.project_id parameter (an identifier derived from the project
directory). Locks from different applications no longer collide, and you
don’t have to change anything in your applications to benefit from this.

When using the Lock component as a standalone library, pass the project
identifier yourself:

use SymfonyComponentLockStoreSemaphoreStore;

$store = new SemaphoreStore($projectId);

Bundles as Compiler Passes


Yonel Ceruto
Contributed by
Yonel Ceruto
in
#62800

Bundles often need to register compiler passes to modify the service
container during its compilation. Even if the bundle class itself implemented
CompilerPassInterface, you still had to override the build() method
and call $container->addCompilerPass($this) explicitly.

In Symfony 8.1, that boilerplate is gone: any bundle class that implements
CompilerPassInterface is registered automatically as a compiler pass:

use SymfonyComponentDependencyInjectionCompilerCompilerPassInterface;
use SymfonyComponentDependencyInjectionContainerBuilder;
use SymfonyComponentHttpKernelBundleAbstractBundle;

class AcmeBundle extends AbstractBundle implements CompilerPassInterface
{
    // there's no need to override the build() method to register this;
    // Symfony calls it automatically when compiling the container
    public function process(ContainerBuilder $container): void
    {
        // ... manipulate the container services
    }
}


Sponsor the Symfony project.
Tagged:

Leave a Reply

Your email address will not be published. Required fields are marked *