<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Migration on Guillaume Delré</title><link>https://guillaumedelre.github.io/tags/migration/</link><description>Recent content in Migration on Guillaume Delré</description><generator>Hugo</generator><language>en</language><lastBuildDate>Fri, 12 Jan 2018 00:00:00 +0000</lastBuildDate><atom:link href="https://guillaumedelre.github.io/tags/migration/index.xml" rel="self" type="application/rss+xml"/><item><title>Symfony 3.4 LTS: the bridge you actually want to cross</title><link>https://guillaumedelre.github.io/2018/01/12/symfony-3.4-lts-the-bridge-you-actually-want-to-cross/</link><pubDate>Fri, 12 Jan 2018 00:00:00 +0000</pubDate><guid>https://guillaumedelre.github.io/2018/01/12/symfony-3.4-lts-the-bridge-you-actually-want-to-cross/</guid><description>Part 2 of 11 in &amp;quot;Symfony Releases&amp;quot;: Symfony 3.4 LTS is the migration bridge: same features as 3.3 plus every deprecation warning that 4.0 will enforce.</description><category>symfony-releases</category><content:encoded><![CDATA[<p>Symfony 3.4 and 4.0 were released the same day: November 30, 2017. That&rsquo;s not a coincidence, it&rsquo;s the strategy.</p>
<p>3.4 is not a feature release. It ships with exactly the same features as 3.3, plus every deprecation warning that 4.0 will enforce. Its whole purpose is to be the migration tool: upgrade from 3.3 to 3.4, fix what&rsquo;s in your logs, then step to 4.0 cleanly.</p>
<h2 id="why-lts-releases-matter-in-symfonys-model">Why LTS releases matter in Symfony&rsquo;s model</h2>
<p>Symfony releases a new minor version every six months. That pace would be brutal for production apps to follow, so the project designates every fourth minor as an LTS: three years of bug fixes, four of security fixes. Which means teams can target 3.4 and mostly stop thinking about upgrades for a while.</p>
<p>3.4 is the last LTS of the 3.x line. If you&rsquo;re still on 2.x or early 3.x, this is your landing zone.</p>
<h2 id="the-deprecation-layer">The deprecation layer</h2>
<p>Every feature that 4.0 removes is deprecated in 3.4. Run your app on 3.4 with deprecation notices enabled and your logs become a to-do list. The common ones:</p>
<ul>
<li>Services without explicit visibility (public/private) generate warnings — 4.0 makes all services private by default</li>
<li><code>ControllerTrait</code> is deprecated in favor of <code>AbstractController</code></li>
<li>The old security authenticator interfaces are marked for removal</li>
<li>YAML-only service configuration without autowiring annotations triggers warnings</li>
</ul>
<p>The intended workflow: upgrade to 3.4, run the test suite with deprecation notices as errors (<code>SYMFONY_DEPRECATIONS_HELPER=max[self]=0</code> in PHPUnit), fix everything that fails. After that, the upgrade to 4.0 is basically mechanical.</p>
<h2 id="the-support-window">The support window</h2>
<p>3.4 LTS receives bug fixes until November 2020 and security fixes until November 2021. That&rsquo;s a comfortable runway for apps that can&rsquo;t follow every release. The cost: staying on the 3.x architecture, with no Flex, no micro-framework structure, no zero-config autowiring by default.</p>
<p>The bridge is there. Whether and when you cross it is a business decision, not a technical one.</p>
<h2 id="services-go-private">Services go private</h2>
<p>3.4 flipped the default visibility of services from public to private. Before this, <code>$container-&gt;get('app.my_service')</code> was perfectly normal code. After this, it&rsquo;s an anti-pattern that generates a deprecation warning in 3.4 and breaks entirely in 4.0.</p>
<p>The reasoning is simple: fetching services directly from the container hides dependencies and defeats static analysis. If you inject through the constructor, the container can optimize the graph, tree-shake unused services, and catch mistakes at compile time. If you pull them at runtime, it can&rsquo;t.</p>
<p>For apps already using autowiring, the migration is usually small. The sticky point is controllers that extend <code>Controller</code> and call <code>$this-&gt;get('something')</code>. The fix is switching to <code>AbstractController</code>, which provides the same shortcuts but through lazy service locators instead of raw container access.</p>
<p>For services that genuinely need to be public (accessed from legacy code or functional tests), mark them explicitly:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">services</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">App\Service\LegacyAdapter</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">public</span>: <span style="color:#66d9ef">true</span>
</span></span></code></pre></div><h2 id="binding-scalar-arguments-once">Binding scalar arguments once</h2>
<p>A classic friction point with autowiring: scalar constructor arguments. If ten services all need <code>$projectDir</code>, you had to configure each one individually. The <code>bind</code> key under <code>_defaults</code> fixes that:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">services</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">_defaults</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">autowire</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">autoconfigure</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">bind</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">$projectDir</span>: <span style="color:#e6db74">&#39;%kernel.project_dir%&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">$mailerDsn</span>: <span style="color:#e6db74">&#39;%env(MAILER_DSN)%&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">Psr\Log\LoggerInterface $auditLogger</span>: <span style="color:#e6db74">&#39;@monolog.logger.audit&#39;</span>
</span></span></code></pre></div><p>Any service with a constructor parameter named <code>$projectDir</code> gets the bound value automatically. You can also bind by type-hint, which handles the common case where multiple logger channels exist and you need a specific one. Bindings in <code>_defaults</code> apply to all services in the file; you can override per-service if needed.</p>
<h2 id="injecting-tagged-services-without-a-compiler-pass">Injecting tagged services without a compiler pass</h2>
<p>Before 3.4, collecting all services with a given tag meant writing a compiler pass. Now there&rsquo;s a YAML shorthand:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">services</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">App\Chain\TransformerChain</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">arguments</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">$transformers</span>: !<span style="color:#ae81ff">tagged app.transformer</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">TransformerChain</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">__construct</span>(<span style="color:#66d9ef">private</span> <span style="color:#a6e22e">iterable</span> $transformers) {}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The <code>!tagged</code> notation creates an <code>IteratorArgument</code>: services are lazily instantiated as you iterate, so unused transformers never get built. For ordering, add a <code>priority</code> attribute to the tag definition on each service.</p>
<h2 id="a-logger-that-ships-with-the-framework">A logger that ships with the framework</h2>
<p>No Monolog? No problem. Symfony 3.4 includes a PSR-3 logger that writes to <code>php://stderr</code> by default. Autowire it with <code>Psr\Log\LoggerInterface</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">Psr\Log\LoggerInterface</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">MyService</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">__construct</span>(<span style="color:#66d9ef">private</span> <span style="color:#a6e22e">LoggerInterface</span> $logger) {}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">doSomething</span>()<span style="color:#f92672">:</span> <span style="color:#a6e22e">void</span>
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">logger</span><span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">warning</span>(<span style="color:#e6db74">&#39;Something questionable happened&#39;</span>, [<span style="color:#e6db74">&#39;context&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#39;here&#39;</span>]);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The default minimum level is <code>warning</code>. The target is container and Kubernetes workloads where stderr is the natural log sink. It&rsquo;s deliberately minimal: no handlers, no processors, no channels. When you need those, install Monolog.</p>
<h2 id="guard-authenticators-got-a-supports-method">Guard authenticators got a supports() method</h2>
<p>The Guard component&rsquo;s <code>getCredentials()</code> method was pulling double duty: deciding whether the authenticator should handle the request, and extracting the credentials. Returning <code>null</code> was the signal to skip. That made the contract messy.</p>
<p>3.4 added <code>supports()</code> to separate those concerns:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">ApiTokenAuthenticator</span> <span style="color:#66d9ef">extends</span> <span style="color:#a6e22e">AbstractGuardAuthenticator</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">supports</span>(<span style="color:#a6e22e">Request</span> $request)<span style="color:#f92672">:</span> <span style="color:#a6e22e">bool</span>
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> $request<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">headers</span><span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">has</span>(<span style="color:#e6db74">&#39;X-API-TOKEN&#39;</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">getCredentials</span>(<span style="color:#a6e22e">Request</span> $request)<span style="color:#f92672">:</span> <span style="color:#66d9ef">array</span>
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Only called when supports() returns true.
</span></span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Must always return credentials now.
</span></span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> [<span style="color:#e6db74">&#39;token&#39;</span> <span style="color:#f92672">=&gt;</span> $request<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">headers</span><span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">get</span>(<span style="color:#e6db74">&#39;X-API-TOKEN&#39;</span>)];
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The old <code>GuardAuthenticatorInterface</code> is deprecated. The practical benefit: base classes can implement shared <code>getUser()</code> and <code>checkCredentials()</code> logic, while subclasses only override <code>supports()</code> and <code>getCredentials()</code>. One responsibility each.</p>
<h2 id="two-new-debug-commands">Two new debug commands</h2>
<p><code>debug:autowiring</code> replaces the old <code>debug:container --types</code> for discovering which type-hints work with autowiring:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ bin/console debug:autowiring log
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Autowirable Services
</span></span><span style="display:flex;"><span><span style="color:#f92672">====================</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  Psr<span style="color:#ae81ff">\L</span>og<span style="color:#ae81ff">\L</span>oggerInterface
</span></span><span style="display:flex;"><span>      alias to monolog.logger
</span></span><span style="display:flex;"><span>  Psr<span style="color:#ae81ff">\L</span>og<span style="color:#ae81ff">\L</span>oggerInterface $auditLogger
</span></span><span style="display:flex;"><span>      alias to monolog.logger.audit
</span></span></code></pre></div><p>Pass a keyword to filter. No more guessing whether it&rsquo;s <code>LoggerInterface</code> or <code>Logger</code>.</p>
<p><code>debug:form</code> gives you the same introspection capability for form types:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ bin/console debug:form App<span style="color:#ae81ff">\F</span>orm<span style="color:#ae81ff">\O</span>rderType label_attr
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Option: label_attr
</span></span><span style="display:flex;"><span>  Required: false
</span></span><span style="display:flex;"><span>  Default: <span style="color:#f92672">[]</span>
</span></span><span style="display:flex;"><span>  Allowed types: array
</span></span></code></pre></div><p>Without arguments it lists all registered form types, extensions, and guessers. With a type name and option name it shows every constraint on that option. Before this, you either read the source or trial-and-errored your way through.</p>
<h2 id="sessions-got-stricter-by-default">Sessions got stricter by default</h2>
<p>3.4 implements PHP 7.0&rsquo;s <code>SessionUpdateTimestampHandlerInterface</code>, which brings two things: lazy session writes (only written when data actually changed) and strict session ID validation (IDs that don&rsquo;t exist in the store are rejected rather than silently created, which blocks a class of session fixation attacks).</p>
<p>The old <code>WriteCheckSessionHandler</code>, <code>NativeSessionHandler</code>, and <code>NativeProxy</code> classes are deprecated. The <code>MemcacheSessionHandler</code> (note: not Memcached) is gone too, since the underlying PECL extension stopped receiving PHP 7 updates.</p>
<h2 id="twig-form-themes-can-now-be-scoped">Twig form themes can now be scoped</h2>
<p>Global form themes apply to every form in the app. If one form needs a completely different look, you had no clean way to opt out. The <code>only</code> keyword handles that:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-twig" data-lang="twig"><span style="display:flex;"><span><span style="color:#75715e">{%</span> <span style="color:#66d9ef">raw</span> <span style="color:#75715e">%}</span>{% form_theme orderForm with [&#39;form/order_layout.html.twig&#39;] only %}<span style="color:#75715e">{%</span> <span style="color:#66d9ef">endraw</span> <span style="color:#75715e">%}</span>
</span></span></code></pre></div><p>The <code>only</code> keyword disables all global themes for that form, including the base <code>form_div_layout.html.twig</code>. Your custom theme then needs to either provide all the blocks it uses, or explicitly pull them in with <code>{% raw %}{% use 'form_div_layout.html.twig' %}{% endraw %}</code>.</p>
<h2 id="overriding-bundle-templates-without-infinite-loops">Overriding bundle templates without infinite loops</h2>
<p>Overriding a bundle template that you also need to extend used to cause a circular reference error. Override <code>@TwigBundle/Exception/error404.html.twig</code> and also try to inherit from it? The old namespace resolution would follow your override and loop forever.</p>
<p>3.4 introduced the <code>@!</code> prefix to explicitly reference the original bundle template, bypassing any overrides:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-twig" data-lang="twig"><span style="display:flex;"><span><span style="color:#75715e">{%</span> <span style="color:#66d9ef">raw</span> <span style="color:#75715e">%}</span>{# templates/bundles/TwigBundle/Exception/error404.html.twig #}
</span></span><span style="display:flex;"><span>{% extends &#39;@!Twig/Exception/error404.html.twig&#39; %}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>{% block title %}Page not found{% endblock %}<span style="color:#75715e">{%</span> <span style="color:#66d9ef">endraw</span> <span style="color:#75715e">%}</span>
</span></span></code></pre></div><p><code>@TwigBundle</code> resolves to your override if one exists. <code>@!TwigBundle</code> always resolves to the original. Override-and-extend, without the gymnastics.</p>
]]></content:encoded></item></channel></rss>