<?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>Injection-De-Dependances on Guillaume Delré</title><link>https://guillaumedelre.github.io/fr/tags/injection-de-dependances/</link><description>Recent content in Injection-De-Dependances on Guillaume Delré</description><generator>Hugo</generator><language>fr-FR</language><lastBuildDate>Thu, 13 Jul 2017 00:00:00 +0000</lastBuildDate><atom:link href="https://guillaumedelre.github.io/fr/tags/injection-de-dependances/index.xml" rel="self" type="application/rss+xml"/><item><title>Symfony 3.3 : quand les services ont arrêté d'être un cauchemar de configuration</title><link>https://guillaumedelre.github.io/fr/2017/07/13/symfony-3.3-quand-les-services-ont-arr%C3%AAt%C3%A9-d%C3%AAtre-un-cauchemar-de-configuration/</link><pubDate>Thu, 13 Jul 2017 00:00:00 +0000</pubDate><guid>https://guillaumedelre.github.io/fr/2017/07/13/symfony-3.3-quand-les-services-ont-arr%C3%AAt%C3%A9-d%C3%AAtre-un-cauchemar-de-configuration/</guid><description>Part 1 of 11 in &amp;quot;Sorties Symfony&amp;quot;: Symfony 3.3 a rendu l&amp;#39;autowiring par défaut et transformé la configuration des services — des montagnes de YAML en presque rien.</description><category>symfony-releases</category><content:encoded><![CDATA[<p>Symfony 3.3 est sorti le 29 mai. C&rsquo;est la version qui a changé ma façon de penser la configuration des services. Avec le recul, c&rsquo;était une prévisualisation de ce que 4.0 allait adopter comme nouveau standard.</p>
<h2 id="le-problème-de-lautowiring">Le problème de l&rsquo;autowiring</h2>
<p>Avant 3.3, le DI de Symfony était puissant mais verbeux. Chaque service devait être déclaré explicitement dans <code>services.yml</code> avec ses arguments listés. L&rsquo;autowiring existait depuis 3.1, mais il était opt-in par service et avait assez de cas limites pour vous mordre. Les équipes écrivaient soit des montagnes de YAML, soit s&rsquo;appuyaient sur des bundles tiers pour réduire le bruit.</p>
<p>3.3 a réécrit les defaults. Avec <code>autoconfigure: true</code> et <code>autowire: true</code> définis une seule fois dans la section defaults, chaque classe dans <code>src/</code> devient automatiquement un service, et ses dépendances de constructeur sont résolues par type. Ce qui prenait vingt lignes de YAML ne prend maintenant plus rien :</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">App\</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">resource</span>: <span style="color:#e6db74">&#39;../src/&#39;</span>
</span></span></code></pre></div><p>Ce bloc unique est toute la configuration de services pour la plupart des applications. Le framework découvre les services, injecte les dépendances, et applique les tags (command, event subscriber, voter&hellip;) en fonction des interfaces que chaque classe implémente.</p>
<h2 id="les-conditionnels-instanceof">Les conditionnels instanceof</h2>
<p>Le mot-clé <code>instanceof</code> dans la configuration des services gère le tagging qui nécessitait auparavant une déclaration explicite :</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">_instanceof</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">Symfony\Component\EventDispatcher\EventSubscriberInterface</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">tags</span>: [<span style="color:#e6db74">&#39;kernel.event_subscriber&#39;</span>]
</span></span></code></pre></div><p>Tout service implémentant <code>EventSubscriberInterface</code> reçoit le tag automatiquement. Même chose pour <code>Command</code>, <code>Voter</code>, <code>MessageHandlerInterface</code>. Le boilerplate s&rsquo;évapore.</p>
<h2 id="le-composant-dotenv">Le composant Dotenv</h2>
<p>Avant 3.3, Symfony n&rsquo;avait aucun moyen natif de charger des fichiers <code>.env</code>. La réponse standard était un package tiers. Le nouveau composant <code>Dotenv</code> lit <code>.env</code> et peuple <code>$_ENV</code> et <code>$_SERVER</code>, faisant de la configuration basée sur l&rsquo;environnement un citoyen de première classe enfin.</p>
<h2 id="découverte-des-services-depuis-le-filesystem">Découverte des services depuis le filesystem</h2>
<p>L&rsquo;option <code>resource</code> rassemble tout. Au lieu d&rsquo;enregistrer chaque classe individuellement, on pointe le conteneur vers un répertoire et il scanne les classes PSR-4 :</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\</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">resource</span>: <span style="color:#e6db74">&#39;../src/&#39;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">exclude</span>: <span style="color:#e6db74">&#39;../src/{Entity,Migrations}&#39;</span>
</span></span></code></pre></div><p>Chaque classe trouvée devient un service avec son FQCN comme service ID. L&rsquo;option <code>exclude</code> gère les cas comme les entités Doctrine qu&rsquo;on ne veut pas que le conteneur touche. Et non, ce n&rsquo;est pas de la magie : c&rsquo;est un scan filesystem à la compilation, donc le coût est payé une fois pendant le cache warmup, pas par requête.</p>
<h2 id="quand-on-a-besoin-dun-sous-ensemble-du-conteneur">Quand on a besoin d&rsquo;un sous-ensemble du conteneur</h2>
<p>Les service locators résolvent une tension spécifique : certains services ont légitimement besoin d&rsquo;accéder de manière paresseuse à un ensemble variable d&rsquo;autres services, mais injecter le conteneur entier est un anti-pattern — ça masque les dépendances et déjoue l&rsquo;analyse statique. La solution est un locator qui déclare explicitement ce qu&rsquo;il contient.</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\Handler\HandlerLocator</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">class</span>: <span style="color:#ae81ff">Symfony\Component\DependencyInjection\ServiceLocator</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">tags</span>: [<span style="color:#e6db74">&#39;container.service_locator&#39;</span>]
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">arguments</span>:
</span></span><span style="display:flex;"><span>            -
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">App\Command\CreateOrder</span>: <span style="color:#e6db74">&#39;@App\Handler\CreateOrderHandler&#39;</span>
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">App\Command\CancelOrder</span>: <span style="color:#e6db74">&#39;@App\Handler\CancelOrderHandler&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">App\Bus\CommandBus</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">arguments</span>: [<span style="color:#e6db74">&#39;@App\Handler\HandlerLocator&#39;</span>]
</span></span></code></pre></div><p>Le locator implémente <code>ContainerInterface</code> de PSR-11, donc la classe réceptrice type-hinte contre <code>Psr\Container\ContainerInterface</code>. Les services à l&rsquo;intérieur sont instanciés de manière paresseuse : si un handler donné n&rsquo;est jamais appelé pendant une requête, il n&rsquo;est jamais construit.</p>
<p>Et à propos de PSR-11 : Symfony 3.3 a fait implémenter ce standard à son conteneur. Ce qui signifie que toute bibliothèque attendant un conteneur PSR-11 fonctionne maintenant directement avec le conteneur Symfony, sans adaptateur.</p>
<h2 id="le-routing-est-devenu-plus-rapide">Le routing est devenu plus rapide</h2>
<p>Le composant de routing a réécrit comment il génère les fichiers dump. Dans une application avec 900 routes, la correspondance d&rsquo;URL est passée de 7,5 ms à 2,5 ms par correspondance : une réduction de 66%. Les optimisations vivent dans la sortie compilée, pas dans le chemin d&rsquo;exécution, donc les définitions de routes existantes en bénéficient automatiquement après un cache clear.</p>
<h2 id="trouver-la-racine-du-projet-sans-compter-les-séparateurs-de-répertoires">Trouver la racine du projet sans compter les séparateurs de répertoires</h2>
<p>Avant 3.3, obtenir la racine du projet nécessitait le pattern délicieusement maladroit <code>%kernel.root_dir%/../</code>, parce que <code>getRootDir()</code> pointait vers le répertoire <code>app/</code>. La nouvelle méthode <code>getProjectDir()</code> remonte depuis le fichier kernel jusqu&rsquo;à trouver <code>composer.json</code> et retourne ce répertoire.</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:#75715e">// Avant
</span></span></span><span style="display:flex;"><span>$path <span style="color:#f92672">=</span> $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">getParameter</span>(<span style="color:#e6db74">&#39;kernel.root_dir&#39;</span>) <span style="color:#f92672">.</span> <span style="color:#e6db74">&#39;/../var/data.db&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Après
</span></span></span><span style="display:flex;"><span>$path <span style="color:#f92672">=</span> $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">getParameter</span>(<span style="color:#e6db74">&#39;kernel.project_dir&#39;</span>) <span style="color:#f92672">.</span> <span style="color:#e6db74">&#39;/var/data.db&#39;</span>;
</span></span></code></pre></div><p>Le paramètre correspondant est <code>%kernel.project_dir%</code>. Si vous déployez sans <code>composer.json</code>, vous pouvez surcharger la méthode dans votre classe kernel et retourner n&rsquo;importe quel chemin qui fait sens.</p>
<h2 id="les-messages-flash-sans-toucher-lobjet-session">Les messages flash sans toucher l&rsquo;objet session</h2>
<p>L&rsquo;ancienne façon d&rsquo;itérer les messages flash dans Twig nécessitait de passer par <code>app.session.flashbag</code>, ce qui forçait aussi le démarrage de la session qu&rsquo;il y ait des messages ou non. Le nouveau helper <code>app.flashes</code> évite les deux :</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>{% for label, messages in app.flashes %}
</span></span><span style="display:flex;"><span>    {% for message in messages %}
</span></span><span style="display:flex;"><span>        &lt;div class=&#34;flash-{{ label }}&#34;&gt;{{ message }}&lt;/div&gt;
</span></span><span style="display:flex;"><span>    {% endfor %}
</span></span><span style="display:flex;"><span>{% endfor %}<span style="color:#75715e">{%</span> <span style="color:#66d9ef">endraw</span> <span style="color:#75715e">%}</span>
</span></span></code></pre></div><p>S&rsquo;il n&rsquo;y a pas de messages flash, la session ne démarre jamais. On peut aussi filtrer par type : <code>app.flashes('error')</code> ne retourne que les messages d&rsquo;erreur.</p>
<h2 id="la-commande-encode-password-est-devenue-intelligente">La commande encode-password est devenue intelligente</h2>
<p>La commande console <code>security:encode-password</code> est devenue plus futée. Au lieu d&rsquo;exiger qu&rsquo;on passe la classe utilisateur en argument, elle liste maintenant les classes utilisateur configurées et laisse choisir :</p>
<pre tabindex="0"><code>$ bin/console security:encode-password

  For which user class would you like to encode a password?
  [0] App\Entity\User
  [1] App\Entity\AdminUser
</code></pre><p>Elle normalise aussi la configuration des encodeurs pour gérer les cas limites avec des noms d&rsquo;utilisateur au format email que la version précédente corrompait silencieusement en remplaçant <code>@</code> par des underscores. Beau rattrapage.</p>
<h2 id="http2-push-et-resource-hints">HTTP/2 push et resource hints</h2>
<p>Le composant WebLink gère l&rsquo;en-tête HTTP <code>Link</code>, qui dit aux navigateurs (et aux proxies HTTP/2) de précharger, prérécupérer ou se préconnecter à des ressources avant même que la page les demande. Il se présente sous forme de fonctions Twig :</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>{{ preload(&#39;/fonts/custom.woff2&#39;, { as: &#39;font&#39;, crossorigin: true }) }}
</span></span><span style="display:flex;"><span>{{ prefetch(&#39;/api/next-page-data.json&#39;) }}
</span></span><span style="display:flex;"><span>{{ dns_prefetch(&#39;https://fonts.googleapis.com&#39;) }}<span style="color:#75715e">{%</span> <span style="color:#66d9ef">endraw</span> <span style="color:#75715e">%}</span>
</span></span></code></pre></div><p>Chaque appel ajoute un en-tête <code>Link</code> correspondant à la réponse. Pour les applications derrière un proxy capable HTTP/2, ça peut déclencher un server push avant même que le navigateur ait parsé le HTML. On l&rsquo;active dans <code>config.yml</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-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">framework</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">web_link</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">enabled</span>: <span style="color:#66d9ef">true</span>
</span></span></code></pre></div><h2 id="des-dépréciations-auxquelles-on-peut-vraiment-faire-confiance">Des dépréciations auxquelles on peut vraiment faire confiance</h2>
<p>La compilation du conteneur générait des avertissements de dépréciation qui disparaissaient au prochain chargement de page parce que le conteneur mis en cache était déjà construit. 3.3 persiste ces messages sur disque et les affiche dans la barre de débogage web aux côtés des dépréciations de la phase de requête. Si une classe est dépréciée pendant la compilation des services, vous le verrez sans avoir à vider le cache d&rsquo;abord.</p>
<h2 id="ce-que-ça-a-signifié-pour-40">Ce que ça a signifié pour 4.0</h2>
<p>Les defaults d&rsquo;autowiring de 3.3 sont exactement ce que Symfony 4.0 a adopté comme nouvelle structure de projet standard. Le <code>services.yaml</code> de chaque nouveau projet Symfony 4 est essentiellement le snippet ci-dessus. Si on avait déjà assimilé ce que 3.3 introduisait, le &ldquo;nouveau mode&rdquo; de 4.0 semblait familier plutôt qu&rsquo;étranger.</p>
<p>La direction était claire : moins de configuration, plus de convention. Laisser PHP comprendre ce qu&rsquo;il faut câbler ensemble.</p>
]]></content:encoded></item></channel></rss>