<?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>Wed, 10 Jun 2026 10:51:50 +0200</pubDate>
        <lastBuildDate>Wed, 10 Jun 2026 08:35:00 +0200</lastBuildDate>
        <language>en</language>
                        <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>
                        <item>
            <title><![CDATA[New in Symfony 8.1: HttpClient Improvements]]></title>
            <link>https://symfony.com/blog/new-in-symfony-8-1-httpclient-improvements?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed</link>
            <description>Symfony 8.1 ships a batch of improvements for the HttpClient component that
touch performance, interoperability, security and testing.

Persistent cURL Connections

    
                    
                
            
            
    
        Contributed…</description>
            <content:encoded><![CDATA[
                                <p>Symfony 8.1 ships a batch of improvements for the <a href="https://symfony.com/http-client" class="reference external">HttpClient component</a> that
touch performance, interoperability, security and testing.</p>
<div class="section">
<h2 id="persistent-curl-connections"><a class="headerlink" href="#persistent-curl-connections" title="Permalink to this headline">Persistent cURL Connections</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/62751">#62751</a>
                                                </span>
            </div>
</div>
<p>When you make many requests in a row, re-establishing connections for every
request adds DNS, TCP and TLS overhead. PHP 8.5 introduces <strong>persistent cURL
handles</strong> and Symfony 8.1 exposes them through the new
<code translate="no" class="notranslate">extra.use_persistent_connections</code> option of <code translate="no" class="notranslate">CurlHttpClient</code>:</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">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">HttpClient</span>\<span class="hljs-title">CurlHttpClient</span>;

<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>client</span> = <span class="hljs-keyword">new</span> <span class="hljs-title invoke__">CurlHttpClient</span>([
    <span class="hljs-string">'extra'</span> =&gt; [
        <span class="hljs-string">'use_persistent_connections'</span> =&gt; <span class="hljs-keyword">true</span>,
    ],
]);</code></pre>
    </div>
</div>
<p>When enabled (and running on PHP 8.5 or later), the DNS cache, SSL sessions and
connection data are reused across requests, reducing the connection overhead.
This is most useful for CLI commands and workers that make many sequential requests.</p>
</div>
<div class="section">
<h2 id="using-symfony-httpclient-as-a-guzzle-handler"><a class="headerlink" href="#using-symfony-httpclient-as-a-guzzle-handler" title="Permalink to this headline">Using Symfony HttpClient as a Guzzle Handler</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/63433">#63433</a>
                                                </span>
            </div>
</div>
<p>Some SDKs are built on top of <a href="https://docs.guzzlephp.org/" class="reference external" rel="external noopener noreferrer" target="_blank">Guzzle</a> and don't allow replacing the underlying
HTTP client. Symfony 8.1 adds a <code translate="no" class="notranslate">GuzzleHttpHandler</code> that lets those SDKs use
Symfony HttpClient as their transport layer, benefiting from features such as
retries, HTTP/2 support and scoped clients:</p>
<div translate="no" data-loc="6" 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">GuzzleHttp</span>\<span class="hljs-title">Client</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">HttpClient</span>\<span class="hljs-title">GuzzleHttpHandler</span>;

<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>guzzle</span> = <span class="hljs-keyword">new</span> <span class="hljs-title invoke__">Client</span>([<span class="hljs-string">'handler'</span> =&gt; <span class="hljs-keyword">new</span> <span class="hljs-title invoke__">GuzzleHttpHandler</span>()]);

<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>response</span> = <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>guzzle</span>-&gt;<span class="hljs-title invoke__">request</span>(<span class="hljs-string">'GET'</span>, <span class="hljs-string">'https://symfony.com/versions.json'</span>);</code></pre>
    </div>
</div>
<p>By default, the handler creates a new HttpClient instance. You can also pass any
<code translate="no" class="notranslate">HttpClientInterface</code> implementation to reuse the same configuration across
your application and third-party SDKs.</p>
</div>
<div class="section">
<h2 id="allowing-specific-hosts-in-noprivatenetworkhttpclient"><a class="headerlink" href="#allowing-specific-hosts-in-noprivatenetworkhttpclient" title="Permalink to this headline">Allowing Specific Hosts in NoPrivateNetworkHttpClient</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/64160">#64160</a>
                                                </span>
            </div>
</div>
<p><code translate="no" class="notranslate">NoPrivateNetworkHttpClient</code> helps protect applications from <a href="https://en.wikipedia.org/wiki/Server-side_request_forgery" class="reference external" rel="external noopener noreferrer" target="_blank">SSRF attacks</a>
by blocking requests to private networks (<code translate="no" class="notranslate">10.0.0.0/8</code>, <code translate="no" class="notranslate">192.168.0.0/16</code>,
etc.). Symfony 8.1 adds a third <code translate="no" class="notranslate">$allowList</code> argument that lets you exempt
specific hosts (e.g. a local proxy) while continuing to block all other
private-network addresses:</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">HttpClient</span>\<span class="hljs-title">HttpClient</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">HttpClient</span>\<span class="hljs-title">NoPrivateNetworkHttpClient</span>;

<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>client</span> = <span class="hljs-keyword">new</span> <span class="hljs-title invoke__">NoPrivateNetworkHttpClient</span>(
    HttpClient::<span class="hljs-title invoke__">create</span>(),
    <span class="hljs-keyword">null</span>,           <span class="hljs-comment">// null = block the default private subnets</span>
    <span class="hljs-string">'10.0.0.42'</span>,    <span class="hljs-comment">// allow this host (also accepts an array of IPs/CIDR subnets)</span>
);</code></pre>
    </div>
</div>
</div>
<div class="section">
<h2 id="safer-default-for-cached-responses"><a class="headerlink" href="#safer-default-for-cached-responses" title="Permalink to this headline">Safer Default for Cached Responses</a></h2>
<div class="blog-post-contributor-info">
    <div class="blog-post-contributor-avatar">
                    <a target="_blank" href="https://connect.symfony.com/profile/lctrs">
                <img src="https://connect.symfony.com/profile/lctrs.picture" alt="Jérôme Parmentier">
            </a>
                    <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/lctrs">Jérôme Parmentier</a>
             and                     <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/63441">#63441</a>
                                                </span>
            </div>
</div>
<p><code translate="no" class="notranslate">CachingHttpClient</code> stores responses according to the cache-control directives
returned by the server. When no cache directives are provided, responses could
previously remain cached indefinitely.</p>
<p>In Symfony 8.1, the maximum time-to-live now defaults to <code translate="no" class="notranslate">86400</code> seconds (24
hours) instead of being unlimited. Server-provided TTLs are still respected,
but they are capped to this value to prevent stale cache entries from living
indefinitely. You can change the cap with the <code translate="no" class="notranslate">max_ttl</code> option:</p>
<div translate="no" data-loc="8" class="notranslate codeblock codeblock-length-sm codeblock-yaml">
        <div class="codeblock-scroll">
        
        <pre class="codeblock-code"><code><span class="hljs-comment"># config/packages/framework.yaml</span>
<span class="hljs-attr">framework:</span>
    <span class="hljs-attr">http_client:</span>
        <span class="hljs-attr">scoped_clients:</span>
            <span class="hljs-attr">example.client:</span>
                <span class="hljs-attr">base_uri:</span> <span class="hljs-string">'https://example.com'</span>
                <span class="hljs-attr">caching:</span>
                    <span class="hljs-attr">max_ttl:</span> <span class="hljs-number">3600</span></code></pre>
    </div>
</div>
<p>Passing <code translate="no" class="notranslate">null</code> to disable the cap is now deprecated; you can only set positive
integers as the value of this option.</p>
</div>
<div class="section">
<h2 id="failing-fast-with-max-connect-duration"><a class="headerlink" href="#failing-fast-with-max-connect-duration" title="Permalink to this headline">Failing Fast with <code translate="no" class="notranslate">max_connect_duration</code></a></h2>
<div class="blog-post-contributor-info">
    <div class="blog-post-contributor-avatar">
                    <a target="_blank" href="https://connect.symfony.com/profile/alexandre-daubois">
                <img src="https://connect.symfony.com/profile/alexandre-daubois.picture" alt="Alexandre Daubois">
            </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/alexandre-daubois">Alexandre Daubois</a>
                                        <span class="blog-post-contributor-prs"> in
                                    <a target="_blank" href="https://github.com/symfony/symfony/pull/62854">#62854</a>
                                                </span>
            </div>
</div>
<p>The <code translate="no" class="notranslate">timeout</code> option controls how long the client waits for activity on an
idle connection and <code translate="no" class="notranslate">max_duration</code> caps the total request time. Neither
provides a strict deadline for establishing the connection.</p>
<p>Symfony 8.1 adds the <code translate="no" class="notranslate">max_connect_duration</code> option, which limits the time
spent on DNS resolution, the TCP connection and the TLS handshake:</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-variable"><span class="hljs-variable-other-marker">$</span>response</span> = <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>client</span>-&gt;<span class="hljs-title invoke__">request</span>(<span class="hljs-string">'GET'</span>, <span class="hljs-string">'https://...'</span>, [
    <span class="hljs-comment">// fail fast if the connection cannot be established within 0.5 seconds</span>
    <span class="hljs-string">'max_connect_duration'</span> =&gt; <span class="hljs-number">0.5</span>,
    <span class="hljs-string">'timeout'</span> =&gt; <span class="hljs-number">10</span>,
]);</code></pre>
    </div>
</div>
<p>A value of <code translate="no" class="notranslate">0</code> or less means there is no limit on connection time, as long as
the <code translate="no" class="notranslate">timeout</code> option is respected.</p>
</div>
<div class="section">
<h2 id="per-client-mocking-in-tests"><a class="headerlink" href="#per-client-mocking-in-tests" title="Permalink to this headline">Per-Client Mocking 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/tarjei">
                <img src="https://connect.symfony.com/profile/tarjei.picture" alt="Tarjei Huse">
            </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/tarjei">Tarjei Huse</a>
                                        <span class="blog-post-contributor-prs"> in
                                    <a target="_blank" href="https://github.com/symfony/symfony/pull/52265">#52265</a>
                                                </span>
            </div>
</div>
<p>When testing, you can set <code translate="no" class="notranslate">mock_response_factory</code> to replace HttpClient with a
mock that returns canned responses. In Symfony 8.1, this option can now be
configured per scoped client, making it easier to mock specific APIs while
leaving others untouched.</p>
<p>Set it to <code translate="no" class="notranslate">true</code> for a <code translate="no" class="notranslate">MockHttpClient</code> that returns empty <code translate="no" class="notranslate">200</code>
responses, to <code translate="no" class="notranslate">false</code> to disable mocking for a specific client, or to a
service ID for full control over the returned responses:</p>
<div translate="no" data-loc="11" class="notranslate codeblock codeblock-length-md codeblock-yaml">
        <div class="codeblock-scroll">
        
        <pre class="codeblock-code"><code><span class="hljs-comment"># config/packages/test/framework.yaml</span>
<span class="hljs-attr">framework:</span>
    <span class="hljs-attr">http_client:</span>
        <span class="hljs-attr">mock_response_factory:</span> <span class="hljs-literal">true</span>
        <span class="hljs-attr">scoped_clients:</span>
            <span class="hljs-attr">my_api.client:</span>
                <span class="hljs-attr">base_uri:</span> <span class="hljs-string">'https://my-api.com'</span>
                <span class="hljs-attr">mock_response_factory:</span> <span class="hljs-string">App\Tests\MyApiMockClientCallback</span>
            <span class="hljs-attr">not_mocked.client:</span>
                <span class="hljs-attr">base_uri:</span> <span class="hljs-string">'https://example.com'</span>
                <span class="hljs-attr">mock_response_factory:</span> <span class="hljs-literal">false</span></code></pre>
    </div>
</div>
</div>
<div class="section">
<h2 id="custom-dns-resolution"><a class="headerlink" href="#custom-dns-resolution" title="Permalink to this headline">Custom DNS Resolution</a></h2>
<div class="blog-post-contributor-info">
    <div class="blog-post-contributor-avatar">
                    <a target="_blank" href="https://github.com/peter17">
                <img src="https://github.com/peter17.png" alt="Peter Potrowl">
            </a>
            </div>
    <div class="blog-post-contributor-contents">
        <span>Contributed by</span>
                    <a target="_blank" class="blog-post-contributor-name" href="https://github.com/peter17">Peter Potrowl</a>
                                        <span class="blog-post-contributor-prs"> in
                                    <a target="_blank" href="https://github.com/symfony/symfony/pull/63770">#63770</a>
                                                </span>
            </div>
</div>
<p>Symfony 8.1 adds a <code translate="no" class="notranslate">DnsResolvingHttpClient</code> decorator that lets you provide
custom DNS resolution logic (e.g. to route requests to a specific server while
debugging). The resolver receives the hostname and returns the IP address to
use, or <code translate="no" class="notranslate">null</code> to fall back to the default transport resolution:</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">HttpClient</span>\<span class="hljs-title">DnsResolvingHttpClient</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">HttpClient</span>\<span class="hljs-title">HttpClient</span>;

<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>resolver</span> = <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-params">(<span class="hljs-keyword">string</span> <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>hostname</span>)</span>: ?<span class="hljs-title">string</span> </span>{
    <span class="hljs-comment">// implement your own resolution logic (service discovery, caching, etc.)</span>
    <span class="hljs-keyword">return</span> <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>ip</span>; <span class="hljs-comment">// or null to use the default DNS resolution</span>
};

<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>client</span> = <span class="hljs-keyword">new</span> <span class="hljs-title invoke__">DnsResolvingHttpClient</span>(HttpClient::<span class="hljs-title invoke__">create</span>(), <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>resolver</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-httpclient-improvements?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed</guid>
            <dc:creator><![CDATA[ Javier Eguiluz ]]></dc:creator>
            <pubDate>Wed, 03 Jun 2026 17:32:00 +0200</pubDate>
            <comments>https://symfony.com/blog/new-in-symfony-8-1-httpclient-improvements?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed#comments-list</comments>
        </item>
                        <item>
            <title><![CDATA[SymfonyOnline June 2026: Building TUIs in PHP: The Symfony Terminal Component]]></title>
            <link>https://symfony.com/blog/symfonyonline-june-2026-building-tuis-in-php-the-symfony-terminal-component?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed</link>
            <description>
    

SymfonyOnline June 2026 is taking place online next week, from June 11 to 12, 2026! Prepare for two days of pure Symfony and PHP innovation from the comfort of your screen.

🎤 Speaker announcement!

We are honored to feature Fabien Potencier with his…</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>
<strong><a href="https://live.symfony.com/2026-online-june">SymfonyOnline June 2026</a></strong> is taking place online next week, from June 11 to 12, 2026! Prepare for two days of pure Symfony and PHP innovation from the comfort of your screen.</p>

<h3>🎤 Speaker announcement!</h3>

<p>We are honored to feature <strong><a href="https://connect.symfony.com/profile/fabpot">Fabien Potencier</a></strong> with his talk <strong><a href="https://live.symfony.com/2026-online-june/schedule/building-tuis-in-php-the-symfony-terminal-component">"Building TUIs in PHP: The Symfony Terminal Component"</a></strong>:</p>

<p>"With coding agents moving from the IDE to the terminal, TUIs are having a renaissance. This session introduces the new Symfony Terminal Component, a TUI toolkit built from the ground up in PHP. We'll explore its architecture, the primitives it exposes, and how it makes building rich, interactive terminal interfaces possible in Symfony applications."</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-building-tuis-in-php-the-symfony-terminal-component?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed</guid>
            <dc:creator><![CDATA[ Eloïse Charrier ]]></dc:creator>
            <pubDate>Wed, 03 Jun 2026 16:30:00 +0200</pubDate>
            <comments>https://symfony.com/blog/symfonyonline-june-2026-building-tuis-in-php-the-symfony-terminal-component?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed#comments-list</comments>
        </item>
            </channel>
</rss>
