<?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>Http-Client on Guillaume Delré</title><link>https://guillaumedelre.github.io/fr/tags/http-client/</link><description>Recent content in Http-Client on Guillaume Delré</description><generator>Hugo</generator><language>fr-FR</language><lastBuildDate>Fri, 15 May 2026 15:00:00 +0000</lastBuildDate><atom:link href="https://guillaumedelre.github.io/fr/tags/http-client/index.xml" rel="self" type="application/rss+xml"/><item><title>L'hôte qui cachait le graphe</title><link>https://guillaumedelre.github.io/fr/2026/05/15/lh%C3%B4te-qui-cachait-le-graphe/</link><pubDate>Fri, 15 May 2026 15:00:00 +0000</pubDate><guid>https://guillaumedelre.github.io/fr/2026/05/15/lh%C3%B4te-qui-cachait-le-graphe/</guid><description>Part 4 of 8 in &amp;quot;Symfony vers le Cloud : Douze Facteurs, Treize Services&amp;quot;: Treize services partageant six variables de gateway identiques. La config semblait simple. Le graphe de dépendances était invisible — jusqu&amp;#39;à ce que Kubernetes demande où chaque service habitait vraiment.</description><category>symfony-to-the-cloud</category><content:encoded><![CDATA[<p>Chaque service de la plateforme avait ces six variables :</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>APP__GATEWAY__PRIVATE__HOST<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;platform.internal&#34;</span>
</span></span><span style="display:flex;"><span>APP__GATEWAY__PRIVATE__PORT<span style="color:#f92672">=</span><span style="color:#ae81ff">80</span>
</span></span><span style="display:flex;"><span>APP__GATEWAY__PRIVATE__SCHEME<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;http&#34;</span>
</span></span><span style="display:flex;"><span>APP__GATEWAY__PUBLIC__HOST<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;platform.internal&#34;</span>
</span></span><span style="display:flex;"><span>APP__GATEWAY__PUBLIC__PORT<span style="color:#f92672">=</span><span style="color:#ae81ff">80</span>
</span></span><span style="display:flex;"><span>APP__GATEWAY__PUBLIC__SCHEME<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;http&#34;</span>
</span></span></code></pre></div><p>Treize services, six variables chacun, une seule valeur. En lisant la config d&rsquo;un service quelconque, l&rsquo;architecture semblait plate. Tout parlait au même hôte. C&rsquo;était tout le tableau.</p>
<p>Ce ne l&rsquo;était pas.</p>
<h2 id="comment-fonctionnait-la-gateway">Comment fonctionnait la gateway</h2>
<p>La gateway se trouvait devant chaque service et gérait tout le trafic inter-services. Un service appelant l&rsquo;API content construisait une requête vers <code>http://platform.internal/content/api/</code> — la gateway la recevait, identifiait la cible depuis le chemin de l&rsquo;URL, et la transmettait au bon backend. Chaque client HTTP inter-service dans <code>framework.yaml</code> suivait le même schéma :</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">content.client</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">base_uri</span>: <span style="color:#e6db74">&#34;%http_client.gateway.base_uri%/content/api/&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">headers</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">Host</span>: <span style="color:#e6db74">&#34;%env(APP__GATEWAY__PRIVATE__HOST)%&#34;</span>
</span></span></code></pre></div><p>Le paramètre <code>http_client.gateway.base_uri</code> était assemblé depuis les variables GATEWAY. La gateway savait où tournait chaque service. Les services n&rsquo;avaient pas besoin de le savoir. De leur point de vue, tout était <code>platform.internal</code>.</p>
<p>Ça fonctionnait. Pendant des années, ça fonctionnait bien. Ajouter un service signifiait ajouter un alias DNS dans la config de la gateway, pas toucher treize fichiers <code>.env</code>. La gateway abstraisait la topologie. Les services restaient découplés du détail d&rsquo;infrastructure de qui tournait où.</p>
<h2 id="ce-que-la-gateway-absorbait">Ce que la gateway absorbait</h2>
<p>L&rsquo;abstraction avait un coût qui n&rsquo;apparaissait pas tant qu&rsquo;on n&rsquo;essayait pas de lire le système.</p>
<p>En regardant le fichier env de <code>content</code>, on voyait six variables de gateway et rien d&rsquo;autre sur la communication inter-services. Pour découvrir que <code>content</code> appelait <code>conversion</code>, <code>shorty</code> et <code>media</code>, il fallait lire <code>framework.yaml</code>. Pour découvrir que <code>pilot</code> appelait dix services externes, il fallait tracer les clients HTTP un par un et compter.</p>
<p>Le chiffre était dix. Authentication, bam, config, content, conversion, media, product, shorty, sitemap, social. Dix des treize services de la plateforme dont <code>pilot</code> dépendait à l&rsquo;exécution, aucun d&rsquo;eux visible depuis sa configuration. Six variables disaient : parle à la gateway. Elles ne disaient rien de la forme de ce qui se trouvait derrière.</p>
<p>Cette information existait — dans le code, dans la config framework, dans les têtes des gens qui avaient construit ces intégrations. Elle ne vivait juste nulle part où on pouvait la lire d&rsquo;un coup d&rsquo;œil.</p>
<h2 id="ce-que-kubernetes-a-rendu-explicite">Ce que Kubernetes a rendu explicite</h2>
<p>On-premise, la gateway était un seul hostname résolvable. Un enregistrement DNS, un jeu de variables, un seul endroit à mettre à jour. Kubernetes ne fonctionne pas comme ça. Chaque service obtient son propre nom DNS à l&rsquo;intérieur du cluster — <code>content.namespace.svc.cluster.local</code>, <code>conversion.namespace.svc.cluster.local</code>. Le trafic inter-services passe directement, service à service, sans gateway partagée.</p>
<p>Passer à Kubernetes signifiait que l&rsquo;abstraction de la gateway devait céder la place. Chaque service devait savoir, concrètement, où vivait chacune de ses dépendances. Les six variables génériques ne pouvaient pas exprimer ça.</p>
<p>Le refacto les a remplacées par des variables HOST par cible — une par dépendance de service, nommée d&rsquo;après la cible :</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><span style="color:#75715e"># content/.env — content appelle ces quatre services</span>
</span></span><span style="display:flex;"><span>APP__CONFIG__HOST<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;platform.internal&#34;</span>
</span></span><span style="display:flex;"><span>APP__CONVERSION__HOST<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;platform.internal&#34;</span>
</span></span><span style="display:flex;"><span>APP__MEDIA__HOST<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;platform.internal&#34;</span>
</span></span><span style="display:flex;"><span>APP__SHORTY__HOST<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;platform.internal&#34;</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-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># pilot/.env — dix dépendances de service</span>
</span></span><span style="display:flex;"><span>APP__AUTHENTICATION__HOST<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;platform.internal&#34;</span>
</span></span><span style="display:flex;"><span>APP__BAM__HOST<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;platform.internal&#34;</span>
</span></span><span style="display:flex;"><span>APP__CONFIG__HOST<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;platform.internal&#34;</span>
</span></span><span style="display:flex;"><span>APP__CONTENT__HOST<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;platform.internal&#34;</span>
</span></span><span style="display:flex;"><span>APP__CONVERSION__HOST<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;platform.internal&#34;</span>
</span></span><span style="display:flex;"><span>APP__MEDIA__HOST<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;platform.internal&#34;</span>
</span></span><span style="display:flex;"><span>APP__PRODUCT__HOST<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;platform.internal&#34;</span>
</span></span><span style="display:flex;"><span>APP__SHORTY__HOST<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;platform.internal&#34;</span>
</span></span><span style="display:flex;"><span>APP__SITEMAP__HOST<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;platform.internal&#34;</span>
</span></span><span style="display:flex;"><span>APP__SOCIAL__HOST<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;platform.internal&#34;</span>
</span></span></code></pre></div><p>Chaque client HTTP dans <code>framework.yaml</code> a reçu sa propre <code>base_uri</code> construite depuis la variable HOST de sa cible, et le header <code>Host</code> a cédé la place à un <code>User-Agent</code> qui identifie l&rsquo;appelant :</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">content.client</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">base_uri</span>: <span style="color:#e6db74">&#34;%env(APP__HTTP__SCHEME)%://%env(APP__CONTENT__HOST)%:%env(APP__HTTP__PORT)%/content/api/&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">headers</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">User-Agent</span>: <span style="color:#e6db74">&#34;Platform Content - %semver%&#34;</span>
</span></span></code></pre></div><p>Le changement n&rsquo;est pas cosmétique. Dans l&rsquo;ancienne configuration, le header <code>Host</code> explicite garantissait que les requêtes atteignaient le bon virtual host de la gateway quelle que soit la résolution DNS. Dans la nouvelle, chaque client pointe directement vers le nom DNS de sa cible — le <code>Host</code> correct est dérivé automatiquement de la <code>base_uri</code>. L&rsquo;emplacement du header ne reste pas vide : le <code>User-Agent</code> identifie désormais le service appelant, ce qui remonte dans les logs et le traçage distribué sans instrumentation supplémentaire.</p>
<h2 id="linconfort-de-la-lisibilité">L&rsquo;inconfort de la lisibilité</h2>
<p>Le fichier env de <code>pilot</code> est passé de neuf variables de gateway à dix variables HOST spécifiques par service. Le fichier est devenu plus long. L&rsquo;architecture n&rsquo;est pas devenue plus simple — les dix dépendances étaient là avant et elles sont toujours là. Ce qui a changé, c&rsquo;est qu&rsquo;elles sont lisibles.</p>
<p>Le <a href="https://12factor.net/config" target="_blank" rel="noopener noreferrer">Facteur III</a>
 dit de stocker la config dans l&rsquo;environnement. L&rsquo;ancienne approche satisfaisait ça à la lettre : six variables, toutes dans des fichiers env, aucune en dur dans le code. Mais des variables qui effondrent le graphe de dépendances entier dans un seul hostname opaque ne sont pas vraiment de la configuration — elles sont un raccourci qui échange la lisibilité contre la commodité. Le Facteur III ne demande pas seulement que la config soit externalisée — il suppose implicitement qu&rsquo;elle reste informative une fois externalisée.</p>
<p>Le refacto n&rsquo;a rien simplifié. Il a rendu la complexité visible. Les dix variables HOST de <code>pilot</code> documentent, dans le fichier <code>.env</code> lui-même, les dix services dont il dépend. Un nouveau membre d&rsquo;équipe qui lit ce fichier apprend quelque chose de réel sur l&rsquo;architecture. L&rsquo;ancien fichier lui apprenait qu&rsquo;il y avait une gateway.</p>
<p>Il y a une version de cette histoire où on lit l&rsquo;état final et on conclut que l&rsquo;équipe a fait un travail inutile — elle a remplacé six variables par dix, toutes pointant vers le même hôte de toute façon. En développement local, <code>platform.internal</code> résout toujours au même endroit. Le comportement fonctionnel n&rsquo;a pas changé.</p>
<p>Le changement est dans ce que la config communique. Dans Kubernetes, les valeurs HOST divergent : chaque cible obtient son propre nom DNS interne au cluster, différent par environnement. Les variables portent maintenant une vraie information. Le refacto a préparé la config à être honnête sur une topologie qu&rsquo;elle simplifiait silencieusement depuis des années.</p>
]]></content:encoded></item><item><title>Symfony 4.4 LTS : HttpClient, Mailer, Messenger et les fonctionnalités qui ont tenu bon</title><link>https://guillaumedelre.github.io/fr/2020/01/04/symfony-4.4-lts-httpclient-mailer-messenger-et-les-fonctionnalit%C3%A9s-qui-ont-tenu-bon/</link><pubDate>Sat, 04 Jan 2020 00:00:00 +0000</pubDate><guid>https://guillaumedelre.github.io/fr/2020/01/04/symfony-4.4-lts-httpclient-mailer-messenger-et-les-fonctionnalit%C3%A9s-qui-ont-tenu-bon/</guid><description>Part 4 of 11 in &amp;quot;Sorties Symfony&amp;quot;: Symfony 4.4 LTS embarque un HttpClient mature et un Messenger prêt pour la production — les couches HTTP et asynchrone qui manquaient à Symfony.</description><category>symfony-releases</category><content:encoded><![CDATA[<p>Symfony 4.4 et 5.0 sont tous les deux sortis le 21 novembre 2019. La 4.4 est la LTS : même ensemble de fonctionnalités que la 5.0, couche de dépréciation intégrée, et une longue fenêtre de support pour les équipes qui ne peuvent pas suivre chaque release.</p>
<p>La fonctionnalité qui mérite d&rsquo;être mise en avant est arrivée en 4.2 et a mûri tout au long des 4.3 et 4.4 : <code>HttpClient</code>.</p>
<h2 id="httpclient">HttpClient</h2>
<p>Les options HTTP natives de PHP (<code>file_get_contents</code> avec des contextes de flux, cURL, Guzzle) ont chacune leur propre modèle, leurs propres bizarreries et leur propre coût d&rsquo;abstraction. Symfony 4.2 a introduit <code>HttpClient</code>, un client HTTP first-party avec une seule API pour plusieurs transports.</p>
<p>L&rsquo;interface est claire :</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>$response <span style="color:#f92672">=</span> $client<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">request</span>(<span style="color:#e6db74">&#39;GET&#39;</span>, <span style="color:#e6db74">&#39;https://api.example.com/users&#39;</span>);
</span></span><span style="display:flex;"><span>$users <span style="color:#f92672">=</span> $response<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">toArray</span>();
</span></span></code></pre></div><p>L&rsquo;implémentation est asynchrone par défaut. Les réponses sont paresseuses : la requête réseau n&rsquo;a pas lieu tant qu&rsquo;on ne lit pas réellement la réponse. Plusieurs requêtes peuvent être initiées et résolues au fil de l&rsquo;arrivée des données, sans threads ni callbacks.</p>
<p>Le transport mock intégré (<code>MockHttpClient</code>) rend les tests d&rsquo;appels HTTP indolores sans avoir à démarrer des serveurs ou patcher des fonctions globales.</p>
<h2 id="mailer">Mailer</h2>
<p>Également stabilisé en 4.4 : le composant <code>Mailer</code>, qui remplace <code>SwiftMailerBundle</code> comme solution d&rsquo;email recommandée. Le transport se configure via DSN :</p>
<pre tabindex="0"><code>MAILER_DSN=smtp://user:pass@smtp.example.com:587
</code></pre><p>L&rsquo;approche DSN signifie que changer de fournisseur (Mailgun, Postmark, SES, SMTP local) est un changement de config, pas un changement de code. Les tests d&rsquo;emails utilisent un spooler par défaut dans les environnements hors production.</p>
<h2 id="messenger-se-mâture">Messenger se mâture</h2>
<p>Le composant Messenger a atterri en 3.4 à titre expérimental. En 4.4, il est stable et éprouvé en production : gestion de messages asynchrones avec logique de retry, transport d&rsquo;échec, et adaptateurs pour AMQP, Redis, Doctrine, et les transports in-process.</p>
<p>Le pattern qu&rsquo;il permet (traiter une requête de façon synchrone, dispatcher du travail de façon asynchrone, réessayer en cas d&rsquo;échec) remplace toute une classe de setups Gearman/RabbitMQ qui nécessitaient des bibliothèques tierces et une configuration conséquente.</p>
<h2 id="la-fenêtre-lts">La fenêtre LTS</h2>
<p>La 4.4 est supportée pour les bugs jusqu&rsquo;en novembre 2022 et pour les correctifs de sécurité jusqu&rsquo;en novembre 2023. Si vous êtes sur la 4.x et recherchez la stabilité, c&rsquo;est un endroit confortable où rester. Les avertissements de dépréciation qu&rsquo;elle introduit pointent directement vers ce que la 5.0 exigera.</p>
<h2 id="le-composant-messenger-de-lexpérimental-à-la-production">Le composant Messenger, de l&rsquo;expérimental à la production</h2>
<p>Messenger est arrivé en 4.1 comme une expérience. Le concept était simple : dispatcher un objet message vers un bus, le traiter immédiatement ou le router vers un transport pour un traitement asynchrone. En 4.3 et 4.4, l&rsquo;expérience était devenue de l&rsquo;infrastructure.</p>
<p>La release 4.3 a ajouté un transport d&rsquo;échec dédié. Quand un message échoue après toutes les tentatives de retry, il va quelque part de récupérable plutôt que de simplement disparaître :</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">messenger</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">failure_transport</span>: <span style="color:#ae81ff">failed</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">transports</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">async</span>: <span style="color:#e6db74">&#39;%env(MESSENGER_TRANSPORT_DSN)%&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">failed</span>: <span style="color:#e6db74">&#39;doctrine://default?queue_name=failed&#39;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">routing</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">App\Message\SendEmail</span>: <span style="color:#ae81ff">async</span>
</span></span></code></pre></div><p>Les messages qui atterrissent dans <code>failed</code> peuvent être inspectés et retentés manuellement. Avant ça, les messages en échec étaient une entrée de log et un mal de tête. Après, c&rsquo;est une file qu&rsquo;on peut vraiment travailler.</p>
<h2 id="le-dispatching-dévénements-avec-les-objets-en-première-place">Le dispatching d&rsquo;événements, avec les objets en première place</h2>
<p>Depuis le début, le système d&rsquo;événements de Symfony utilisait des noms d&rsquo;événements en chaîne comme identifiant principal. On définissait <code>OrderEvents::NEW_ORDER = 'order.new_order'</code>, on écoutait cette chaîne, et on passait l&rsquo;objet événement comme paramètre secondaire.</p>
<p>La 4.3 a inversé ça. L&rsquo;objet événement passe en premier, et le nom de l&rsquo;événement devient optionnel :</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>$dispatcher<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">dispatch</span>(<span style="color:#a6e22e">OrderEvents</span><span style="color:#f92672">::</span><span style="color:#a6e22e">NEW_ORDER</span>, $event);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// 4.3+
</span></span></span><span style="display:flex;"><span>$dispatcher<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">dispatch</span>($event);
</span></span></code></pre></div><p>Omettez le nom et Symfony utilise le nom de classe comme identifiant. Les listeners et subscribers peuvent maintenant référencer la classe directement :</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">public</span> <span style="color:#66d9ef">static</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">getSubscribedEvents</span>()<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:#66d9ef">return</span> [
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">OrderPlacedEvent</span><span style="color:#f92672">::</span><span style="color:#a6e22e">class</span> <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#39;onOrderPlaced&#39;</span>,
</span></span><span style="display:flex;"><span>    ];
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Les événements HttpKernel ont été renommés en conséquence : <code>GetResponseEvent</code> est devenu <code>RequestEvent</code>, <code>FilterResponseEvent</code> est devenu <code>ResponseEvent</code>. Les anciens noms sont restés comme alias pendant toute la 4.x.</p>
<h2 id="vardumper-obtient-un-serveur">VarDumper obtient un serveur</h2>
<p>Un <code>dump()</code> dans un contrôleur qui retourne du JSON, et votre sortie de debug se retrouve injectée directement dans le corps de la réponse. Pour le développement d&rsquo;API, c&rsquo;est suffisamment agaçant pour que les gens désactivent le dumping complètement.</p>
<p>La 4.1 a ajouté un serveur VarDumper qui capture les dumps séparément :</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 server:dump
</span></span></code></pre></div><p>Configurez la destination du dump dans <code>config/packages/dev/debug.yaml</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">debug</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">dump_destination</span>: <span style="color:#e6db74">&#34;tcp://%env(VAR_DUMPER_SERVER)%&#34;</span>
</span></span></code></pre></div><p>Maintenant, <code>dump()</code> dans votre contrôleur API envoie les données vers la console du serveur au lieu de polluer la réponse. Le serveur affiche le dump avec le fichier source, la requête HTTP qui l&rsquo;a déclenché, et le timestamp.</p>
<h2 id="varexporter-pour-quand-var_export-vous-déçoit">VarExporter, pour quand <code>var_export()</code> vous déçoit</h2>
<p><code>var_export()</code> a deux problèmes : il ignore la sémantique de sérialisation et sa sortie n&rsquo;est pas conforme à PSR-2. Le composant VarExporter de la 4.2 corrige 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-php" data-lang="php"><span style="display:flex;"><span>$exported <span style="color:#f92672">=</span> <span style="color:#a6e22e">VarExporter</span><span style="color:#f92672">::</span><span style="color:#a6e22e">export</span>([<span style="color:#ae81ff">123</span>, [<span style="color:#e6db74">&#39;abc&#39;</span>, <span style="color:#66d9ef">true</span>]]);
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Retourne :
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">// [
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">//     123,
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">//     [
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">//         &#39;abc&#39;,
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">//         true,
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">//     ],
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">// ]
</span></span></span></code></pre></div><p>Plus important encore, il gère correctement les objets implémentant <code>Serializable</code>, <code>__sleep</code>, et <code>__wakeup</code>. Là où <code>var_export()</code> abandonne silencieusement les méthodes de sérialisation et exporte les propriétés brutes, VarExporter produit du code qui appelle les mêmes hooks qu&rsquo;<code>unserialize()</code> utiliserait. Le cas d&rsquo;usage pratique est le préchauffage du cache : générer des fichiers PHP que OPcache peut charger sans ré-exécuter des calculs coûteux.</p>
<h2 id="des-mots-de-passe-vérifiés-contre-les-bases-de-données-de-violations">Des mots de passe vérifiés contre les bases de données de violations</h2>
<p>La contrainte <code>NotCompromisedPassword</code> est arrivée en 4.3. Elle vérifie les mots de passe soumis contre la base de données de violations d&rsquo;haveibeenpwned.com sans envoyer le vrai mot de passe nulle part.</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">Symfony\Component\Validator\Constraints</span> <span style="color:#66d9ef">as</span> <span style="color:#a6e22e">Assert</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">User</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">#[Assert\NotCompromisedPassword]
</span></span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#a6e22e">string</span> $plainPassword;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>L&rsquo;implémentation utilise la k-anonymité : on hash le mot de passe en SHA-1, on envoie seulement les cinq premiers caractères à l&rsquo;API, on récupère tous les hashs correspondants, on vérifie localement. Le mot de passe ne quitte jamais votre serveur. Pour les formulaires d&rsquo;inscription, ajouter cette contrainte c&rsquo;est une ligne et un signal de sécurité vraiment utile.</p>
<h2 id="workflow-obtient-du-contexte">Workflow obtient du contexte</h2>
<p>Le composant Workflow existait avant la 4.x, mais la 4.3 a ajouté la propagation de contexte : la possibilité de passer des données arbitraires à travers une transition et d&rsquo;y accéder dans les listeners.</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>$workflow<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">apply</span>($article, <span style="color:#e6db74">&#39;publish&#39;</span>, [
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#39;user&#39;</span> <span style="color:#f92672">=&gt;</span> $user<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">getUsername</span>(),
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#39;reason&#39;</span> <span style="color:#f92672">=&gt;</span> $request<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">request</span><span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">get</span>(<span style="color:#e6db74">&#39;reason&#39;</span>),
</span></span><span style="display:flex;"><span>]);
</span></span></code></pre></div><p>Le contexte arrive dans <code>TransitionEvent</code> et est stocké aux côtés du marquage. Pour les pistes d&rsquo;audit, c&rsquo;est la différence entre savoir qu&rsquo;une transition s&rsquo;est produite et savoir qui l&rsquo;a déclenchée et pourquoi. On peut aussi injecter du contexte depuis un subscriber sans toucher à chaque appel <code>apply()</code>, ce qui est pratique pour les préoccupations transversales comme les timestamps ou l&rsquo;utilisateur courant.</p>
<h2 id="lautowiring-est-devenu-plus-intelligent">L&rsquo;autowiring est devenu plus intelligent</h2>
<p>La 4.2 a ajouté la liaison par type et par nom simultanément. Avant, on pouvait lier par type (<code>LoggerInterface</code>) ou par nom (<code>$logger</code>), mais pas les deux en même temps. Ça posait problème quand un service a besoin de deux implémentations différentes de la même interface :</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">bind</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">Psr\Log\LoggerInterface $orderLogger</span>: <span style="color:#e6db74">&#39;@monolog.logger.orders&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">Psr\Log\LoggerInterface $paymentLogger</span>: <span style="color:#e6db74">&#39;@monolog.logger.payments&#39;</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">OrderService</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></span><span style="display:flex;"><span>        <span style="color:#66d9ef">private</span> <span style="color:#a6e22e">LoggerInterface</span> $orderLogger,   <span style="color:#75715e">// gets monolog.logger.orders
</span></span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">private</span> <span style="color:#a6e22e">LoggerInterface</span> $paymentLogger, <span style="color:#75715e">// gets monolog.logger.payments
</span></span></span><span style="display:flex;"><span>    ) {}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>La correspondance exige que le type et le nom de l&rsquo;argument s&rsquo;alignent, donc pas de risque d&rsquo;injecter accidentellement le mauvais logger.</p>
<h2 id="errorhandler-remplace-le-composant-debug">ErrorHandler remplace le composant Debug</h2>
<p>Le composant <code>Debug</code>, inchangé depuis 2013, avait une dépendance maladroite sur TwigBundle même pour les apps API-only. Toute exception non attrapée dans une API JSON rendait une page d&rsquo;erreur HTML à moins d&rsquo;écrire des listeners d&rsquo;exception personnalisés.</p>
<p>La 4.4 extrait ça dans un composant <code>ErrorHandler</code> dédié. Pour les requêtes non-HTML, les réponses d&rsquo;erreur suivent désormais RFC 7807 nativement :</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-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;title&#34;</span>: <span style="color:#e6db74">&#34;Not Found&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;status&#34;</span>: <span style="color:#ae81ff">404</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;detail&#34;</span>: <span style="color:#e6db74">&#34;Sorry, the page you are looking for could not be found&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Pas besoin de Twig. Le format suit l&rsquo;en-tête <code>Accept</code> : JSON pour les requêtes JSON, XML pour les requêtes XML. Pour personnaliser davantage, on fournit un normalizer via le composant Serializer plutôt qu&rsquo;un template Twig.</p>
<h2 id="le-préchargement-php-74-câblé-automatiquement">Le préchargement PHP 7.4, câblé automatiquement</h2>
<p>PHP 7.4 a introduit le préchargement OPcache : charger des fichiers en mémoire partagée avant l&rsquo;arrivée de toute requête, pour qu&rsquo;ils soient disponibles sous forme d&rsquo;opcodes compilés dès la toute première requête. Le gain pratique est de 30 à 50 % de temps de réponse en moins sans changer une ligne de code.</p>
<p>Le bémol c&rsquo;est la configuration : il faut spécifier exactement quels fichiers précharger dans <code>php.ini</code>. Symfony 4.4 génère ce fichier automatiquement dans le répertoire de cache :</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-ini" data-lang="ini"><span style="display:flex;"><span><span style="color:#75715e">; php.ini</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">opcache.preload</span><span style="color:#f92672">=</span><span style="color:#e6db74">/path/to/project/var/cache/prod/App_KernelProdContainer.preload.php</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">opcache.preload_user</span><span style="color:#f92672">=</span><span style="color:#e6db74">www-data</span>
</span></span></code></pre></div><p>Lancez <code>cache:warmup</code> en production et pointez OPcache vers le fichier généré. Symfony précharge le container, les routes compilées et les templates Twig : les fichiers lus à chaque requête et qui ne changent jamais entre les déploiements.</p>
<h2 id="console--codes-de-retour-et-no_color">Console : codes de retour et NO_COLOR</h2>
<p>Deux petites choses en 4.4 qui auraient honnêtement dû exister plus tôt. Les commandes qui ne retournent pas d&rsquo;entier depuis <code>execute()</code> déclenchent maintenant un avertissement de dépréciation. En 5.0, le type de retour devient obligatoire. Retourner <code>0</code> pour le succès, non-zéro pour l&rsquo;échec : comportement Unix standard, et ça rend l&rsquo;intégration avec les superviseurs de processus et les pipelines CI sans ambiguïté.</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">protected</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">execute</span>(<span style="color:#a6e22e">InputInterface</span> $input, <span style="color:#a6e22e">OutputInterface</span> $output)<span style="color:#f92672">:</span> <span style="color:#a6e22e">int</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// ...
</span></span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">Command</span><span style="color:#f92672">::</span><span style="color:#a6e22e">SUCCESS</span>; <span style="color:#75715e">// = 0
</span></span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Le deuxième point : le support de la variable d&rsquo;environnement <code>NO_COLOR</code>, suivant la convention de no-color.org. Activez-la et toutes les commandes console de Symfony abandonnent les codes d&rsquo;échappement ANSI quelle que soit la capacité déclarée par le terminal. Utile pour les environnements CI qui capturent la sortie en texte et qui s&rsquo;étranglent sur les codes couleur intégrés dans les logs.</p>
]]></content:encoded></item></channel></rss>