<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0"
    xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:dc="http://purl.org/dc/elements/1.1/"
    xmlns:atom="http://www.w3.org/2005/Atom"
>
    <channel>
        <title>Symfony Blog</title>
        <atom:link href="https://feeds.feedburner.com/symfony/blog" rel="self" type="application/rss+xml" />
        <link>https://symfony.com/blog/</link>
        <description>Most recent posts published on the Symfony project blog</description>
        <pubDate>Thu, 11 Jun 2026 10:56:23 +0200</pubDate>
        <lastBuildDate>Thu, 11 Jun 2026 09:15:00 +0200</lastBuildDate>
        <language>en</language>
                        <item>
            <title><![CDATA[New in Symfony 8.1: DX Improvements (Part 2)]]></title>
            <link>https://symfony.com/blog/new-in-symfony-8-1-dx-improvements-part-2?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed</link>
            <description>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…</description>
            <content:encoded><![CDATA[
                                <p>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.</p>
<div class="section">
<h2 id="map-request-headers-to-controller-arguments"><a class="headerlink" href="#map-request-headers-to-controller-arguments" title="Permalink to this headline">Map Request Headers to Controller Arguments</a></h2>
<div class="blog-post-contributor-info">
    <div class="blog-post-contributor-avatar">
                    <a target="_blank" href="https://connect.symfony.com/profile/steven_renaux">
                <img src="https://connect.symfony.com/profile/steven_renaux.picture" alt="Steven RENAUX">
            </a>
            </div>
    <div class="blog-post-contributor-contents">
        <span>Contributed by</span>
                    <a target="_blank" class="blog-post-contributor-name" href="https://connect.symfony.com/profile/steven_renaux">Steven RENAUX</a>
                                        <span class="blog-post-contributor-prs"> in
                                    <a target="_blank" href="https://github.com/symfony/symfony/pull/51379">#51379</a>
                                                </span>
            </div>
</div>
<p>Symfony provides several attributes, such as <code translate="no" class="notranslate">#[MapQueryParameter]</code> and
<code translate="no" class="notranslate">#[MapRequestPayload]</code>, 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 <code translate="no" class="notranslate">Request</code> object just to read one of them.</p>
<p>In Symfony 8.1 we're introducing a new <code translate="no" class="notranslate">#[MapRequestHeader]</code> attribute to
map request headers directly to controller arguments:</p>
<div translate="no" data-loc="25" class="notranslate codeblock codeblock-length-md codeblock-php">
        <div class="codeblock-scroll">
        
        <pre class="codeblock-code"><code><span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">HttpFoundation</span>\<span class="hljs-title">AcceptHeader</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">HttpFoundation</span>\<span class="hljs-title">Response</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">HttpKernel</span>\<span class="hljs-title">Attribute</span>\<span class="hljs-title">MapRequestHeader</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DashboardController</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">AbstractController</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">index</span><span class="hljs-params">(
        // argument names are converted from camelCase to the
        // kebab-case header name <span class="hljs-params">(in this case: accept-language)</span>
        <span class="hljs-meta">#[MapRequestHeader]</span> <span class="hljs-keyword">string</span> <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>acceptLanguage</span>,

        // use the <span class="hljs-string">'name'</span> option when the header name cannot be
        // derived from the argument name
        <span class="hljs-meta">#[MapRequestHeader(<span class="hljs-attr">name</span>: <span class="hljs-string">'x-request-id'</span>)]</span> ?<span class="hljs-keyword">string</span> <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>requestId</span>,

        // type-hint the argument as <span class="hljs-keyword">array</span> to get all the header values
        <span class="hljs-meta">#[MapRequestHeader]</span> <span class="hljs-keyword">array</span> <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>accept</span>,

        // type-hint it as AcceptHeader to get a parsed <span class="hljs-keyword">object</span> that
        // allows sorting and filtering values by their quality
        <span class="hljs-meta">#[MapRequestHeader]</span> AcceptHeader <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>acceptEncoding</span>,
    )</span>: <span class="hljs-title">Response</span> </span>{
        <span class="hljs-comment">// ...</span>
    }
}</code></pre>
    </div>
</div>
<p>If a required header is missing, Symfony returns a <code translate="no" class="notranslate">400 Bad Request</code>
response. You can customize this status code with the
<code translate="no" class="notranslate">validationFailedStatusCode</code> option of the attribute.</p>
</div>
<div class="section">
<h2 id="use-the-controller-instance-in-security-expressions"><a class="headerlink" href="#use-the-controller-instance-in-security-expressions" title="Permalink to this headline">Use the Controller Instance in Security Expressions</a></h2>
<div class="blog-post-contributor-info">
    <div class="blog-post-contributor-avatar">
                    <a target="_blank" href="https://connect.symfony.com/profile/valtzu">
                <img src="https://connect.symfony.com/profile/valtzu.picture" alt="Valtteri R">
            </a>
            </div>
    <div class="blog-post-contributor-contents">
        <span>Contributed by</span>
                    <a target="_blank" class="blog-post-contributor-name" href="https://connect.symfony.com/profile/valtzu">Valtteri R</a>
                                        <span class="blog-post-contributor-prs"> in
                                    <a target="_blank" href="https://github.com/symfony/symfony/pull/63201">#63201</a>
                                                </span>
            </div>
</div>
<p>The <code translate="no" class="notranslate">#[IsGranted]</code> attribute accepts an expression as its <code translate="no" class="notranslate">subject</code>
option. Inside that expression, you could only use the <code translate="no" class="notranslate">request</code> and
<code translate="no" class="notranslate">args</code> 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.</p>
<p>In Symfony 8.1, subject expressions can also use a new <code translate="no" class="notranslate">this</code> variable,
which refers to the controller instance itself:</p>
<div translate="no" data-loc="19" class="notranslate codeblock codeblock-length-md codeblock-php">
        <div class="codeblock-scroll">
        
        <pre class="codeblock-code"><code><span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">ExpressionLanguage</span>\<span class="hljs-title">Expression</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">Security</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">Attribute</span>\<span class="hljs-title">IsGranted</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">UX</span>\<span class="hljs-title">LiveComponent</span>\<span class="hljs-title">Attribute</span>\<span class="hljs-title">AsLiveComponent</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">UX</span>\<span class="hljs-title">LiveComponent</span>\<span class="hljs-title">Attribute</span>\<span class="hljs-title">LiveProp</span>;

<span class="hljs-meta">#[AsLiveComponent]</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PostComponent</span>
</span>{
    <span class="hljs-meta">#[LiveProp]</span>
    <span class="hljs-keyword">public</span> Post <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>post</span>;

    <span class="hljs-comment">// 'this' refers to this PostComponent instance, so you can use</span>
    <span class="hljs-comment">// any of its properties and methods as the subject</span>
    <span class="hljs-meta">#[IsGranted(<span class="hljs-string">'POST_EDIT'</span>, <span class="hljs-attr">subject</span>: <span class="hljs-keyword">new</span> <span class="hljs-title invoke__">Expression</span>(<span class="hljs-string">'this.post'</span>))]</span>
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__invoke</span><span class="hljs-params">()</span>: <span class="hljs-title">void</span>
    </span>{
        <span class="hljs-comment">// ...</span>
    }
}</code></pre>
    </div>
</div>
</div>
<div class="section">
<h2 id="csp-compliant-dump-output"><a class="headerlink" href="#csp-compliant-dump-output" title="Permalink to this headline">CSP-Compliant <code translate="no" class="notranslate">dump()</code> Output</a></h2>
<div class="blog-post-contributor-info">
    <div class="blog-post-contributor-avatar">
                    <a target="_blank" href="https://connect.symfony.com/profile/amoifr">
                <img src="https://connect.symfony.com/profile/amoifr.picture" alt="Pascal CESCON">
            </a>
            </div>
    <div class="blog-post-contributor-contents">
        <span>Contributed by</span>
                    <a target="_blank" class="blog-post-contributor-name" href="https://connect.symfony.com/profile/amoifr">Pascal CESCON</a>
                                        <span class="blog-post-contributor-prs"> in
                                    <a target="_blank" href="https://github.com/symfony/symfony/pull/63762">#63762</a>
                     and                                     <a target="_blank" href="https://github.com/symfony/symfony/pull/64087">#64087</a>
                                                </span>
            </div>
</div>
<p>The <code translate="no" class="notranslate">dump()</code> function renders its HTML output using inline <code translate="no" class="notranslate">&lt;script&gt;</code> and
<code translate="no" class="notranslate">&lt;style&gt;</code> tags. If your application enforces a strict <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP" class="reference external" rel="external noopener noreferrer" target="_blank">Content Security Policy</a>,
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.</p>
<p>In Symfony 8.1, the <code translate="no" class="notranslate">HtmlDumper</code> class provides a new <code translate="no" class="notranslate">setNonce()</code> method
to add a CSP nonce to all the tags it generates:</p>
<div translate="no" data-loc="8" class="notranslate codeblock codeblock-length-sm codeblock-php">
        <div class="codeblock-scroll">
        
        <pre class="codeblock-code"><code><span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">VarDumper</span>\<span class="hljs-title">Dumper</span>\<span class="hljs-title">HtmlDumper</span>;

<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>dumper</span> = <span class="hljs-keyword">new</span> <span class="hljs-title invoke__">HtmlDumper</span>();
<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>dumper</span>-&gt;<span class="hljs-title invoke__">setNonce</span>(<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>request</span>-&gt;attributes-&gt;<span class="hljs-title invoke__">get</span>(<span class="hljs-string">'_csp_nonce'</span>));

<span class="hljs-comment">// in long-running workers, where each request has its own nonce,</span>
<span class="hljs-comment">// pass a closure to resolve the nonce lazily</span>
<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>dumper</span>-&gt;<span class="hljs-title invoke__">setNonce</span>(static <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-params">()</span>: <span class="hljs-title">string</span> =&gt;</span> <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>nonceProvider</span>-&gt;<span class="hljs-title invoke__">current</span>());</code></pre>
    </div>
</div>
<p>In addition, when using the web profiler in the <code translate="no" class="notranslate">dev</code> 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.</p>
</div>
<div class="section">
<h2 id="custom-marshallers-per-cache-pool"><a class="headerlink" href="#custom-marshallers-per-cache-pool" title="Permalink to this headline">Custom Marshallers per Cache Pool</a></h2>
<div class="blog-post-contributor-info">
    <div class="blog-post-contributor-avatar">
                    <a target="_blank" href="https://github.com/mvanduijker">
                <img src="https://github.com/mvanduijker.png" alt="Mark van Duijker">
            </a>
            </div>
    <div class="blog-post-contributor-contents">
        <span>Contributed by</span>
                    <a target="_blank" class="blog-post-contributor-name" href="https://github.com/mvanduijker">Mark van Duijker</a>
                                        <span class="blog-post-contributor-prs"> in
                                    <a target="_blank" href="https://github.com/symfony/symfony/pull/63356">#63356</a>
                                                </span>
            </div>
</div>
<p>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.</p>
<p>In Symfony 8.1, cache pools support a new <code translate="no" class="notranslate">marshaller</code> option, so you can
mix different strategies in the same application:</p>
<div translate="no" data-loc="23" class="notranslate codeblock codeblock-length-md codeblock-yaml">
        <div class="codeblock-scroll">
        
        <pre class="codeblock-code"><code><span class="hljs-comment"># config/packages/cache.yaml</span>
<span class="hljs-attr">framework:</span>
    <span class="hljs-attr">cache:</span>
        <span class="hljs-attr">pools:</span>
            <span class="hljs-comment"># encrypt the contents of this pool</span>
            <span class="hljs-attr">cache.tokens:</span>
                <span class="hljs-attr">adapter:</span> <span class="hljs-string">cache.adapter.redis</span>
                <span class="hljs-attr">marshaller:</span> <span class="hljs-string">'app.sodium_marshaller'</span>
            <span class="hljs-comment"># compress the contents of this pool</span>
            <span class="hljs-attr">cache.large_data:</span>
                <span class="hljs-attr">adapter:</span> <span class="hljs-string">cache.adapter.filesystem</span>
                <span class="hljs-attr">marshaller:</span> <span class="hljs-string">'app.deflate_marshaller'</span>

<span class="hljs-attr">services:</span>
    <span class="hljs-attr">app.sodium_marshaller:</span>
        <span class="hljs-attr">class:</span> <span class="hljs-string">Symfony\Component\Cache\Marshaller\SodiumMarshaller</span>
        <span class="hljs-attr">arguments:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">['%env(base64:CACHE_DECRYPTION_KEY)%']</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">'@cache.default_marshaller'</span>

    <span class="hljs-attr">app.deflate_marshaller:</span>
        <span class="hljs-attr">class:</span> <span class="hljs-string">Symfony\Component\Cache\Marshaller\DeflateMarshaller</span>
        <span class="hljs-attr">arguments:</span> <span class="hljs-string">['@cache.default_marshaller']</span></code></pre>
    </div>
</div>
</div>
<div class="section">
<h2 id="configurable-webhook-headers-and-signing-algorithm"><a class="headerlink" href="#configurable-webhook-headers-and-signing-algorithm" title="Permalink to this headline">Configurable Webhook Headers and Signing Algorithm</a></h2>
<div class="blog-post-contributor-info">
    <div class="blog-post-contributor-avatar">
                    <a target="_blank" href="https://connect.symfony.com/profile/lacatoire">
                <img src="https://connect.symfony.com/profile/lacatoire.picture" alt="Louis-Arnaud">
            </a>
            </div>
    <div class="blog-post-contributor-contents">
        <span>Contributed by</span>
                    <a target="_blank" class="blog-post-contributor-name" href="https://connect.symfony.com/profile/lacatoire">Louis-Arnaud</a>
                                        <span class="blog-post-contributor-prs"> in
                                    <a target="_blank" href="https://github.com/symfony/symfony/pull/63520">#63520</a>
                                                </span>
            </div>
</div>
<p>When sending webhooks with the <a href="https://symfony.com/doc/current/webhook.html" class="reference external">Webhook component</a>, Symfony transmits the
event metadata in the <code translate="no" class="notranslate">Webhook-Event</code>, <code translate="no" class="notranslate">Webhook-Id</code> and
<code translate="no" class="notranslate">Webhook-Signature</code> 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.</p>
<p>In Symfony 8.1 you can configure all of them:</p>
<div translate="no" data-loc="7" class="notranslate codeblock codeblock-length-sm codeblock-yaml">
        <div class="codeblock-scroll">
        
        <pre class="codeblock-code"><code><span class="hljs-comment"># config/packages/webhook.yaml</span>
<span class="hljs-attr">framework:</span>
    <span class="hljs-attr">webhook:</span>
        <span class="hljs-attr">event_header_name:</span> <span class="hljs-string">'X-Acme-Event'</span>
        <span class="hljs-attr">id_header_name:</span> <span class="hljs-string">'X-Acme-Id'</span>
        <span class="hljs-attr">signature_header_name:</span> <span class="hljs-string">'X-Acme-Signature'</span>
        <span class="hljs-attr">signing_algorithm:</span> <span class="hljs-string">'sha512'</span></code></pre>
    </div>
</div>
</div>
<div class="section">
<h2 id="safer-lock-stores-on-shared-servers"><a class="headerlink" href="#safer-lock-stores-on-shared-servers" title="Permalink to this headline">Safer Lock Stores on Shared Servers</a></h2>
<div class="blog-post-contributor-info">
    <div class="blog-post-contributor-avatar">
                    <a target="_blank" href="https://connect.symfony.com/profile/nicolas-grekas">
                <img src="https://connect.symfony.com/profile/nicolas-grekas.picture" alt="Nicolas Grekas">
            </a>
            </div>
    <div class="blog-post-contributor-contents">
        <span>Contributed by</span>
                    <a target="_blank" class="blog-post-contributor-name" href="https://connect.symfony.com/profile/nicolas-grekas">Nicolas Grekas</a>
                                        <span class="blog-post-contributor-prs"> in
                                    <a target="_blank" href="https://github.com/symfony/symfony/pull/63263">#63263</a>
                                                </span>
            </div>
</div>
<p>By default, the <a href="https://symfony.com/doc/current/lock.html" class="reference external">Lock component</a> uses a <code translate="no" class="notranslate">semaphore</code> store when available,
or a <code translate="no" class="notranslate">flock</code> 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.</p>
<p>Starting in Symfony 8.1, both stores are scoped by the
<code translate="no" class="notranslate">kernel.project_id</code> 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.</p>
<p>When using the Lock component as a standalone library, pass the project
identifier yourself:</p>
<div translate="no" data-loc="3" class="notranslate codeblock codeblock-length-sm codeblock-php">
        <div class="codeblock-scroll">
        
        <pre class="codeblock-code"><code><span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">Lock</span>\<span class="hljs-title">Store</span>\<span class="hljs-title">SemaphoreStore</span>;

<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>store</span> = <span class="hljs-keyword">new</span> <span class="hljs-title invoke__">SemaphoreStore</span>(<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>projectId</span>);</code></pre>
    </div>
</div>
</div>
<div class="section">
<h2 id="bundles-as-compiler-passes"><a class="headerlink" href="#bundles-as-compiler-passes" title="Permalink to this headline">Bundles as Compiler Passes</a></h2>
<div class="blog-post-contributor-info">
    <div class="blog-post-contributor-avatar">
                    <a target="_blank" href="https://connect.symfony.com/profile/yonelceruto">
                <img src="https://connect.symfony.com/profile/yonelceruto.picture" alt="Yonel Ceruto">
            </a>
            </div>
    <div class="blog-post-contributor-contents">
        <span>Contributed by</span>
                    <a target="_blank" class="blog-post-contributor-name" href="https://connect.symfony.com/profile/yonelceruto">Yonel Ceruto</a>
                                        <span class="blog-post-contributor-prs"> in
                                    <a target="_blank" href="https://github.com/symfony/symfony/pull/62800">#62800</a>
                                                </span>
            </div>
</div>
<p>Bundles often need to register <a href="https://symfony.com/doc/current/service_container/compiler_passes.html" class="reference external">compiler passes</a> to modify the service
container during its compilation. Even if the bundle class itself implemented
<code translate="no" class="notranslate">CompilerPassInterface</code>, you still had to override the <code translate="no" class="notranslate">build()</code> method
and call <code translate="no" class="notranslate">$container-&gt;addCompilerPass($this)</code> explicitly.</p>
<p>In Symfony 8.1, that boilerplate is gone: any bundle class that implements
<code translate="no" class="notranslate">CompilerPassInterface</code> is registered automatically as a compiler pass:</p>
<div translate="no" data-loc="13" class="notranslate codeblock codeblock-length-md codeblock-php">
        <div class="codeblock-scroll">
        
        <pre class="codeblock-code"><code><span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">DependencyInjection</span>\<span class="hljs-title">Compiler</span>\<span class="hljs-title">CompilerPassInterface</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">DependencyInjection</span>\<span class="hljs-title">ContainerBuilder</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">HttpKernel</span>\<span class="hljs-title">Bundle</span>\<span class="hljs-title">AbstractBundle</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AcmeBundle</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">AbstractBundle</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">CompilerPassInterface</span>
</span>{
    <span class="hljs-comment">// there's no need to override the build() method to register this;</span>
    <span class="hljs-comment">// Symfony calls it automatically when compiling the container</span>
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">process</span><span class="hljs-params">(ContainerBuilder <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>container</span>)</span>: <span class="hljs-title">void</span>
    </span>{
        <span class="hljs-comment">// ... manipulate the container services</span>
    }
}</code></pre>
    </div>
</div>
</div>
                <hr style="margin-bottom: 5px" />
                <div style="font-size: 90%">
                    <a href="https://symfony.com/sponsor">Sponsor</a> the Symfony project.
                </div>
            ]]></content:encoded>
            <guid isPermaLink="false">https://symfony.com/blog/new-in-symfony-8-1-dx-improvements-part-2?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed</guid>
            <dc:creator><![CDATA[ Javier Eguiluz ]]></dc:creator>
            <pubDate>Thu, 11 Jun 2026 09:15:00 +0200</pubDate>
            <comments>https://symfony.com/blog/new-in-symfony-8-1-dx-improvements-part-2?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed#comments-list</comments>
        </item>
                        <item>
            <title><![CDATA[Case Study: TreeHouse - Servicing the rental real estate market with Symfony]]></title>
            <link>https://symfony.com/blog/case-study-treehouse-servicing-the-rental-real-estate-market-with-symfony?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed</link>
            <description>
.cls-1{fill:currentColor;}.cls-2{fill:#63af5e;}.cls-3{fill:#2e854a;}


TreeHouse powers two of the largest real estate marketplaces in the Netherlands: huurwoningen.nl and pararius.nl. Both sites host over 2 million unique monthly visitors. Treehouse is…</description>
            <content:encoded><![CDATA[
                                <p><a class="block text-center" href="https://treehouse.nl/" title="Th Logo Grijs W49362Z6">
<svg class="block" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1864.18 312.21"><defs><style>.cls-1{fill:currentColor;}.cls-2{fill:#63af5e;}.cls-3{fill:#2e854a;}</style></defs><g id="Laag_2" data-name="Laag 2"><g id="Layer_1" data-name="Layer 1"><path class="cls-1" d="M1855.74,206.92c-2,0-5.33,2.79-9.47,8.78-22.54,39.48-47.43,59-74.5,59-15.92,0-26.86-9.17-30.34-23.92a70.7,70.7,0,0,1-.7-28.31c33.3,4.39,57.25,1.19,72.42-10.36s21.16-25.11,17.49-40.66c-5.36-22.71-23.79-35.08-47.3-35.08-29.89,0-61.43,21.52-77.79,58.61a95.88,95.88,0,0,0-4.85,14.55,23.57,23.57,0,0,0-4.18,6.2c-12.57,24.31-25,41.07-37.35,51,4.13-11.16,4.8-23.54,1.7-36.68A209.11,209.11,0,0,0,1647,191.39c-4-8.37-6.4-18.72-4.28-28.31,7.61-10,11.48-17.15,11-20.74-1.47-8-6.71-11.57-12.67-11.57-5.19,0-10.19,2.4-15,7.18-5,4-14.63,15.55-29.24,34.69-14.3,18.73-23.51,30.3-27,33.89-9.81-4.39-18.12-.8-25.14,10a44.28,44.28,0,0,0-6,16.56q-25.35,39.33-39.33,39.27c-5.19,0-8.13-2.4-9.27-7.18-1.6-6.77,4.52-24.73,17.79-54.61s20.15-46.24,20.77-48.65c4-10-4.13-19.14-20.44-19.14-7.56,0-11.56,0-14.92,4.39-3.53,3.59-3.53,3.59-6.91,11.16-19.62,43.45-34.79,73.75-45.07,90.91-10.4,16.74-21.16,25.11-31.92,25.11-7.57,0-12-3.59-13.71-10.78-2-8.36-.23-19.52,5.55-33.89a327.83,327.83,0,0,1,19.51-42.26c7.46-13.95,11.64-21.52,12.06-23.12,3.76-12.76-3.9-21.52-22.61-21.52-13.66,1.19-14.59,4-19.83,15.55-2.16,6-6.78,15.14-14,29.92l-1.39,2.92c-8.13,5.94-20.6,13.82-37.68,13.82,0-6.77-.34-11.58-1-14.37-1.42-6-4.06-10.36-7.49-13.15,4-21.93-13.19-40.66-41.47-40.66-29.09,0-54.33,12.77-75,38.26-20.75,25.11-28.08,51.43-21.78,78.15C1197.22,282.3,1224.89,305,1254,305c18.32,0,35.08-8.37,49.84-25.11s25.08-35.5,30.14-56.22a91.8,91.8,0,0,0,29.14-5.91c-6.65,18.94-8.33,35-5.21,48.18,6.68,28.31,24.13,39.88,44.47,39.88,14.74,0,28.08-7.57,37.84-21.93,9.65-14.75,14.4-25.11,21.7-43.06l1,.8c-3.4,15.94-4.36,27.1-2.94,33.09,4.42,18.73,16.75,28.7,32.26,28.7,17.63,0,33.66-13.87,50.21-40,3.61,9.15,9.47,17.83,17.78,26,10.92,10.77,25.27,15.94,42.79,15.94,28.29,0,50.41-7.57,66.82-22.32,10.64-9.53,20.42-21.93,29.4-37,.39,2.51.88,5,1.47,7.52,7.25,30.69,31.67,51.43,63.13,51.43,20.72,0,40.16-8.75,58.82-25.91a155.7,155.7,0,0,0,40.76-60.21A8.44,8.44,0,0,0,1855.74,206.92ZM1768.47,173c10.8-10,20-14.75,28-14.75,6.38,0,10.51,4,12.29,11.57,2.56,10.77-2,20.33-13.14,28.7s-28.34,11.55-51.1,9.56A84.3,84.3,0,0,1,1768.47,173Zm-454.35-1.22c-9.17,0-16.29,3.59-21.06,10.36s-6.46,13.15-5,19.53c4.59,12.76,12.52,15.94,25.5,20.33-3.1,8.78-10.81,19.92-24,33.09-12.83,13.15-25.27,19.53-37.61,19.53-10.74,0-17.94-6.79-21.14-20.33q-6.93-29.3,17.58-62.2c16.72-21.93,33.22-33.09,49.94-33.09,10,0,16.16,6,16.54,12.76h-.74Zm304.76,89.72c-9.11,10.36-18.63,15.55-29,15.55-11.15,0-18.15-6-20.88-17.54-3.57-15.16,12.31-25.52,15.61-35.08,4.36-5.17,10.82-11.55,18.82-19.92,8.38-8.37,13.52-13.56,15-15.55a198.44,198.44,0,0,0,4.88,20.72c2.38,8.37,3.9,13.15,4.1,13.95C1631,238.41,1627.91,250.76,1618.88,261.53Z"/><path class="cls-1" d="M1198,144.71c6.71-23.5,14.27-45.44,22.3-65.77l18.33-43.86c3.71-9.17,5.16-15.16,4.23-18.34-1.39-4.78-8.85-7.18-14-7.18-6.38,0-12.21,3.18-18.07,9.17q-23.85,26.9-61.61,137.53c-42.61,0-75.16.42-98.15.8,12.54-29.5,20.31-56.18,24.93-71.73,0-.19.1-.34.16-.52l13.16-31.49c3.72-9.17,5.16-15.17,4.23-18.34-1.39-4.78-8.85-7.18-14-7.18-6.37,0-12.21,3.17-18,9.17q-20.13,22.74-50.23,105.19c-2.32,5-4.93,10.46-7.92,16.48l-13.52,27.51-27.83,55.33c-17.78,22.17-36.85,33.17-57.19,33.17-15.93,0-26.87-9.17-30.35-23.92a70.87,70.87,0,0,1-.73-28.31c33.3,4.39,57.25,1.19,72.45-10.36S967.18,187,963.51,171.4c-5.36-22.71-23.82-35.08-47.3-35.08-29.87,0-61.43,21.52-77.8,58.61a96.27,96.27,0,0,0-6.09,20.31l-.31.44c-22.56,39.47-47.41,59-74.48,59-15.93,0-26.87-9.17-30.36-23.92a70.74,70.74,0,0,1-.69-28.32c33.29,4.4,57.24,1.19,72.44-10.35S820.09,187,816.4,171.43c-5.37-22.71-23.83-35.09-47.31-35.09-29.86,0-61.43,21.52-77.79,58.62a95.29,95.29,0,0,0-6.07,20.23,4.68,4.68,0,0,1-.33.49c-22.95,39.47-41,59.41-54.49,59.41-4.39,0-7.22-2-8.18-6-1.88-8,11-39.47,39-94.5,3.66-6.38,4.65-12.37,3.43-17.54-2.17-9.17-7.59-13.56-16.33-13.56-3.59,0-12.86,1.19-27.34,4s-24.16,4-28.44,4.39a28.37,28.37,0,0,0-.23-11.16C590,130.74,584.11,126,575,126c-8.37,0-15.18,3.18-20.94,9.17-5.36,6-7.35,12.76-5.54,20.33C550.74,165,559.23,169,566,169c-2.84,10-11.52,25.53-25.07,47.43a361.4,361.4,0,0,1-48.34,61.79c-4.44,4.78-9.39,9.17-10.42,11.58-1.35,2.79-1.76,4.39-1,7.57,1.42,6,7.12,10,11.92,10q18.51,0,50.93-52.62c21.6-35.09,36.23-64.2,43.82-87.71a181.44,181.44,0,0,0,37.81-5.19c-2.37,6.8-7.84,19.14-16.44,36.68a425,425,0,0,0-20.47,46.63c-5.16,13.56-6.5,24.72-4.54,33.09,4.13,17.55,17.86,26.71,34.2,26.71,25.58,0,46.9-19.06,67.34-54.76.23,1.11.46,2.22.72,3.33C693.72,284.21,718.11,305,749.6,305c20.7,0,40.16-8.75,58.82-25.91a158.05,158.05,0,0,0,24.52-28.57c.21,1,.42,2,.67,3.08,7.26,30.69,31.67,51.43,63.13,51.43,16.91,0,32.94-5.84,48.42-17.28a24.37,24.37,0,0,0,.8,6.92c2.9,10,14,16.74,27.16,16.74,6.76,0,12-4,16.57-11.57s11.89-23.54,21.68-47.46l28-68.17q58.82-1.2,101.23-1.19c-11.93,37.07-20.32,68.56-25.32,95.27-1.6,6.77,1.44,13.15,8.75,19.15a40.56,40.56,0,0,0,26,8.78c7.35,2,11.2-10.77,16.26-35.88l8.18-36.27c4-16.35,8.21-33.48,12.78-50.63l7-26.71ZM901.44,173c10.79-10,20-14.75,28-14.75,6.37,0,10.5,4,12.29,11.57,2.52,10.77-2,20.33-13.14,28.7s-28.34,11.55-51.11,9.56A83.86,83.86,0,0,1,901.44,173Zm-147.11,0c10.78-10,20-14.75,28-14.75,6.38,0,10.51,4,12.29,11.57,2.53,10.77-2,20.33-13.14,28.7s-28.34,11.55-51.1,9.56A83.77,83.77,0,0,1,754.33,173Z"/><path class="cls-1" d="M533.31,66.18l132.1-17.54q15.11-1.78,13-10.77C674.93,23.12,661.85,10,639.55,10c-2.4,0-31.21,2.79-86.54,8,1.13-3.59,1.67-6.38,1.21-8.37C552.7,3.18,548,0,540,0c-6.76,0-14.24,7.18-22.79,21.52-84.86,8.37-134.52,14-149.39,16.74-21.47,3.59-27.71,9.19-25.08,20.35q5.66,23.91,49.86,23.93c10,0,43-3.6,99.73-11.16l-23.12,53.83c-9.78,22.73-17.4,41.08-22.66,54.22s-11.54,28.7-18.07,46.66c-13.53,35.49-18.51,56.62-16.24,66.18,3.1,13.15,11.88,19.94,26.61,19.94,6.77,0,12.31-3.59,15.77-10.77q5.12-11.35,19.62-58.61c10.84-35.08,20.31-65.77,29-91.71Q519.39,100.87,533.31,66.18Z"/><path class="cls-2" d="M64.47,66.54s-14.85,11.54-6.6,31.33S99.15,109.41,100,83,74.37,56.64,64.47,66.54Z"/><path class="cls-3" d="M137.12,67.76a29.26,29.26,0,0,0-4.94,21.85c2.66,17,35.08,28.43,48.7,7S155.67,47.57,137.12,67.76Z"/><path class="cls-3" d="M215.1,38.07s-19.4,9.9-8.66,29.69,37.56,8.65,39.22-9.07S225.85,31.48,215.1,38.07Z"/><path class="cls-2" d="M281.43,53.86s-18.57,9.9-13.21,26S300.83,98,307.43,78.17,291.89,47.18,281.43,53.86Z"/><path class="cls-2" d="M155.67,5.94s-14.44,13.19-3.3,28S196.54,50,200.66,25.3,166.49-6.33,155.67,5.94Z"/><path class="cls-3" d="M5.45,67.8S-8.33,84.23,7.94,97.89C24.43,111.74,43.83,91.7,39.3,75.62S11.66,60.2,5.45,67.8Z"/><path class="cls-3" d="M93.38,8.6s-19.45,7.12-11.6,27,34.47,12.49,38.08-4.05S104.29,3.59,93.38,8.6Z"/><path class="cls-1" d="M296.28,272.73a3.32,3.32,0,0,0-3.15-2.31l-105.3,0c-1.06-14.09-5.57-33.54-.07-57.4,30-7.75,100.21-46,105.35-60.38,5.5-15.4-8.26-37.15-19.81-22-7.31,9.58-42.67,29.86-67.76,43.5,18.47-27.51,41-50,7.59-60.75,0,0-32.29,28.26-45.48,48-9.07,13.59-24.84,34.9-34.57,56.2-6.32,15.55-8,25.75-8.28,28,0,.16,0,.34-.07.5-1,12.44-1.87,19.77-2.1,24.33l-98.62,0a3.31,3.31,0,0,0-3.16,2.31,3.27,3.27,0,0,0,1.25,3.68c42.18,29.47,123.06,35.67,137.22,35.67h1.73c13.64,0,91.73-6.21,133.93-35.72A3.23,3.23,0,0,0,296.28,272.73Z"/><path class="cls-1" d="M142.53,159.69a5.74,5.74,0,0,0-.46-1.34c-5.82-10.06-12.56-22.95-15.82-29.31-.65-1.39-1.29-2.68-2-3.83l0,0c-2.16-3.66-4.64-6-8.88-6.54-9.36-1.16-22,9.91-22,9.91.54,16.42,27.52,45.13,22.58,46.23S97.79,175.85,84,166.51s-34.69-42.86-41.28-44.57-13.2,26.44-8.24,37.44,25.31,31.32,45.12,44.5,38.53,14.29,38.53,14.29c.55-15.38,16.1-33.44,23.11-45.07C144.4,167.85,144.28,163.85,142.53,159.69Z"/></g></g></svg>
</a></p>

<p><strong><a href="https://treehouse.nl/">TreeHouse</a></strong> powers two of the largest real estate marketplaces in the Netherlands: huurwoningen.nl and pararius.nl. Both sites host over 2 million unique monthly visitors. Treehouse is maintained by a team of 40 developers and since 2013, Symfony has become the backbone of both their B2C marketplaces and B2B agent tools, supporting massive product growth and technical complexity.</p>

<h3>The high-volume open house: Broadcasting 1 million alerts daily with Symfony</h3>

<p>TreeHouse’s alert engine dispatches over 1 million emails each day, notifying users about new listings matching their criteria. To scale this massive workload across their Dutch <a href="http://pararius.nl/">(pararius.nl</a> <a href="https://www.huurwoningen.nl/">and huurwoningen.nl)</a>, UK <a href="https://www.rentaroof.co.uk/">(rentaroof.co.uk)</a>, and French <a href="https://www.toitpourtoi.fr/">(toitpourtoi.fr)</a> platforms, the team leverages a powerful stack combining Symfony, Elasticsearch, Google Pub/Sub, and SendGrid. The native compatibility of Symfony with modern cloud infrastructure and its rich ecosystem of bundles have been crucial in keeping the codebase clean and manageable.</p>

<p>The next major milestone for TreeHouse is enhancing the search experience through AI agents. These agents will guide home seekers in fine-tuning their search queries to maximize their chances of finding a home. The emerging Symfony AI tools are proving to be a massive asset in getting this innovative system up and running quickly.</p>

<h3>The hourly property inspection: 300k listings every 60 minutes</h3>

<p>Every single hour, TreeHouse audits around 300,000 real estate listings via their Casco platform. This continuous inspection ensures that data from external CRMs is perfectly synced. Built as a collection of long-running console applications, the system relies heavily on Symfony Console and HTTP Client.</p>

<p>Backed by a Google Pub/Sub messaging spine and a robust Kubernetes infrastructure, the system scales automatically as the volume of listings fluctuates. Symfony shines in this environment, offering native support for signal handling and structured error logging, which are essential for stable containerized operations.</p>

<p>Thanks to this cloud infrastructure and deep expertise in PHP and Symfony, TreeHouse handles these massive processing loads without needing to introduce languages like Python. By breaking heavy tasks into small, isolated chunks, they achieve highly efficient scaling using Kubernetes and Pub/Sub.</p>

<h3>A continuous renovation</h3>

<p>The Casco application originally started on Symfony 4 and has been systematically upgraded with every single release. Today, the platform always runs on the latest stable versions of PHP and Symfony. TreeHouse enforces a strict upgrade policy for every minor and major release.</p>

<h3>Rebuilding the CRM brick by brick</h3>

<p>Beyond marketplaces, TreeHouse develops specialized B2B products to help real estate agents find the perfect tenants. Their flagship CRM, <a href="http://parariusoffice.nl/">Pararius Office</a>, allows agents to manage portfolios and publish listings across multiple marketplaces.</p>

<p>Originally built on the legacy Zend Framework, the CRM is currently being completely migrated to Symfony. To achieve this without interrupting service, the team is using the Strangler Fig Pattern, gradually replacing old controllers with Symfony ones. A proxyservice sits in front of both PHP applications, intelligently routing traffic while ensuring a completely seamless, unified login session for users across both systems.</p>

<h3>Managing the rental rush: Simplifying thousands of viewing requests</h3>

<p>In high-demand cities like Amsterdam, a new listing triggers hundreds of viewing requests in just a few hours. To prevent inbox chaos, TreeHouse built a <a href="https://leadflow.rent/">workflow tool</a> that automates candidate screening, schedules viewings, and verifies IDs. Architectured as a React Single Page Application (SPA) with a Symfony backend, the application delivers a fast experience, leveraging Symfony’s ability to build robust JSON API endpoints with the same ease as traditional HTML.</p>

<p>Ultimately, Symfony has proven to be much more than just a framework for TreeHouse, it is the solid foundation that keeps their digital estate scaling smoothly. From handling millions of daily alerts to remodeling legacy CRMs brick by brick, this robust ecosystem empowers their team to innovate with total confidence. As they look toward the future with AI integration, TreeHouse’s team can rely on an architecture that is fully equipped to continue leading the way in European PropTech.</p>

<p><em>Thanks to Johnny van de Laar, Engineering manager (Aanbod, DevOps, Pararius Office)</em></p>

<hr />

<h4>Technical stack</h4>

<ul>
<li>Symfony</li>
<li>React</li>
<li>Kubernetes</li>
<li>Google Pub/Sub</li>
<li>SendGrid</li>
<li>Elasticsearch</li>
</ul>

                <hr style="margin-bottom: 5px" />
                <div style="font-size: 90%">
                    <a href="https://symfony.com/sponsor">Sponsor</a> the Symfony project.
                </div>
            ]]></content:encoded>
            <guid isPermaLink="false">https://symfony.com/blog/case-study-treehouse-servicing-the-rental-real-estate-market-with-symfony?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed</guid>
            <dc:creator><![CDATA[ Eloïse Charrier ]]></dc:creator>
            <pubDate>Wed, 10 Jun 2026 11:30:00 +0200</pubDate>
            <comments>https://symfony.com/blog/case-study-treehouse-servicing-the-rental-real-estate-market-with-symfony?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed#comments-list</comments>
        </item>
                        <item>
            <title><![CDATA[New in Symfony 8.1: DX Improvements (Part 1)]]></title>
            <link>https://symfony.com/blog/new-in-symfony-8-1-dx-improvements-part-1?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed</link>
            <description>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…</description>
            <content:encoded><![CDATA[
                                <p>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.</p>
<div class="section">
<h2 id="copy-requests-as-curl-commands"><a class="headerlink" href="#copy-requests-as-curl-commands" title="Permalink to this headline">Copy Requests as cURL Commands</a></h2>
<div class="blog-post-contributor-info">
    <div class="blog-post-contributor-avatar">
                    <a target="_blank" href="https://connect.symfony.com/profile/darkweak">
                <img src="https://connect.symfony.com/profile/darkweak.picture" alt="Sylvain Combraque">
            </a>
            </div>
    <div class="blog-post-contributor-contents">
        <span>Contributed by</span>
                    <a target="_blank" class="blog-post-contributor-name" href="https://connect.symfony.com/profile/darkweak">Sylvain Combraque</a>
                                        <span class="blog-post-contributor-prs"> in
                                    <a target="_blank" href="https://github.com/symfony/symfony/pull/62320">#62320</a>
                                                </span>
            </div>
</div>
<p>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 <code translate="no" class="notranslate">curl</code> command by hand from its headers, cookies,
and body is tedious and error-prone.</p>
<p>Symfony 8.1 adds a <strong>"Copy as cURL"</strong> 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.</p>
<div class="figure">
    <img alt="Symfony 8.1 profiler includes a feature to copy requests as cURL so you can replay them" class="align-center" src="https://symfony.com/uploads/assets/blog/symfony-8-1-profiler-copy-as-curl.png">
</div>
</div>
<div class="section">
<h2 id="a-more-accessible-web-debug-toolbar"><a class="headerlink" href="#a-more-accessible-web-debug-toolbar" title="Permalink to this headline">A More Accessible Web Debug Toolbar</a></h2>
<div class="blog-post-contributor-info">
    <div class="blog-post-contributor-avatar">
                    <a target="_blank" href="https://connect.symfony.com/profile/nitram1618">
                <img src="https://connect.symfony.com/profile/nitram1618.picture" alt="Martin Gilbert">
            </a>
            </div>
    <div class="blog-post-contributor-contents">
        <span>Contributed by</span>
                    <a target="_blank" class="blog-post-contributor-name" href="https://connect.symfony.com/profile/nitram1618">Martin Gilbert</a>
                                        <span class="blog-post-contributor-prs"> in
                                    <a target="_blank" href="https://github.com/symfony/symfony/pull/63943">#63943</a>
                                                </span>
            </div>
</div>
<p>The Web Debug Toolbar revealed its panels only on mouse hover, making the
information within them inaccessible to keyboard and screen reader users.</p>
<p>Symfony 8.1 makes the toolbar fully <strong>keyboard-navigable</strong> and adds the <strong>WAI-ARIA</strong>
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 <code translate="no" class="notranslate">toolbar</code> and <code translate="no" class="notranslate">dialog</code> roles.</p>
</div>
<div class="section">
<h2 id="sorting-routes-in-the-debug-router-command"><a class="headerlink" href="#sorting-routes-in-the-debug-router-command" title="Permalink to this headline">Sorting Routes in the <code translate="no" class="notranslate">debug:router</code> Command</a></h2>
<div class="blog-post-contributor-info">
    <div class="blog-post-contributor-avatar">
                    <a target="_blank" href="https://connect.symfony.com/profile/mthieulin">
                <img src="https://connect.symfony.com/profile/mthieulin.picture" alt="Michaël Thieulin">
            </a>
            </div>
    <div class="blog-post-contributor-contents">
        <span>Contributed by</span>
                    <a target="_blank" class="blog-post-contributor-name" href="https://connect.symfony.com/profile/mthieulin">Michaël Thieulin</a>
                                        <span class="blog-post-contributor-prs"> in
                                    <a target="_blank" href="https://github.com/symfony/symfony/pull/63905">#63905</a>
                                                </span>
            </div>
</div>
<p>The <code translate="no" class="notranslate">debug:router</code> 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 <code translate="no" class="notranslate">sort</code>, which broke the clickable
links to the route definitions.</p>
<p>Symfony 8.1 adds a <code translate="no" class="notranslate">--sort</code> option to sort routes by any column: <code translate="no" class="notranslate">name</code>,
<code translate="no" class="notranslate">path</code>, <code translate="no" class="notranslate">method</code>, <code translate="no" class="notranslate">scheme</code>, or <code translate="no" class="notranslate">host</code>:</p>
<div translate="no" data-loc="4" class="notranslate codeblock codeblock-length-sm codeblock-terminal codeblock-bash">
        <div class="codeblock-scroll">
        
        <pre class="codeblock-code"><code><span class="hljs-prompt">$ </span>php bin/console debug:router --sort=path

<span class="hljs-comment"># the option is case-insensitive and works for all output formats</span>
<span class="hljs-prompt">$ </span>php bin/console debug:router --sort=name --format=json</code></pre>
    </div>
</div>
</div>
<div class="section">
<h2 id="sorting-scheduled-messages-by-next-run-date"><a class="headerlink" href="#sorting-scheduled-messages-by-next-run-date" title="Permalink to this headline">Sorting Scheduled Messages by Next Run Date</a></h2>
<div class="blog-post-contributor-info">
    <div class="blog-post-contributor-avatar">
                    <a target="_blank" href="https://connect.symfony.com/profile/yoye_">
                <img src="https://connect.symfony.com/profile/yoye_.picture" alt="yoann aparici">
            </a>
            </div>
    <div class="blog-post-contributor-contents">
        <span>Contributed by</span>
                    <a target="_blank" class="blog-post-contributor-name" href="https://connect.symfony.com/profile/yoye_">yoann aparici</a>
                                        <span class="blog-post-contributor-prs"> in
                                    <a target="_blank" href="https://github.com/symfony/symfony/pull/63135">#63135</a>
                                                </span>
            </div>
</div>
<p>When you have many scheduled tasks, the <code translate="no" class="notranslate">debug:scheduler</code> 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.</p>
<p>Symfony 8.1 adds a <code translate="no" class="notranslate">--sort</code> option that orders recurring messages by their next
run date, so the task that runs soonest appears first:</p>
<div translate="no" data-loc="1" class="notranslate codeblock codeblock-length-sm codeblock-terminal codeblock-bash">
        <div class="codeblock-scroll">
        
        <pre class="codeblock-code"><code><span class="hljs-prompt">$ </span>php bin/console debug:scheduler --sort</code></pre>
    </div>
</div>
</div>
<div class="section">
<h2 id="matching-transport-names-with-regular-expressions"><a class="headerlink" href="#matching-transport-names-with-regular-expressions" title="Permalink to this headline">Matching Transport Names with Regular Expressions</a></h2>
<div class="blog-post-contributor-info">
    <div class="blog-post-contributor-avatar">
                    <a target="_blank" href="https://connect.symfony.com/profile/santysisi">
                <img src="https://connect.symfony.com/profile/santysisi.picture" alt="Santiago San Martin">
            </a>
            </div>
    <div class="blog-post-contributor-contents">
        <span>Contributed by</span>
                    <a target="_blank" class="blog-post-contributor-name" href="https://connect.symfony.com/profile/santysisi">Santiago San Martin</a>
                                        <span class="blog-post-contributor-prs"> in
                                    <a target="_blank" href="https://github.com/symfony/symfony/pull/62875">#62875</a>
                                                </span>
            </div>
</div>
<p>The <code translate="no" class="notranslate">messenger:consume</code> 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.</p>
<p>In Symfony 8.1, the transport names argument also accepts regular expressions,
which are matched against configured transport names:</p>
<div translate="no" data-loc="8" class="notranslate codeblock codeblock-length-sm codeblock-terminal codeblock-bash">
        <div class="codeblock-scroll">
        
        <pre class="codeblock-code"><code><span class="hljs-comment"># consume all transports whose name starts with "scheduler_"</span>
<span class="hljs-prompt">$ </span>php bin/console messenger:consume <span class="hljs-string">'scheduler_.*'</span>

<span class="hljs-comment"># use the (?i) flag for case-insensitive matching</span>
<span class="hljs-prompt">$ </span>php bin/console messenger:consume <span class="hljs-string">'(?i)async.*'</span>

<span class="hljs-comment"># you can still mix regular expressions and explicit names</span>
<span class="hljs-prompt">$ </span>php bin/console messenger:consume <span class="hljs-string">'scheduler_.*'</span> async</code></pre>
    </div>
</div>
<p>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.</p>
</div>
<div class="section">
<h2 id="mocking-non-shared-services-in-tests"><a class="headerlink" href="#mocking-non-shared-services-in-tests" title="Permalink to this headline">Mocking Non-Shared Services in Tests</a></h2>
<div class="blog-post-contributor-info">
    <div class="blog-post-contributor-avatar">
                    <a target="_blank" href="https://connect.symfony.com/profile/hypemc">
                <img src="https://connect.symfony.com/profile/hypemc.picture" alt="HypeMC">
            </a>
            </div>
    <div class="blog-post-contributor-contents">
        <span>Contributed by</span>
                    <a target="_blank" class="blog-post-contributor-name" href="https://connect.symfony.com/profile/hypemc">HypeMC</a>
                                        <span class="blog-post-contributor-prs"> in
                                    <a target="_blank" href="https://github.com/symfony/symfony/pull/62909">#62909</a>
                                                </span>
            </div>
</div>
<p>In functional tests, you can replace a service with a test double by calling
<code translate="no" class="notranslate">getContainer()-&gt;set()</code>. Until now this only worked for shared services;
non-shared services, which are created fresh every time they are requested, could
not be replaced.</p>
<p>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:</p>
<div translate="no" data-loc="15" class="notranslate codeblock codeblock-length-md codeblock-php">
        <div class="codeblock-scroll">
        
        <pre class="codeblock-code"><code><span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Bundle</span>\<span class="hljs-title">FrameworkBundle</span>\<span class="hljs-title">Test</span>\<span class="hljs-title">KernelTestCase</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OrderProcessorTest</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">KernelTestCase</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">testProcess</span><span class="hljs-params">()</span>: <span class="hljs-title">void</span>
    </span>{
        <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>container</span> = <span class="hljs-keyword">static</span>::<span class="hljs-title invoke__">getContainer</span>();

        <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>container</span>-&gt;<span class="hljs-title invoke__">set</span>(<span class="hljs-string">'app.payment_gateway'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">()</span> </span>{
            return <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>this</span>-&gt;<span class="hljs-title invoke__">createMock</span>(PaymentGateway::<span class="hljs-variable language_">class</span>);
        });

        <span class="hljs-comment">// ...</span>
    }
}</code></pre>
    </div>
</div>
</div>
<div class="section">
<h2 id="asserting-flash-messages-in-tests"><a class="headerlink" href="#asserting-flash-messages-in-tests" title="Permalink to this headline">Asserting Flash Messages in Tests</a></h2>
<div class="blog-post-contributor-info">
    <div class="blog-post-contributor-avatar">
                    <a target="_blank" href="https://connect.symfony.com/profile/pierstoval">
                <img src="https://connect.symfony.com/profile/pierstoval.picture" alt="Alex Rock">
            </a>
            </div>
    <div class="blog-post-contributor-contents">
        <span>Contributed by</span>
                    <a target="_blank" class="blog-post-contributor-name" href="https://connect.symfony.com/profile/pierstoval">Alex Rock</a>
                                        <span class="blog-post-contributor-prs"> in
                                    <a target="_blank" href="https://github.com/symfony/symfony/pull/60008">#60008</a>
                                                </span>
            </div>
</div>
<p>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.</p>
<p>Symfony 8.1 replaces all of that with a single assertion called
<code translate="no" class="notranslate">assertSessionHasFlashMessage()</code>:</p>
<div translate="no" data-loc="5" class="notranslate codeblock codeblock-length-sm codeblock-php">
        <div class="codeblock-scroll">
        
        <pre class="codeblock-code"><code><span class="hljs-comment">// assert that a flash message of the given type exists</span>
<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>this</span>-&gt;<span class="hljs-title invoke__">assertSessionHasFlashMessage</span>(<span class="hljs-string">'success'</span>);

<span class="hljs-comment">// optionally, assert its exact content too</span>
<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>this</span>-&gt;<span class="hljs-title invoke__">assertSessionHasFlashMessage</span>(<span class="hljs-string">'success'</span>, <span class="hljs-string">'Your changes were saved.'</span>);</code></pre>
    </div>
</div>
</div>
<div class="section">
<h2 id="controlling-time-in-messenger-stamps"><a class="headerlink" href="#controlling-time-in-messenger-stamps" title="Permalink to this headline">Controlling Time in Messenger Stamps</a></h2>
<div class="blog-post-contributor-info">
    <div class="blog-post-contributor-avatar">
                    <a target="_blank" href="https://connect.symfony.com/profile/bluemmb">
                <img src="https://connect.symfony.com/profile/bluemmb.picture" alt="Mohammad Eftekhari">
            </a>
            </div>
    <div class="blog-post-contributor-contents">
        <span>Contributed by</span>
                    <a target="_blank" class="blog-post-contributor-name" href="https://connect.symfony.com/profile/bluemmb">Mohammad Eftekhari</a>
                                        <span class="blog-post-contributor-prs"> in
                                    <a target="_blank" href="https://github.com/symfony/symfony/pull/62572">#62572</a>
                                                </span>
            </div>
</div>
<p>The <code translate="no" class="notranslate">DelayStamp</code> and <code translate="no" class="notranslate">RedeliveryStamp</code> classes computed time using PHP's native
<code translate="no" class="notranslate">time()</code> function and <code translate="no" class="notranslate">DateTimeImmutable</code> objects, making any logic that
depends on their values impossible to test deterministically.</p>
<p>Symfony 8.1 updates both stamps to rely on the <a href="https://symfony.com/doc/current/components/clock.html" class="reference external">Clock component</a> instead. In your
tests, you can now freeze time with <code translate="no" class="notranslate">MockClock</code> and get fully predictable delays
and redelivery timestamps:</p>
<div translate="no" data-loc="8" class="notranslate codeblock codeblock-length-sm codeblock-php">
        <div class="codeblock-scroll">
        
        <pre class="codeblock-code"><code><span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">Clock</span>\<span class="hljs-title">Clock</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">Clock</span>\<span class="hljs-title">MockClock</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">Messenger</span>\<span class="hljs-title">Stamp</span>\<span class="hljs-title">RedeliveryStamp</span>;

Clock::<span class="hljs-title invoke__">set</span>(<span class="hljs-keyword">new</span> <span class="hljs-title invoke__">MockClock</span>(<span class="hljs-string">'2026-01-01 00:00:00'</span>));

<span class="hljs-comment">// the stamp now reads the current time from the frozen clock</span>
<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>stamp</span> = <span class="hljs-keyword">new</span> <span class="hljs-title invoke__">RedeliveryStamp</span>(<span class="hljs-attr">retryCount</span>: <span class="hljs-number">1</span>);</code></pre>
    </div>
</div>
</div>
                <hr style="margin-bottom: 5px" />
                <div style="font-size: 90%">
                    <a href="https://symfony.com/sponsor">Sponsor</a> the Symfony project.
                </div>
            ]]></content:encoded>
            <guid isPermaLink="false">https://symfony.com/blog/new-in-symfony-8-1-dx-improvements-part-1?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed</guid>
            <dc:creator><![CDATA[ Javier Eguiluz ]]></dc:creator>
            <pubDate>Wed, 10 Jun 2026 08:35:00 +0200</pubDate>
            <comments>https://symfony.com/blog/new-in-symfony-8-1-dx-improvements-part-1?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed#comments-list</comments>
        </item>
                        <item>
            <title><![CDATA[SymfonyOnline June 2026: Reconfiguring Symfony​ in real time​ with sidekicks]]></title>
            <link>https://symfony.com/blog/symfonyonline-june-2026-reconfiguring-symfony-in-real-time-with-sidekicks?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed</link>
            <description>
    

To wrap up an amazing lineup, SymfonyOnline June 2026 will stream its final expert sessions live online on June 12, 2026.

🎤 Speaker announcement!

Don&#039;t miss Nicolas Grekas for the talk &quot;Reconfiguring Symfony in real time with sidekicks&quot;:

&quot;PHP was…</description>
            <content:encoded><![CDATA[
                                <p><a class="block text-center" href="https://live.symfony.com/2026-online-june" title="Nl Blog Banner 2026 04 17T163921 336">
    <img src="https://symfony.com/uploads/assets/blog/NL-BLOG-Banner-2026-04-17T163921-336.png" alt="Nl Blog Banner 2026 04 17T163921 336">
</a>
To wrap up an amazing lineup, <strong><a href="https://live.symfony.com/2026-online-june">SymfonyOnline June 2026</a></strong> will stream its final expert sessions live online on June 12, 2026.</p>

<h3>🎤 Speaker announcement!</h3>

<p>Don't miss <strong><a href="https://connect.symfony.com/profile/nicolas-grekas">Nicolas Grekas</a></strong> for the talk <strong><a href="https://live.symfony.com/2026-online-june/schedule/reconfiguring-symfony-in-real-time-with-sidekicks">"Reconfiguring Symfony in real time with sidekicks"</a></strong>:</p>

<p>"PHP was long designed as a strictly stateless language: one request, one process, and then everything starts over.</p>

<p>FrankenPHP fundamentally changes this model by allowing long-running PHP workers to run directly within a Symfony application. Not to turn Symfony into a Node-style server, but to give it capabilities it has never had before.</p>

<p>In this talk, I introduce a new pattern: application sidekicks. These are specialized PHP workers, outside the HTTP lifecycle, that listen to their environment and reconfigure the application in real time; without polling, without approximate TTLs, and without redeployments.</p>

<p>Through concrete use cases (Redis Sentinel discovery, dynamic feature flags, etc.), we’ll see how to evolve an existing Symfony application while staying true to its principles.</p>

<p>The goal: to show that PHP can listen, react, and adapt in real time, without sacrificing its simplicity nor its traditional robustness."</p>

<p>👉 Discover more talks by reading the <strong><a href="https://live.symfony.com/2026-online-june/schedule">full schedule</a></strong></p>

<p>✨ Key Feature: All talks are pre-recorded to ensure the highest technical quality, but speakers will be joining us live to answer your questions in real-time during the dedicated Q&amp;A sessions!</p>

<hr />

<h3>🎟️ Register now!</h3>

<p>Join the global PHP &amp; Symfony community from the comfort of your home or office by clicking <strong><a href="https://live.symfony.com/2026-online-june/registration/">here</a></strong>.</p>

<ul>
<li>▶️ <strong>Instant Replays</strong>: Missed a session? All talks are available for replay as soon as they start.</li>
<li>🆒 <strong>Accessibility</strong>: English subtitles are provided for all presentations.</li>
</ul>

<p>We look forward to seeing you online to explore the future of PHP together!</p>

<hr />

<h3>Join us online!</h3>

<p>💡Follow the "conference" blog posts to not miss anything!</p>

<p>Want the latest Symfony updates? Follow us and tune in from wherever you are 🌎</p>

<p><a class="block text-center" href="https://linktr.ee/symfony">
   <img src="https://symfony.com/uploads/assets/blog/Banner-BLOG.png" alt="Banner Blog">
</a></p>

                <hr style="margin-bottom: 5px" />
                <div style="font-size: 90%">
                    <a href="https://symfony.com/sponsor">Sponsor</a> the Symfony project.
                </div>
            ]]></content:encoded>
            <guid isPermaLink="false">https://symfony.com/blog/symfonyonline-june-2026-reconfiguring-symfony-in-real-time-with-sidekicks?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed</guid>
            <dc:creator><![CDATA[ Eloïse Charrier ]]></dc:creator>
            <pubDate>Tue, 09 Jun 2026 15:00:00 +0200</pubDate>
            <comments>https://symfony.com/blog/symfonyonline-june-2026-reconfiguring-symfony-in-real-time-with-sidekicks?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed#comments-list</comments>
        </item>
                        <item>
            <title><![CDATA[SymfonyOnline June 2026: Coding at the speed of thought: Symfony DX in 2026]]></title>
            <link>https://symfony.com/blog/symfonyonline-june-2026-coding-at-the-speed-of-thought-symfony-dx-in-2026?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed</link>
            <description>
    

The wait is over! SymfonyOnline June 2026 is coming to you live online on June 11-12, 2026, featuring an incredible lineup of expert speakers.

🎤 Speaker announcement!

We are thrilled to welcome Kévin Dunglas with his talk &quot;Coding at the speed of…</description>
            <content:encoded><![CDATA[
                                <p><a class="block text-center" href="https://live.symfony.com/2026-online-june" title="Nl Blog Banner 2026 04 17T163921 336">
    <img src="https://symfony.com/uploads/assets/blog/NL-BLOG-Banner-2026-04-17T163921-336.png" alt="Nl Blog Banner 2026 04 17T163921 336">
</a>
The wait is over! <strong><a href="https://live.symfony.com/2026-online-june">SymfonyOnline June 2026</a></strong> is coming to you live online on June 11-12, 2026, featuring an incredible lineup of expert speakers.</p>

<h3>🎤 Speaker announcement!</h3>

<p>We are thrilled to welcome <strong><a href="https://connect.symfony.com/profile/dunglas">Kévin Dunglas</a></strong> with his talk <strong><a href="https://live.symfony.com/2026-online-june/schedule/coding-at-the-speed-of-thought-symfony-dx-in-2026">"Coding at the speed of thought: Symfony DX in 2026"</a></strong>:</p>

<p>"Forget everything you know about setting up a PHP development environment. No more complex configurations, Docker permission headaches, or agonizingly slow application cache warmups every time you hit refresh (Sylius, I'm looking at you).</p>

<p>In 2026, FrankenPHP has not only revolutionized production, but it is also totally redefining the Developer Experience (DX) of Symfony.</p>

<p>In this talk, I will show you how we've pushed the boundaries of the application server to offer a fluid, instant, and modern workflow. We will see how to:</p>

<ul>
<li>Start a project in seconds: A simple command is all it takes to get a fully-featured, standalone environment, complete with automatic HTTPS. Whether you are on Linux or macOS, natively on Windows thanks to the bleeding-edge features of Go 1.26, or using the official Symfony Docker stack with Dev Containers, the experience is unified and immediate.</li>
<li>Get instant feedback (Hot Reloading): Stop hammering the F5 key. FrankenPHP now watches your files and brings true hot reloading to PHP. By dispatching Mercure updates directly to the browser, your app updates on the fly when you change code, just like modern JS frameworks!</li>
<li>Eliminate load times: Worker mode is no longer just for prod. In dev, it keeps your app hot and instantly refreshes the Symfony cache in the background when you save a file in your editor. The result? Pages that are available dramatically faster, reducing wait times even for the heaviest applications.</li>
</ul>

<p>As a bonus, we'll even see how this new ecosystem seamlessly integrates with AI coding agents like Claude Code inside a fully autonomous, sandboxed environment.</p>

<p>Join me to discover how the power of Go and Caddy, coupled with the flexibility of Symfony, allows you to focus on what matters: your code.</p>

<p>The future of PHP development is here, and it is lightning fast. 🐘🧟⚡️"</p>

<p>👉 Discover more talks by reading the <strong><a href="https://live.symfony.com/2026-online-june/schedule">full schedule</a></strong></p>

<p>✨ Key Feature: All talks are pre-recorded to ensure the highest technical quality, but speakers will be joining us live to answer your questions in real-time during the dedicated Q&amp;A sessions!</p>

<hr />

<h3>🛠️ Pre-conference Workshops (June 9-10)</h3>

<p>Don't forget that the conference is preceded by two days of hands-on technical workshops. These small-group sessions are the perfect opportunity to master specific Symfony features under the guidance of certified trainers: <strong><a href="https://live.symfony.com/2026-online-june/workshop">Discover the topics!</a></strong></p>

<p>Note: Workshop spots are limited and no replays are available for these sessions to ensure an interactive learning experience.</p>

<h3>🎟️ Register now!</h3>

<p>Join the global PHP &amp; Symfony community from the comfort of your home or office by clicking <strong><a href="https://live.symfony.com/2026-online-june/registration/">here</a></strong>.</p>

<ul>
<li>▶️ <strong>Instant Replays</strong>: Missed a session? All talks are available for replay as soon as they start.</li>
<li>🆒 <strong>Accessibility</strong>: English subtitles are provided for all presentations.</li>
</ul>

<p>We look forward to seeing you online to explore the future of PHP together!</p>

<hr />

<h3>Join us online!</h3>

<p>💡Follow the "conference" blog posts to not miss anything!</p>

<p>Want the latest Symfony updates? Follow us and tune in from wherever you are 🌎</p>

<p><a class="block text-center" href="https://linktr.ee/symfony">
   <img src="https://symfony.com/uploads/assets/blog/Banner-BLOG.png" alt="Banner Blog">
</a></p>

                <hr style="margin-bottom: 5px" />
                <div style="font-size: 90%">
                    <a href="https://symfony.com/sponsor">Sponsor</a> the Symfony project.
                </div>
            ]]></content:encoded>
            <guid isPermaLink="false">https://symfony.com/blog/symfonyonline-june-2026-coding-at-the-speed-of-thought-symfony-dx-in-2026?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed</guid>
            <dc:creator><![CDATA[ Eloïse Charrier ]]></dc:creator>
            <pubDate>Mon, 08 Jun 2026 14:52:00 +0200</pubDate>
            <comments>https://symfony.com/blog/symfonyonline-june-2026-coding-at-the-speed-of-thought-symfony-dx-in-2026?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed#comments-list</comments>
        </item>
                        <item>
            <title><![CDATA[SymfonyOnline June 2026: Dealing with audit logs]]></title>
            <link>https://symfony.com/blog/symfonyonline-june-2026-dealing-with-audit-logs?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed</link>
            <description>
    

Join web developers from all over the world this week for SymfonyOnline June 2026, broadcasting live on June 11-12, 2026.

🎤 Speaker announcement!

We are thrilled to welcome Hubert Lenoir with his talk &quot;Dealing with audit logs&quot;:

&quot;Audit logs are essential…</description>
            <content:encoded><![CDATA[
                                <p><a class="block text-center" href="https://live.symfony.com/2026-online-june" title="Nl Blog Banner 2026 04 17T163921 336">
    <img src="https://symfony.com/uploads/assets/blog/NL-BLOG-Banner-2026-04-17T163921-336.png" alt="Nl Blog Banner 2026 04 17T163921 336">
</a>
Join web developers from all over the world this week for <strong><a href="https://live.symfony.com/2026-online-june">SymfonyOnline June 2026</a></strong>, broadcasting live on June 11-12, 2026.</p>

<h3>🎤 Speaker announcement!</h3>

<p>We are thrilled to welcome <strong><a href="https://connect.symfony.com/profile/hubert_lenoir">Hubert Lenoir</a></strong> with his talk <strong><a href="https://live.symfony.com/2026-online-june/schedule/dealing-with-audit-logs">"Dealing with audit logs"</a></strong>:</p>

<p>"Audit logs are essential for compliance, debugging, and security. But setting them up quickly raises real questions: what should we capture? Where to store them? How do we stay GDPR-compliant? How do we avoid leaking sensitive data?</p>

<p>In this talk, we explore how we deal with audit logs in our Symfony projects at SensioLabs: the concrete needs, the classic traps, a tour of the existing bundles, ideas to build a custom audit logger, and how to consume the logs."</p>

<p>👉 Discover more talks by reading the <strong><a href="https://live.symfony.com/2026-online-june/schedule">full schedule</a></strong></p>

<p>✨ Key Feature: All talks are pre-recorded to ensure the highest technical quality, but speakers will be joining us live to answer your questions in real-time during the dedicated Q&amp;A sessions!</p>

<hr />

<h3>🛠️ Pre-conference Workshops (June 9-10)</h3>

<p>Don't forget that the conference is preceded by two days of hands-on technical workshops. These small-group sessions are the perfect opportunity to master specific Symfony features under the guidance of certified trainers: <strong><a href="https://live.symfony.com/2026-online-june/workshop">Discover the topics!</a></strong></p>

<p>Note: Workshop spots are limited and no replays are available for these sessions to ensure an interactive learning experience.</p>

<h3>🎟️ Register now!</h3>

<p>Join the global PHP &amp; Symfony community from the comfort of your home or office by clicking <strong><a href="https://live.symfony.com/2026-online-june/registration/">here</a></strong>.</p>

<ul>
<li>▶️ <strong>Instant Replays</strong>: Missed a session? All talks are available for replay as soon as they start.</li>
<li>🆒 <strong>Accessibility</strong>: English subtitles are provided for all presentations.</li>
</ul>

<p>We look forward to seeing you online to explore the future of PHP together!</p>

<hr />

<h3>Join us online!</h3>

<p>💡Follow the "conference" blog posts to not miss anything!</p>

<p>Want the latest Symfony updates? Follow us and tune in from wherever you are 🌎</p>

<p><a class="block text-center" href="https://linktr.ee/symfony">
   <img src="https://symfony.com/uploads/assets/blog/Banner-BLOG.png" alt="Banner Blog">
</a></p>

                <hr style="margin-bottom: 5px" />
                <div style="font-size: 90%">
                    <a href="https://symfony.com/sponsor">Sponsor</a> the Symfony project.
                </div>
            ]]></content:encoded>
            <guid isPermaLink="false">https://symfony.com/blog/symfonyonline-june-2026-dealing-with-audit-logs?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed</guid>
            <dc:creator><![CDATA[ Eloïse Charrier ]]></dc:creator>
            <pubDate>Mon, 08 Jun 2026 10:46:00 +0200</pubDate>
            <comments>https://symfony.com/blog/symfonyonline-june-2026-dealing-with-audit-logs?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed#comments-list</comments>
        </item>
                        <item>
            <title><![CDATA[A Week of Symfony #1014 (June 1–7, 2026)]]></title>
            <link>https://symfony.com/blog/a-week-of-symfony-1014-june-1-7-2026?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed</link>
            <description>This week, Symfony focused on bug fixes for the recent Symfony 8.1 release. Meanwhile, we published more details about the upcoming SymfonyOnline June 2026 conference.

Symfony development highlights

This week, 39 pull requests were merged (33 in code and…</description>
            <content:encoded><![CDATA[
                                <p>This week, Symfony focused on bug fixes for the recent <a href="https://symfony.com/blog/symfony-8-1-0-released">Symfony 8.1 release</a>. Meanwhile, we published more details about the upcoming <a href="https://live.symfony.com/2026-online-june/">SymfonyOnline June 2026</a> conference.</p>

<h2>Symfony development highlights</h2>

<p>This week, 39 pull requests were merged (33 in code and 6 in docs) and 20 issues were closed (19 in code and 1 in docs). Excluding merges, 25 authors made 1,845 additions and 5,515 deletions. See details for <a href="https://github.com/symfony/symfony/pulse">code</a> and <a href="https://github.com/symfony/symfony-docs/pulse">docs</a>.</p>

<p><a href="https://github.com/symfony/symfony/commits/6.4">6.4 changelog</a>:</p>

<ul>
<li><a href="https://github.com/symfony/symfony/commit/f55035b76ff1f5b1e0fb7931e7d4ed6fbbb8556e">f55035b</a>: &#91;Mailer&#93; fix inline images in MandrillApiTransport by using the Content-ID as image name</li>
<li><a href="https://github.com/symfony/symfony/commit/2a1be845331f20af8b6ffe15ecea5893e207e7c8">2a1be84</a>: &#91;Notifier&#93; send message to MS-Teams via Workflow</li>
<li><a href="https://github.com/symfony/symfony/commit/9b185f599292969ea987391142e598622e3b0062">9b185f5</a>: &#91;Form&#93; add missing translation for invalid UUID</li>
<li><a href="https://github.com/symfony/symfony/commit/a79faa0ebdeae1fec688351dbc395341d314ac48">a79faa0</a>: &#91;HttpKernel, Security&#93; add allowed_classes => false to unserialize() in CacheWarmerAggregate</li>
<li><a href="https://github.com/symfony/symfony/commit/d988b226edaba4964d71ac43a3daa79a41d17aca">d988b22</a>: &#91;Form&#93; translate TranslatableInterface label in violation messages</li>
<li><a href="https://github.com/symfony/symfony/commit/1be0a58d8dbe07493b3bba99cf217577a6ad9d48">1be0a58</a>: &#91;Translation&#93; copy domains metadata when moving messages to intl ones</li>
<li><a href="https://github.com/symfony/symfony/commit/3d7304bb7131293caeeeb5e3d12a167b5ee062aa">3d7304b</a>: &#91;HttpFoundation&#93; add RFC6598 Shared Address Space to IpUtils::PRIVATE_SUBNETS</li>
<li><a href="https://github.com/symfony/symfony/commit/6ae13cf4f49e022da395d8472f106fea8c15f8d8">6ae13cf</a>: &#91;Webhook&#93; fix Content-Type key in createRequest method</li>
<li><a href="https://github.com/symfony/symfony/commit/db3c26be6772e8a5fca36c5574f166be7167d477">db3c26b</a>: &#91;AssetMapper&#93; render an empty import map as a JSON object</li>
</ul>

<p><a href="https://github.com/symfony/symfony/commits/7.4">7.4 changelog</a>:</p>

<ul>
<li><a href="https://github.com/symfony/symfony/commit/dbda9c21cf02fc1ea80b3887c4dd4de33ccd2a7f">dbda9c2</a>: &#91;Validator&#93; support SVG dimensions with units</li>
<li><a href="https://github.com/symfony/symfony/commit/3c2e8b99b53acf0a9db93930d93b39f1e838c183">3c2e8b9</a>: &#91;Serializer&#93; keep collection value type for iterable constructor parameters</li>
</ul>

<p><a href="https://github.com/symfony/symfony/commits/8.1">8.1 changelog</a>:</p>

<ul>
<li><a href="https://github.com/symfony/symfony/commit/ba829ca9394ee0aabdfe892083c2e588cf5a9a25">ba829ca</a>: &#91;HttpKernel&#93; fix TypeError in ResponseEvent when argument resolution throws</li>
<li><a href="https://github.com/symfony/symfony/commit/74fb98927be262f95debb71c7d8fdc1e3fa84dc0">74fb989</a>: &#91;HttpKernel&#93; fix #[MapRequestPayload] being handled before #[IsGranted]</li>
<li><a href="https://github.com/symfony/symfony/commit/407b535e8521174c77b5e8eabd9870765ebaa147">407b535</a>: &#91;DomCrawler&#93; remove final keyword on ChoiceFormField::addChoice()</li>
<li><a href="https://github.com/symfony/symfony/commit/683490b6de0d35e0abc3a73372e1c6e0c8cfac44">683490b</a>: &#91;FrameworkBundle&#93; fix dumping the debug container on cache:clear/cache:warmup</li>
<li><a href="https://github.com/symfony/symfony/commit/313333cbd78c8323036c195078fccf26cf2f6273">313333c</a>: &#91;HttpKernel&#93; add @template on ControllerAttributeEvent</li>
<li><a href="https://github.com/symfony/symfony/commit/e5dbdb80ce52fc3642b9f658c2beca133ecc291b">e5dbdb8</a>: &#91;DependencyInjection&#93; improve TaggedIteratorArgument deprecation warning</li>
</ul>

<h2>Newest issues and pull requests</h2>

<ul>
<li><a href="https://github.com/symfony/symfony/pull/64427">Add Encryption component</a></li>
<li><a href="https://github.com/symfony/symfony/issues/64454">Increase maximum variable name length in routes</a></li>
<li><a href="https://github.com/symfony/symfony/pull/64447">[HttpKernel] Add #[AsControllerAttributeListener]</a></li>
<li><a href="https://github.com/symfony/symfony/pull/64455">[HttpFoundation] Deprecate not passing an expiry to UriSigner::sign()</a></li>
</ul>

<h2>Symfony Jobs</h2>

<p>These are some of the most recent Symfony job offers:</p>

<ul>
<li><strong>Lead Symfony Developer</strong> at DocuPet<br>
Full-time - CA$140,000 – CA$180,000 / year<br>
Full remote<br>
<a href="https://symfony.com/jobs/b6a97b9">View details</a></li>
<li><strong>Backend Symfony Developer</strong> at KRUU GmbH<br>
Full-time - €60,000 – €75,000 / month<br>
Remote + part-time onsite (Bad Friedrichshall, Germany)<br>
<a href="https://symfony.com/jobs/b149b01">View details</a></li>
<li><strong>DevOps for a Symfony project</strong> at Cloudpepper<br>
Full-time - $150,000 – $180,000 / year<br>
Full remote<br>
<a href="https://symfony.com/jobs/a9262d7">View details</a></li>
<li><strong>Symfony Developer</strong> at Design Force Marketing<br>
Full-time - $60,000 – $100,000 / year<br>
Grand Haven Michigan, United States<br>
<a href="https://symfony.com/jobs/5ad3b96">View details</a></li>
<li><strong>Backend Symfony Developer</strong> at ShipMonk<br>
Contract / Freelance - $5,000 – $8,000 / month<br>
Full remote<br>
<a href="https://symfony.com/jobs/2bb5783">View details</a></li>
</ul>

<p>You can <a href="https://symfony.com/jobs">publish a Symfony job offer for free</a> on symfony.com.</p>

<h2>They talked about us</h2>

<ul>
<li><a href="https://dev.to/mattleads/the-soc-2-blueprint-beyond-rbac-with-applevel-infrastructure-isolation-key-sharding-part-2-38c4">The SOC 2 Blueprint: Beyond RBAC with AppLevel Infrastructure Isolation &amp; Key Sharding. Part #2</a></li>
<li><a href="https://dev.to/ohugonnot/auditing-a-legacy-symfony-project-where-to-start-without-doing-everything-twice-3p43">Auditing a Legacy Symfony Project: Where to Start Without Doing Everything Twice</a></li>
<li><a href="https://dev.to/carlosgude/integrationengine-a-symfony-bundle-that-centralises-your-external-api-integrations-1bae">IntegrationEngine — a Symfony bundle that centralises your external API integrations</a></li>
<li><a href="https://pierre-schwartz.medium.com/providing-mcp-support-into-a-symfony-project-05be473cdc03">Providing MCP support into a Symfony project</a></li>
<li><a href="https://medium.com/@martselcuk/from-get-to-apiresource-the-final-phase-of-a-php-modernization-d4d528bc3e8c">From $_GET to #[ApiResource]: The Final Phase of a PHP Modernization</a></li>
<li><a href="https://medium.com/@liloulafoudre/%EF%B8%8F-symfony-8-1-vient-de-supprimer-une-corv%C3%A9e-que-tous-les-devs-php-subissaient-en-silence-f64ec8d16ce2">Symfony 8.1 vient de supprimer une corvée que tous les développeur devs PHP subissaient en silence</a></li>
</ul>

<h2>Upcoming Symfony Events</h2>

<ul>
<li><a href="https://www.meetup.com/symfony-php-meetup-barcelona-by-sensiolabs/events/313664247/">Symfony/PHP Meetup Barcelona by SensioLabs</a>: Barcelona, Spain (June 25, 2026)</li>
<li><a href="https://websummercamp.com/2026">Web Summer Camp 2026</a>: Opatija, Croatia (July 2, 2026 – July 4, 2026)</li>
</ul>

<h2>Call to Action</h2>

<ul>
<li>Follow Symfony <a href="https://x.com/symfony">on X</a>, <a href="https://mastodon.social/@symfony">on Mastodon</a>, <a href="https://bsky.app/profile/symfony.com">on Bluesky</a> and <a href="https://www.threads.net/@symfony">on Threads</a> and share this article.</li>
<li><a href="https://feeds.feedburner.com/symfony/blog">Subscribe to the Symfony blog RSS</a> and never miss a Symfony story again.</li>
</ul>

                <hr style="margin-bottom: 5px" />
                <div style="font-size: 90%">
                    <a href="https://symfony.com/sponsor">Sponsor</a> the Symfony project.
                </div>
            ]]></content:encoded>
            <guid isPermaLink="false">https://symfony.com/blog/a-week-of-symfony-1014-june-1-7-2026?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed</guid>
            <dc:creator><![CDATA[ Javier Eguiluz ]]></dc:creator>
            <pubDate>Sun, 07 Jun 2026 09:11:00 +0200</pubDate>
            <comments>https://symfony.com/blog/a-week-of-symfony-1014-june-1-7-2026?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed#comments-list</comments>
        </item>
                        <item>
            <title><![CDATA[New in Symfony 8.1: Console Progress and Testing Improvements]]></title>
            <link>https://symfony.com/blog/new-in-symfony-8-1-console-progress-and-testing-improvements?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed</link>
            <description>The Console component has been a major focus of Symfony 8.1, and we&#039;ve already
covered a lot of new features in previous articles: improved console input,
console argument resolvers, method-based commands, and HTTP-less Symfony applications.
This article…</description>
            <content:encoded><![CDATA[
                                <p>The <a href="https://symfony.com/console" class="reference external">Console component</a> has been a major focus of Symfony 8.1, and we've already
covered a lot of new features in previous articles: <a href="https://symfony.com/blog/new-in-symfony-8-1-improved-console-input" class="reference external">improved console input</a>,
<a href="https://symfony.com/blog/new-in-symfony-8-1-console-argument-resolvers" class="reference external">console argument resolvers</a>, <a href="https://symfony.com/blog/new-in-symfony-8-1-method-based-commands" class="reference external">method-based commands</a>, and <a href="https://symfony.com/blog/new-in-symfony-8-1-http-less-symfony-applications" class="reference external">HTTP-less Symfony applications</a>.
This article showcases more console improvements related to styling and testing.</p>
<div class="section">
<h2 id="reporting-progress-to-the-terminal-taskbar"><a class="headerlink" href="#reporting-progress-to-the-terminal-taskbar" title="Permalink to this headline">Reporting Progress to the Terminal Taskbar</a></h2>
<div class="blog-post-contributor-info">
    <div class="blog-post-contributor-avatar">
                    <a target="_blank" href="https://github.com/canvural">
                <img src="https://github.com/canvural.png" alt="Can Vural">
            </a>
            </div>
    <div class="blog-post-contributor-contents">
        <span>Contributed by</span>
                    <a target="_blank" class="blog-post-contributor-name" href="https://github.com/canvural">Can Vural</a>
                                        <span class="blog-post-contributor-prs"> in
                                    <a target="_blank" href="https://github.com/symfony/symfony/pull/62112">#62112</a>
                                                </span>
            </div>
</div>
<p>Modern terminals can display progress information in the window title or taskbar
using the <a href="https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences" class="reference external" rel="external noopener noreferrer" target="_blank">OSC 9;4 escape sequence</a>. In Symfony 8.1, progress bars emit this
sequence automatically. As soon as a bar starts on a decorated output, the
terminal reflects its progress, and <code translate="no" class="notranslate">finish()</code> or <code translate="no" class="notranslate">clear()</code> reset it.</p>
<p>Terminals that don't understand the sequence simply ignore it, so this feature
works everywhere and requires no configuration. When several progress bars run
at the same time, the indicator falls back to an indeterminate state to avoid
flickering between unrelated percentages.</p>
<p>If your command prompts the user while a progress bar is running, you can pause
the indicator with the static <code translate="no" class="notranslate">pauseAll()</code> and <code translate="no" class="notranslate">resumeAll()</code> methods:</p>
<div translate="no" data-loc="5" class="notranslate codeblock codeblock-length-sm codeblock-php">
        <div class="codeblock-scroll">
        
        <pre class="codeblock-code"><code><span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">Console</span>\<span class="hljs-title">Helper</span>\<span class="hljs-title">ProgressBar</span>;

ProgressBar::<span class="hljs-title invoke__">pauseAll</span>();
<span class="hljs-comment">// ... ask the user something ...</span>
ProgressBar::<span class="hljs-title invoke__">resumeAll</span>();</code></pre>
    </div>
</div>
<p>This is how it looks in practice:</p>
<div class="figure">
    <img alt="Symfony 8 1 Console Progress" class="align-center" src="https://symfony.com/uploads/assets/blog/symfony-8-1-console-progress.gif">
</div>
</div>
<div class="section">
<h2 id="customizing-the-progress-bar-format"><a class="headerlink" href="#customizing-the-progress-bar-format" title="Permalink to this headline">Customizing the Progress Bar Format</a></h2>
<div class="blog-post-contributor-info">
    <div class="blog-post-contributor-avatar">
                    <a target="_blank" href="https://connect.symfony.com/profile/guillaume_vdp">
                <img src="https://connect.symfony.com/profile/guillaume_vdp.picture" alt="Guillaume Van Der Putten">
            </a>
            </div>
    <div class="blog-post-contributor-contents">
        <span>Contributed by</span>
                    <a target="_blank" class="blog-post-contributor-name" href="https://connect.symfony.com/profile/guillaume_vdp">Guillaume Van Der Putten</a>
                                        <span class="blog-post-contributor-prs"> in
                                    <a target="_blank" href="https://github.com/symfony/symfony/pull/63443">#63443</a>
                                                </span>
            </div>
</div>
<p>The <code translate="no" class="notranslate">SymfonyStyle</code> helper lets you display progress bars with a single method
call, but until now you couldn't change their format without creating the bar
first and then calling <code translate="no" class="notranslate">setFormat()</code> on it. Symfony 8.1 adds an optional
<code translate="no" class="notranslate">$format</code> argument to <code translate="no" class="notranslate">createProgressBar()</code>, <code translate="no" class="notranslate">progressStart()</code>, and
<code translate="no" class="notranslate">progressIterate()</code>, allowing you to set a custom format in a single step:</p>
<div translate="no" data-loc="10" class="notranslate codeblock codeblock-length-md codeblock-php">
        <div class="codeblock-scroll">
        
        <pre class="codeblock-code"><code><span class="hljs-comment">// the format is the last argument, after the optional number of steps</span>
<span class="hljs-keyword">foreach</span> (<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>io</span>-&gt;<span class="hljs-title invoke__">progressIterate</span>(<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>items</span>, <span class="hljs-keyword">null</span>, <span class="hljs-string">' %current%/%max% [%bar%] %memory:6s%'</span>) <span class="hljs-keyword">as</span> <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>item</span>) {
    <span class="hljs-comment">// ...</span>
}

<span class="hljs-comment">// the same argument is available when starting a bar manually...</span>
<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>io</span>-&gt;<span class="hljs-title invoke__">progressStart</span>(<span class="hljs-number">100</span>, <span class="hljs-string">' %current%/%max% [%bar%] %memory:6s%'</span>);

<span class="hljs-comment">// ... or when creating a standalone progress bar</span>
<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>progressBar</span> = <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>io</span>-&gt;<span class="hljs-title invoke__">createProgressBar</span>(<span class="hljs-number">100</span>, <span class="hljs-string">' %current%/%max% [%bar%] %memory:6s%'</span>);</code></pre>
    </div>
</div>
</div>
<div class="section">
<h2 id="a-result-based-testing-api-for-commands"><a class="headerlink" href="#a-result-based-testing-api-for-commands" title="Permalink to this headline">A Result-Based Testing API for Commands</a></h2>
<div class="blog-post-contributor-info">
    <div class="blog-post-contributor-avatar">
                    <a target="_blank" href="https://connect.symfony.com/profile/theofidry">
                <img src="https://connect.symfony.com/profile/theofidry.picture" alt="Théo Fidry">
            </a>
            </div>
    <div class="blog-post-contributor-contents">
        <span>Contributed by</span>
                    <a target="_blank" class="blog-post-contributor-name" href="https://connect.symfony.com/profile/theofidry">Théo Fidry</a>
                                        <span class="blog-post-contributor-prs"> in
                                    <a target="_blank" href="https://github.com/symfony/symfony/pull/61494">#61494</a>
                                                </span>
            </div>
</div>
<p>Testing a command that writes to both standard output and standard error
previously required the <code translate="no" class="notranslate">capture_stderr_separately</code> option and two separate
calls, which lost the original output ordering. Symfony 8.1 introduces a
result-based testing API: the new <code translate="no" class="notranslate">CommandTester::run()</code> method returns an
<code translate="no" class="notranslate">ExecutionResult</code> object that exposes all three views at once:</p>
<div translate="no" data-loc="8" class="notranslate codeblock codeblock-length-sm codeblock-php">
        <div class="codeblock-scroll">
        
        <pre class="codeblock-code"><code><span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">Console</span>\<span class="hljs-title">Tester</span>\<span class="hljs-title">CommandTester</span>;

<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>result</span> = (<span class="hljs-keyword">new</span> <span class="hljs-title invoke__">CommandTester</span>(<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>command</span>))-&gt;<span class="hljs-title invoke__">run</span>([<span class="hljs-string">'username'</span> =&gt; <span class="hljs-string">'Wouter'</span>]);

<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>result</span>-&gt;<span class="hljs-title invoke__">getDisplay</span>();      <span class="hljs-comment">// stdout and stderr, interleaved</span>
<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>result</span>-&gt;<span class="hljs-title invoke__">getOutput</span>();       <span class="hljs-comment">// stdout only</span>
<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>result</span>-&gt;<span class="hljs-title invoke__">getErrorOutput</span>();  <span class="hljs-comment">// stderr only</span>
<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>result</span>-&gt;statusCode;        <span class="hljs-comment">// the command exit code</span></code></pre>
    </div>
</div>
<p>The <code translate="no" class="notranslate">run()</code> method also accepts interactive answers directly, so you no
longer need a separate <code translate="no" class="notranslate">setInputs()</code> call. Combined with the new
<code translate="no" class="notranslate">ConsoleAssertionsTrait</code>, tests become more expressive:</p>
<div translate="no" data-loc="18" class="notranslate codeblock codeblock-length-md codeblock-php">
        <div class="codeblock-scroll">
        
        <pre class="codeblock-code"><code><span class="hljs-keyword">use</span> <span class="hljs-title">PHPUnit</span>\<span class="hljs-title">Framework</span>\<span class="hljs-title">TestCase</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">Console</span>\<span class="hljs-title">Tester</span>\<span class="hljs-title">ConsoleAssertionsTrait</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CreateUserCommandTest</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">TestCase</span>
</span>{
    <span class="hljs-keyword">use</span> <span class="hljs-title">ConsoleAssertionsTrait</span>;

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">testExecute</span><span class="hljs-params">()</span>: <span class="hljs-title">void</span>
    </span>{
        <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>result</span> = (<span class="hljs-keyword">new</span> <span class="hljs-title invoke__">CommandTester</span>(<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>command</span>))-&gt;<span class="hljs-title invoke__">run</span>(
            [<span class="hljs-string">'username'</span> =&gt; <span class="hljs-string">'...'</span>],
            <span class="hljs-attr">interactiveInputs</span>: [<span class="hljs-string">'yes'</span>],
        );

        <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>this</span>-&gt;<span class="hljs-title invoke__">assertIsSuccessful</span>(<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>result</span>);
        <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>this</span>-&gt;<span class="hljs-title invoke__">assertResultEquals</span>(<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>result</span>, <span class="hljs-attr">expectedOutput</span>: <span class="hljs-string">'User "..." was created.'</span>);
    }
}</code></pre>
    </div>
</div>
</div>
<div class="section">
<h2 id="a-shortcut-to-run-commands-in-tests"><a class="headerlink" href="#a-shortcut-to-run-commands-in-tests" title="Permalink to this headline">A Shortcut to Run Commands in Tests</a></h2>
<div class="blog-post-contributor-info">
    <div class="blog-post-contributor-avatar">
                    <a target="_blank" href="https://connect.symfony.com/profile/koc">
                <img src="https://connect.symfony.com/profile/koc.picture" alt="Kostiantyn Miakshyn">
            </a>
            </div>
    <div class="blog-post-contributor-contents">
        <span>Contributed by</span>
                    <a target="_blank" class="blog-post-contributor-name" href="https://connect.symfony.com/profile/koc">Kostiantyn Miakshyn</a>
                                        <span class="blog-post-contributor-prs"> in
                                    <a target="_blank" href="https://github.com/symfony/symfony/pull/63095">#63095</a>
                                                </span>
            </div>
</div>
<p>Functional tests for commands typically involve a fair amount of boilerplate:
booting the kernel, creating the application, finding the command, and wrapping
it in a <code translate="no" class="notranslate">CommandTester</code>. Symfony 8.1 adds the <code translate="no" class="notranslate">runCommand()</code> shortcut to
<code translate="no" class="notranslate">KernelTestCase</code>, which handles all of this for you and returns a
ready-to-use tester:</p>
<div translate="no" data-loc="13" class="notranslate codeblock codeblock-length-md codeblock-php">
        <div class="codeblock-scroll">
        
        <pre class="codeblock-code"><code><span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Bundle</span>\<span class="hljs-title">FrameworkBundle</span>\<span class="hljs-title">Test</span>\<span class="hljs-title">KernelTestCase</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CreateUserCommandTest</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">KernelTestCase</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">testExecute</span><span class="hljs-params">()</span>: <span class="hljs-title">void</span>
    </span>{
        <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>commandTester</span> = <span class="hljs-keyword">static</span>::<span class="hljs-title invoke__">runCommand</span>(<span class="hljs-string">'app:create-user'</span>, [
            <span class="hljs-string">'username'</span> =&gt; <span class="hljs-string">'...'</span>,
        ]);

        <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>commandTester</span>-&gt;<span class="hljs-title invoke__">assertCommandIsSuccessful</span>();
    }
}</code></pre>
    </div>
</div>
</div>
<div class="section">
<h2 id="new-command-status-assertions"><a class="headerlink" href="#new-command-status-assertions" title="Permalink to this headline">New Command Status Assertions</a></h2>
<div class="blog-post-contributor-info">
    <div class="blog-post-contributor-avatar">
                    <a target="_blank" href="https://github.com/darovic">
                <img src="https://github.com/darovic.png" alt="Damir Mitrovic">
            </a>
            </div>
    <div class="blog-post-contributor-contents">
        <span>Contributed by</span>
                    <a target="_blank" class="blog-post-contributor-name" href="https://github.com/darovic">Damir Mitrovic</a>
                                        <span class="blog-post-contributor-prs"> in
                                    <a target="_blank" href="https://github.com/symfony/symfony/pull/63130">#63130</a>
                                                </span>
            </div>
</div>
<p>Until now, the console testing traits only provided <code translate="no" class="notranslate">assertCommandIsSuccessful()</code>.
Symfony 8.1 completes the set with two new assertions that explicitly check the
other command exit codes:</p>
<div translate="no" data-loc="5" class="notranslate codeblock codeblock-length-sm codeblock-php">
        <div class="codeblock-scroll">
        
        <pre class="codeblock-code"><code><span class="hljs-comment">// asserts the command returned Command::FAILURE</span>
<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>commandTester</span>-&gt;<span class="hljs-title invoke__">assertCommandFailed</span>();

<span class="hljs-comment">// asserts the command returned Command::INVALID</span>
<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>commandTester</span>-&gt;<span class="hljs-title invoke__">assertCommandIsInvalid</span>();</code></pre>
    </div>
</div>
</div>
                <hr style="margin-bottom: 5px" />
                <div style="font-size: 90%">
                    <a href="https://symfony.com/sponsor">Sponsor</a> the Symfony project.
                </div>
            ]]></content:encoded>
            <guid isPermaLink="false">https://symfony.com/blog/new-in-symfony-8-1-console-progress-and-testing-improvements?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed</guid>
            <dc:creator><![CDATA[ Javier Eguiluz ]]></dc:creator>
            <pubDate>Fri, 05 Jun 2026 12:50:00 +0200</pubDate>
            <comments>https://symfony.com/blog/new-in-symfony-8-1-console-progress-and-testing-improvements?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed#comments-list</comments>
        </item>
                        <item>
            <title><![CDATA[New in Symfony 8.1: ObjectMapper Improvements]]></title>
            <link>https://symfony.com/blog/new-in-symfony-8-1-objectmapper-improvements?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed</link>
            <description>The ObjectMapper component was introduced in Symfony 7.3 to eliminate the
repetitive boilerplate involved in copying data between objects, such as DTOs and
entities. Symfony 8.1 builds on that foundation with several improvements that make
mappings more expressive…</description>
            <content:encoded><![CDATA[
                                <p>The <a href="https://symfony.com/object-mapper" class="reference external">ObjectMapper component</a> was <a href="https://symfony.com/blog/new-in-symfony-7-3-objectmapper-component" class="reference external">introduced in Symfony 7.3</a> to eliminate the
repetitive boilerplate involved in copying data between objects, such as DTOs and
entities. Symfony 8.1 builds on that foundation with several improvements that make
mappings more expressive and easier to debug.</p>
<div class="section">
<h2 id="define-mappings-on-the-target-class"><a class="headerlink" href="#define-mappings-on-the-target-class" title="Permalink to this headline">Define Mappings on the Target Class</a></h2>
<div class="blog-post-contributor-info">
    <div class="blog-post-contributor-avatar">
                    <a target="_blank" href="https://connect.symfony.com/profile/orkin">
                <img src="https://connect.symfony.com/profile/orkin.picture" alt="Florent Blaison">
            </a>
            </div>
    <div class="blog-post-contributor-contents">
        <span>Contributed by</span>
                    <a target="_blank" class="blog-post-contributor-name" href="https://connect.symfony.com/profile/orkin">Florent Blaison</a>
                                        <span class="blog-post-contributor-prs"> in
                                    <a target="_blank" href="https://github.com/symfony/symfony/pull/62522">#62522</a>
                                                </span>
            </div>
</div>
<p>ObjectMapper typically reads the <code translate="no" class="notranslate">#[Map(target: ...)]</code> attribute from the
source class. In hexagonal or clean architectures, you may prefer to keep domain
objects free of mapping metadata. Symfony 8.1 now lets you declare
<code translate="no" class="notranslate">#[Map(source: ...)]</code> on the target class instead:</p>
<div translate="no" data-loc="11" class="notranslate codeblock codeblock-length-md codeblock-php">
        <div class="codeblock-scroll">
        
        <pre class="codeblock-code"><code><span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Domain</span>\<span class="hljs-title">Quote</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">ObjectMapper</span>\<span class="hljs-title">Attribute</span>\<span class="hljs-title">Map</span>;

<span class="hljs-comment">// the mapping lives on the view model, so the Quote domain</span>
<span class="hljs-comment">// object stays free of any mapping concern</span>
<span class="hljs-meta">#[Map(<span class="hljs-attr">source</span>: Quote::<span class="hljs-variable language_">class</span>)]</span>
<span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">QuoteView</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>id</span>;
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>amount</span>;
}</code></pre>
    </div>
</div>
<p>Symfony automatically discovers these attributes and builds the class map, so
no explicit target class is required when mapping:</p>
<div translate="no" data-loc="7" class="notranslate codeblock codeblock-length-sm codeblock-php">
        <div class="codeblock-scroll">
        
        <pre class="codeblock-code"><code><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">show</span><span class="hljs-params">(Quote <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>quote</span>, ObjectMapperInterface <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>mapper</span>)</span>: <span class="hljs-title">Response</span>
</span>{
    <span class="hljs-comment">// Symfony knows Quote maps to QuoteView, so no target is needed</span>
    <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>view</span> = <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>mapper</span>-&gt;<span class="hljs-title invoke__">map</span>(<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>quote</span>);

    <span class="hljs-comment">// ...</span>
}</code></pre>
    </div>
</div>
</div>
<div class="section">
<h2 id="skip-null-values-with-the-isnotnull-condition"><a class="headerlink" href="#skip-null-values-with-the-isnotnull-condition" title="Permalink to this headline">Skip Null Values With the IsNotNull Condition</a></h2>
<div class="blog-post-contributor-info">
    <div class="blog-post-contributor-avatar">
                    <a target="_blank" href="https://connect.symfony.com/profile/nayte">
                <img src="https://connect.symfony.com/profile/nayte.picture" alt="Julien Robic">
            </a>
            </div>
    <div class="blog-post-contributor-contents">
        <span>Contributed by</span>
                    <a target="_blank" class="blog-post-contributor-name" href="https://connect.symfony.com/profile/nayte">Julien Robic</a>
                                        <span class="blog-post-contributor-prs"> in
                                    <a target="_blank" href="https://github.com/symfony/symfony/pull/63595">#63595</a>
                                                </span>
            </div>
</div>
<p>When mapping a source object onto an existing target (for example, during a
partial "PATCH" update), <code translate="no" class="notranslate">null</code> values in the source overwrite the target's
existing values. Symfony 8.1 adds the built-in <code translate="no" class="notranslate">IsNotNull</code> condition so you
can skip a property whenever its source value is <code translate="no" class="notranslate">null</code>:</p>
<div translate="no" data-loc="14" class="notranslate codeblock codeblock-length-md codeblock-php">
        <div class="codeblock-scroll">
        
        <pre class="codeblock-code"><code><span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">ObjectMapper</span>\<span class="hljs-title">Attribute</span>\<span class="hljs-title">Map</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">ObjectMapper</span>\<span class="hljs-title">Condition</span>\<span class="hljs-title">IsNotNull</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserPatch</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span><span class="hljs-params">(
        <span class="hljs-meta">#[Map(<span class="hljs-attr">if</span>: <span class="hljs-keyword">new</span> <span class="hljs-title invoke__">IsNotNull</span>())]</span>
        <span class="hljs-keyword">public</span> ?<span class="hljs-keyword">string</span> <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>name</span> = <span class="hljs-keyword">null</span>,

        <span class="hljs-meta">#[Map(<span class="hljs-attr">if</span>: <span class="hljs-keyword">new</span> <span class="hljs-title invoke__">IsNotNull</span>())]</span>
        <span class="hljs-keyword">public</span> ?<span class="hljs-keyword">string</span> <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>email</span> = <span class="hljs-keyword">null</span>,
    )</span> </span>{
    }
}</code></pre>
    </div>
</div>
<p>Mapping <code translate="no" class="notranslate">new UserPatch('Alice')</code> onto an existing user updates only the <code translate="no" class="notranslate">name</code>
property. The <code translate="no" class="notranslate">email</code> property is left untouched because its value is <code translate="no" class="notranslate">null</code>.</p>
</div>
<div class="section">
<h2 id="map-collections-to-a-specific-class"><a class="headerlink" href="#map-collections-to-a-specific-class" title="Permalink to this headline">Map Collections to a Specific Class</a></h2>
<div class="blog-post-contributor-info">
    <div class="blog-post-contributor-avatar">
                    <a target="_blank" href="https://connect.symfony.com/profile/soyuka">
                <img src="https://connect.symfony.com/profile/soyuka.picture" alt="Antoine Bluchet">
            </a>
            </div>
    <div class="blog-post-contributor-contents">
        <span>Contributed by</span>
                    <a target="_blank" class="blog-post-contributor-name" href="https://connect.symfony.com/profile/soyuka">Antoine Bluchet</a>
                                        <span class="blog-post-contributor-prs"> in
                                    <a target="_blank" href="https://github.com/symfony/symfony/pull/63024">#63024</a>
                                                </span>
            </div>
</div>
<p>The <code translate="no" class="notranslate">MapCollection</code> transform maps an array of source objects into target
objects. Until now, each item class required its own <code translate="no" class="notranslate">#[Map]</code> attribute.
Symfony 8.1 adds a <code translate="no" class="notranslate">targetClass</code> option that lets you declare the destination
class directly on the collection:</p>
<div translate="no" data-loc="9" class="notranslate codeblock codeblock-length-sm codeblock-php">
        <div class="codeblock-scroll">
        
        <pre class="codeblock-code"><code><span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">ObjectMapper</span>\<span class="hljs-title">Attribute</span>\<span class="hljs-title">Map</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">ObjectMapper</span>\<span class="hljs-title">Transform</span>\<span class="hljs-title">MapCollection</span>;

<span class="hljs-meta">#[Map(<span class="hljs-attr">target</span>: Order::<span class="hljs-variable language_">class</span>)]</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OrderInput</span>
</span>{
    <span class="hljs-meta">#[Map(<span class="hljs-attr">transform</span>: <span class="hljs-keyword">new</span> <span class="hljs-title invoke__">MapCollection</span>(<span class="hljs-attr">targetClass</span>: LineItem::<span class="hljs-variable language_">class</span>))]</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">array</span> <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>items</span> = [];
}</code></pre>
    </div>
</div>
<p>Each element of <code translate="no" class="notranslate">$items</code> is mapped to a <code translate="no" class="notranslate">LineItem</code> instance. This is useful
when the same source objects are reused in different contexts and you don't want
to add mapping metadata to shared DTOs.</p>
</div>
<div class="section">
<h2 id="match-multiple-source-and-target-classes"><a class="headerlink" href="#match-multiple-source-and-target-classes" title="Permalink to this headline">Match Multiple Source and Target Classes</a></h2>
<div class="blog-post-contributor-info">
    <div class="blog-post-contributor-avatar">
                    <a target="_blank" href="https://connect.symfony.com/profile/rrajkomar">
                <img src="https://connect.symfony.com/profile/rrajkomar.picture" alt="Ryan RAJKOMAR">
            </a>
            </div>
    <div class="blog-post-contributor-contents">
        <span>Contributed by</span>
                    <a target="_blank" class="blog-post-contributor-name" href="https://connect.symfony.com/profile/rrajkomar">Ryan RAJKOMAR</a>
                                        <span class="blog-post-contributor-prs"> in
                                    <a target="_blank" href="https://github.com/symfony/symfony/pull/63383">#63383</a>
                                                </span>
            </div>
</div>
<p>The <code translate="no" class="notranslate">TargetClass</code> condition restricts a mapping to specific destination
classes. Previously, mapping a property to several target classes required one
<code translate="no" class="notranslate">#[Map]</code> attribute per class. Symfony 8.1 lets you pass an array of class
names so a single condition can match any of them:</p>
<div translate="no" data-loc="12" class="notranslate codeblock codeblock-length-md codeblock-php">
        <div class="codeblock-scroll">
        
        <pre class="codeblock-code"><code><span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">ObjectMapper</span>\<span class="hljs-title">Attribute</span>\<span class="hljs-title">Map</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">ObjectMapper</span>\<span class="hljs-title">Condition</span>\<span class="hljs-title">TargetClass</span>;

<span class="hljs-meta">#[Map(<span class="hljs-attr">target</span>: PublicProfile::<span class="hljs-variable language_">class</span>)]</span>
<span class="hljs-meta">#[Map(<span class="hljs-attr">target</span>: AdminProfile::<span class="hljs-variable language_">class</span>)]</span>
<span class="hljs-meta">#[Map(<span class="hljs-attr">target</span>: AuditProfile::<span class="hljs-variable language_">class</span>)]</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span>
</span>{
    <span class="hljs-comment">// map 'lastLoginIp' only when targeting an admin or audit profile</span>
    <span class="hljs-meta">#[Map(<span class="hljs-attr">target</span>: <span class="hljs-string">'ip'</span>, <span class="hljs-attr">if</span>: <span class="hljs-keyword">new</span> <span class="hljs-title invoke__">TargetClass</span>([AdminProfile::<span class="hljs-variable language_">class</span>, AuditProfile::<span class="hljs-variable language_">class</span>]))]</span>
    <span class="hljs-keyword">public</span> ?<span class="hljs-keyword">string</span> <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>lastLoginIp</span> = <span class="hljs-keyword">null</span>;
}</code></pre>
    </div>
</div>
<p>The same array syntax is supported by the new <code translate="no" class="notranslate">SourceClass</code> condition, which
matches when the source object is an instance of any of the specified classes.</p>
</div>
<div class="section">
<h2 id="clearer-errors-for-invalid-transform-callables"><a class="headerlink" href="#clearer-errors-for-invalid-transform-callables" title="Permalink to this headline">Clearer Errors for Invalid Transform Callables</a></h2>
<div class="blog-post-contributor-info">
    <div class="blog-post-contributor-avatar">
                    <a target="_blank" href="https://github.com/calm329">
                <img src="https://github.com/calm329.png" alt="calm329">
            </a>
                    <a target="_blank" href="https://github.com/Asenar">
                <img src="https://github.com/Asenar.png" alt="Michaël Marinetti">
            </a>
            </div>
    <div class="blog-post-contributor-contents">
        <span>Contributed by</span>
                    <a target="_blank" class="blog-post-contributor-name" href="https://github.com/calm329">calm329</a>
             and                     <a target="_blank" class="blog-post-contributor-name" href="https://github.com/Asenar">Michaël Marinetti</a>
                                        <span class="blog-post-contributor-prs"> in
                                    <a target="_blank" href="https://github.com/symfony/symfony/pull/62957">#62957</a>
                                                </span>
            </div>
</div>
<p>Previously, if a <code translate="no" class="notranslate">transform</code> or <code translate="no" class="notranslate">if</code> callable was misconfigured (for example,
a method that didn't exist or a service that didn't implement the expected interface),
the value was silently left unmapped, making the issue difficult to diagnose.
ObjectMapper now throws a <code translate="no" class="notranslate">NoSuchCallableException</code> instead:</p>
<div translate="no" data-loc="9" class="notranslate codeblock codeblock-length-sm codeblock-php">
        <div class="codeblock-scroll">
        
        <pre class="codeblock-code"><code><span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">ObjectMapper</span>\<span class="hljs-title">Attribute</span>\<span class="hljs-title">Map</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ProductInput</span>
</span>{
    <span class="hljs-comment">// 'wrongMethod' is not callable: Symfony 8.1 throws an exception</span>
    <span class="hljs-comment">// instead of silently skipping the transformation</span>
    <span class="hljs-meta">#[Map(<span class="hljs-attr">transform</span>: <span class="hljs-string">'wrongMethod'</span>)]</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>name</span> = <span class="hljs-string">''</span>;
}</code></pre>
    </div>
</div>
<p>The exception message explains the problem and reminds you that callable classes
must implement <code translate="no" class="notranslate">TransformCallableInterface</code> (or <code translate="no" class="notranslate">ConditionCallableInterface</code>
for the <code translate="no" class="notranslate">if</code> option).</p>
</div>
<div class="section">
<h2 id="merge-nested-objects-into-the-same-target"><a class="headerlink" href="#merge-nested-objects-into-the-same-target" title="Permalink to this headline">Merge Nested Objects Into the Same Target</a></h2>
<div class="blog-post-contributor-info">
    <div class="blog-post-contributor-avatar">
                    <a target="_blank" href="https://connect.symfony.com/profile/soyuka">
                <img src="https://connect.symfony.com/profile/soyuka.picture" alt="Antoine Bluchet">
            </a>
            </div>
    <div class="blog-post-contributor-contents">
        <span>Contributed by</span>
                    <a target="_blank" class="blog-post-contributor-name" href="https://connect.symfony.com/profile/soyuka">Antoine Bluchet</a>
                                        <span class="blog-post-contributor-prs"> in
                                    <a target="_blank" href="https://github.com/symfony/symfony/pull/62511">#62511</a>
                                                </span>
            </div>
</div>
<p>Source DTOs are often nested while the target object is flat. When a nested
source object maps to the same target class as its parent, ObjectMapper now
merges its properties directly into the same target instance, flattening the
structure for you:</p>
<div translate="no" data-loc="20" class="notranslate codeblock codeblock-length-md codeblock-php">
        <div class="codeblock-scroll">
        
        <pre class="codeblock-code"><code><span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">ObjectMapper</span>\<span class="hljs-title">Attribute</span>\<span class="hljs-title">Map</span>;

<span class="hljs-meta">#[Map(<span class="hljs-attr">target</span>: User::<span class="hljs-variable language_">class</span>)]</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserInput</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>name</span>;

    <span class="hljs-comment">// 'address' also maps to User, so its properties are</span>
    <span class="hljs-comment">// merged into the same User instance</span>
    <span class="hljs-keyword">public</span> AddressInput <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>address</span>;
}

<span class="hljs-meta">#[Map(<span class="hljs-attr">target</span>: User::<span class="hljs-variable language_">class</span>)]</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AddressInput</span>
</span>{
    <span class="hljs-meta">#[Map(<span class="hljs-attr">target</span>: <span class="hljs-string">'streetName'</span>)]</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>street</span>;

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>city</span>;
}</code></pre>
    </div>
</div>
<p>Mapping a <code translate="no" class="notranslate">UserInput</code> writes <code translate="no" class="notranslate">street</code> and <code translate="no" class="notranslate">city</code> from the nested
<code translate="no" class="notranslate">address</code> object into the same <code translate="no" class="notranslate">User</code> instance alongside <code translate="no" class="notranslate">name</code>, with no
manual flattening required.</p>
</div>
                <hr style="margin-bottom: 5px" />
                <div style="font-size: 90%">
                    <a href="https://symfony.com/sponsor">Sponsor</a> the Symfony project.
                </div>
            ]]></content:encoded>
            <guid isPermaLink="false">https://symfony.com/blog/new-in-symfony-8-1-objectmapper-improvements?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed</guid>
            <dc:creator><![CDATA[ Javier Eguiluz ]]></dc:creator>
            <pubDate>Thu, 04 Jun 2026 14:52:00 +0200</pubDate>
            <comments>https://symfony.com/blog/new-in-symfony-8-1-objectmapper-improvements?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed#comments-list</comments>
        </item>
                        <item>
            <title><![CDATA[SymfonyOnline June 2026: Symfony 8: The Hexagonal Track]]></title>
            <link>https://symfony.com/blog/symfonyonline-june-2026-symfony-8-the-hexagonal-track?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed</link>
            <description>
    

Upgrade your skills from home! SymfonyOnline June 2026 brings 13 high-level technical sessions online on June 11-12, 2026.

🎤 Speaker announcement!

We are super happy to have Core Team member Robin Chalas with his talk &quot;Symfony 8: The Hexagonal Track&quot;:…</description>
            <content:encoded><![CDATA[
                                <p><a class="block text-center" href="https://live.symfony.com/2026-online-june" title="Nl Blog Banner 2026 04 17T163921 336">
    <img src="https://symfony.com/uploads/assets/blog/NL-BLOG-Banner-2026-04-17T163921-336.png" alt="Nl Blog Banner 2026 04 17T163921 336">
</a>
Upgrade your skills from home! <strong><a href="https://live.symfony.com/2026-online-june">SymfonyOnline June 2026</a></strong> brings 13 high-level technical sessions online on June 11-12, 2026.</p>

<h3>🎤 Speaker announcement!</h3>

<p>We are super happy to have Core Team member <strong><a href="https://connect.symfony.com/profile/chalas_r">Robin Chalas</a></strong> with his talk <strong><a href="https://live.symfony.com/2026-online-june/schedule/symfony-8-the-hexagonal-track">"Symfony 8: The Hexagonal Track"</a></strong>:</p>

<p>"Structuring an application around its domain rather than its framework is an old idea. Making it practical has sometimes felt like swimming against the current.</p>

<p>Thanks to the way Symfony 8 leverages modern PHP, this drastically changes. Recent evolutions in both the language and the framework align naturally with hexagonal thinking and tactical DDD patterns — no workarounds, no fighting the tools.</p>

<p>Join me as I wear both my Core Team member and DDD practitioner hats to give a pragmatic look at putting your business logic first, building applications that scale with your domain's complexity and remain maintainable as they grow, with Symfony's blessing."</p>

<p>👉 Discover more talks by reading the <strong><a href="https://live.symfony.com/2026-online-june/schedule">full schedule</a></strong></p>

<p>✨ Key Feature: All talks are pre-recorded to ensure the highest technical quality, but speakers will be joining us live to answer your questions in real-time during the dedicated Q&amp;A sessions!</p>

<hr />

<h3>🛠️ Pre-conference Workshops (June 9-10)</h3>

<p>Don't forget that the conference is preceded by two days of hands-on technical workshops. These small-group sessions are the perfect opportunity to master specific Symfony features under the guidance of certified trainers: <strong><a href="https://live.symfony.com/2026-online-june/workshop">Discover the topics!</a></strong></p>

<p>Note: Workshop spots are limited and no replays are available for these sessions to ensure an interactive learning experience.</p>

<h3>🎟️ Register now!</h3>

<p>Join the global PHP &amp; Symfony community from the comfort of your home or office by clicking <strong><a href="https://live.symfony.com/2026-online-june/registration/">here</a></strong>.</p>

<ul>
<li>▶️ <strong>Instant Replays</strong>: Missed a session? All talks are available for replay as soon as they start.</li>
<li>🆒 <strong>Accessibility</strong>: English subtitles are provided for all presentations.</li>
</ul>

<p>We look forward to seeing you online to explore the future of PHP together!</p>

<hr />

<h3>Join us online!</h3>

<p>💡Follow the "conference" blog posts to not miss anything!</p>

<p>Want the latest Symfony updates? Follow us and tune in from wherever you are 🌎</p>

<p><a class="block text-center" href="https://linktr.ee/symfony">
   <img src="https://symfony.com/uploads/assets/blog/Banner-BLOG.png" alt="Banner Blog">
</a></p>

                <hr style="margin-bottom: 5px" />
                <div style="font-size: 90%">
                    <a href="https://symfony.com/sponsor">Sponsor</a> the Symfony project.
                </div>
            ]]></content:encoded>
            <guid isPermaLink="false">https://symfony.com/blog/symfonyonline-june-2026-symfony-8-the-hexagonal-track?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed</guid>
            <dc:creator><![CDATA[ Eloïse Charrier ]]></dc:creator>
            <pubDate>Thu, 04 Jun 2026 10:30:00 +0200</pubDate>
            <comments>https://symfony.com/blog/symfonyonline-june-2026-symfony-8-the-hexagonal-track?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed#comments-list</comments>
        </item>
            </channel>
</rss>
