<?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>Workflow on Guillaume Delré</title><link>https://guillaumedelre.github.io/fr/tags/workflow/</link><description>Recent content in Workflow on Guillaume Delré</description><generator>Hugo</generator><language>fr-FR</language><lastBuildDate>Fri, 12 Jan 2024 00:00:00 +0000</lastBuildDate><atom:link href="https://guillaumedelre.github.io/fr/tags/workflow/index.xml" rel="self" type="application/rss+xml"/><item><title>Symfony 7.0 : PHP 8.2 minimum et les annotations enfin disparues</title><link>https://guillaumedelre.github.io/fr/2024/01/12/symfony-7.0-php-8.2-minimum-et-les-annotations-enfin-disparues/</link><pubDate>Fri, 12 Jan 2024 00:00:00 +0000</pubDate><guid>https://guillaumedelre.github.io/fr/2024/01/12/symfony-7.0-php-8.2-minimum-et-les-annotations-enfin-disparues/</guid><description>Part 9 of 11 in &amp;quot;Sorties Symfony&amp;quot;: Symfony 7.0 exige PHP 8.2, abandonne entièrement les annotations Doctrine et livre un composant Workflow reconstruit.</description><category>symfony-releases</category><content:encoded><![CDATA[<p>Symfony 7.0 est sorti le 29 novembre 2023, le même jour que 6.4. Le pattern tient : la version X.0 coupe le code déprécié et élève le plancher PHP. 7.0 exige PHP 8.2 et supprime tout ce que 6.4 avait marqué comme déprécié.</p>
<p>La suppression la plus visible : les annotations Doctrine. <code>@Route</code>, <code>@ORM\Column</code>, <code>@Assert</code> — disparus. Les attributs PHP natifs sont l&rsquo;approche recommandée depuis Symfony 5.2. 7.0 rend juste ça officiel.</p>
<h2 id="les-attributs-partout">Les attributs partout</h2>
<p>La migration des annotations vers les attributs est principalement mécanique : la syntaxe passe de <code>@</code> à <code>#[]</code>, et les références de classes passent des classes d&rsquo;annotation Doctrine aux classes d&rsquo;attribut PHP :</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><span style="color:#e6db74">/** @Route(&#39;/users&#39;, methods={&#34;GET&#34;}) */</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><span style="color:#75715e">#[Route(&#39;/users&#39;, methods: [&#39;GET&#39;])]
</span></span></span></code></pre></div><p>Le vrai gain n&rsquo;est pas juste la syntaxe : les attributs sont validés par le moteur PHP, pas un parseur de docblock. Les IDEs peuvent les résoudre sans plugins personnalisés. Les outils d&rsquo;analyse statique les comprennent nativement. Fini les &ldquo;ça échoue silencieusement à l&rsquo;exécution à cause d&rsquo;une faute de frappe dans un commentaire.&rdquo;</p>
<h2 id="workflow-avec-attributs-php">Workflow avec attributs PHP</h2>
<p>Les listeners et guards d&rsquo;événements Workflow peuvent maintenant être enregistrés via des attributs :</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">#[AsGuard(workflow: &#39;order&#39;, transition: &#39;ship&#39;)]
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">public</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">canShip</span>(<span style="color:#a6e22e">Event</span> $event)<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>    <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span>$event<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">getSubject</span>()<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">isPaymentConfirmed</span>()) {
</span></span><span style="display:flex;"><span>        $event<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">setBlocked</span>(<span style="color:#66d9ef">true</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Le profiler de workflow, un panneau dédié montrant le marquage courant et les transitions disponibles, est un outil de debug vraiment utile quand on travaille avec des machines à états complexes.</p>
<h2 id="datepoint-dans-le-composant-clock">DatePoint dans le composant Clock</h2>
<p><code>DatePoint</code>, le <code>DateTime</code> immutable avec gestion stricte des erreurs introduit dans 6.4, est maintenant la façon recommandée de travailler avec les dates. Combiné avec les propriétés readonly de PHP 8.2, les objets-valeur de dates dans le code de domaine deviennent presque trivialement propres :</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:#a6e22e">readonly</span> <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Order</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">public</span> <span style="color:#a6e22e">DatePoint</span> $createdAt,
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">public</span> <span style="color:#f92672">?</span><span style="color:#a6e22e">DatePoint</span> $shippedAt <span style="color:#f92672">=</span> <span style="color:#66d9ef">null</span>,
</span></span><span style="display:flex;"><span>    ) {}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h2 id="ce-que-70-supprime">Ce que 7.0 supprime</h2>
<p>La liste complète des suppressions : le support des annotations Doctrine, le bridge du composant <code>Templating</code>, le bridge <code>ProxyManager</code>, le bridge <code>Monolog</code> pour les versions inférieures à 3.0, et le transport Sendinblue (remplacé par Brevo). Le support PHP 8.0 et 8.1 se termine aussi. 8.2 est le plancher maintenant.</p>
<p>Monter de 6.4 avec toutes les notices de dépréciation corrigées, et 7.0 est fluide. Sauter cette étape et on s&rsquo;expose à une mauvaise surprise.</p>
<h2 id="scheduler-et-assetmapper-diplômés">Scheduler et AssetMapper diplômés</h2>
<p>Deux composants qui sont sortis en expérimental dans 6.3 sont maintenant stables : Scheduler et AssetMapper. Stable signifie des APIs verrouillées, plus de mises en garde <code>@experimental</code>, et ils apparaissent correctement dans le guide de mise à jour. On peut vraiment compter sur eux maintenant.</p>
<p>Scheduler reçoit <code>#[AsCronTask]</code> et <code>#[AsPeriodicTask]</code> pour l&rsquo;enregistrement de tâches par attribut, la modification de planning à l&rsquo;exécution avec recalcul du heap, <code>FailureEvent</code>, et une option <code>--date</code> sur <code>schedule:debug</code>. AssetMapper ajoute le support des fichiers CSS dans l&rsquo;importmap, une commande <code>outdated</code>, une commande <code>audit</code>, et le préchargement automatique via WebLink.</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">#[AsCronTask(&#39;0 2 * * *&#39;)]
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">NightlyReportMessage</span> {}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#[AsPeriodicTask(frequency: &#39;1 hour&#39;)]
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">HourlyCleanupMessage</span> {}
</span></span></code></pre></div><h2 id="le-câblage-de-services-reçoit-deux-nouveaux-attributs">Le câblage de services reçoit deux nouveaux attributs</h2>
<p><code>#[AutowireLocator]</code> et <code>#[AutowireIterator]</code> ont atterri dans 6.4 et sont stables dans 7.0. Ils remplacent la configuration verbose XML/YAML des service locators taggués par quelque chose qu&rsquo;on peut juste mettre directement en PHP :</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">HandlerRegistry</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:#75715e">#[AutowireLocator(&#39;app.handler&#39;, indexAttribute: &#39;key&#39;)]
</span></span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">private</span> <span style="color:#a6e22e">ContainerInterface</span> $handlers,
</span></span><span style="display:flex;"><span>    ) {}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><code>#[Target]</code> est aussi plus intelligent : quand un service a un alias d&rsquo;autowiring nommé comme <code>invoice.lock.factory</code>, on peut maintenant écrire <code>#[Target('invoice')]</code> au lieu du nom complet de l&rsquo;alias. Moins de bruit quand le type dit déjà ce qu&rsquo;on veut.</p>
<h2 id="messenger-reçoit-une-gestion-plus-précise-des-échecs">Messenger reçoit une gestion plus précise des échecs</h2>
<p><code>RejectRedeliveredMessageException</code> dit au worker de ne pas retenter un message, ce qui est pratique quand un message arrive deux fois à cause d&rsquo;un timeout d&rsquo;ack du transport et qu&rsquo;on a besoin d&rsquo;une sémantique exactly-once. <code>messenger:failed:remove --all</code> vide tout le transport d&rsquo;échec en un coup, pas de boucle nécessaire. Les retries échoués peuvent aussi aller directement au transport d&rsquo;échec, en contournant entièrement la queue de retry.</p>
<p>Plusieurs hôtes Redis Sentinel sont maintenant supportés dans le DSN :</p>
<pre tabindex="0"><code>redis-sentinel://host1:26379,host2:26379,host3:26379/mymaster
</code></pre><h2 id="console-reçoit-les-noms-de-signaux-et-le-profilage-de-commandes">Console reçoit les noms de signaux et le profilage de commandes</h2>
<p><code>SignalMap</code> mappe les entiers de signaux à leurs noms POSIX. Quand un worker attrape <code>SIGTERM</code>, le log dit maintenant <code>SIGTERM</code> au lieu de <code>15</code>. Petite chose, vraie amélioration. <code>ConsoleTerminateEvent</code> est dispatché même quand le processus se termine via signal, ce qui n&rsquo;était pas le cas avant 7.0.</p>
<p>Le profilage de commandes arrive aussi : passer <code>--profile</code> à <code>bin/console</code> et les données collectées vont directement dans le profiler Symfony, navigable depuis l&rsquo;UI web.</p>
<h2 id="form--des-petites-choses-qui-saccumulent">Form : des petites choses qui s&rsquo;accumulent</h2>
<p><code>ChoiceType</code> reçoit une option <code>duplicate_preferred_choices</code>. La définir à <code>false</code> et on arrête de montrer la même option deux fois quand les choix préférés chevauchent la liste complète. <code>FormEvent::setData()</code> est déprécié pour les événements où les données sont déjà verrouillées à ce point du cycle de vie. La barre oblique auto-fermante sur les éléments <code>&lt;input&gt;</code> est aussi supprimée : <code>&lt;input&gt;</code> est un élément void en HTML5 et la barre oblique était techniquement invalide.</p>
<p>Le support des enums dans les formulaires est bien fait : <code>ChoiceType</code> rend les backed enums directement, et les enums translatable reçoivent leurs labels via le translator sans câblage personnalisé.</p>
<h2 id="httpfoundation--small-but-useful">HttpFoundation : small but useful</h2>
<p><code>Response::send()</code> reçoit un paramètre <code>$flush</code>. Passer <code>false</code> pour bufferiser la sortie sans la flusher au client, utile quand on enchaîne des middlewares qui doivent inspecter la réponse avant qu&rsquo;elle quitte le processus.</p>
<p><code>UriSigner</code> passe de HttpKernel à HttpFoundation, où il appartient sémantiquement. Même nom de classe, namespace différent.</p>
<p>Les cookies reçoivent le support CHIPS (Cookies Having Independent Partitioned State), le mécanisme navigateur pour les cookies cross-site dans une partition first-party. Ça ne compte que si on construit des widgets embarquables, mais bon à savoir que c&rsquo;est là.</p>
<h2 id="translation--provider-phrase-et-sortie-arborescente">Translation : provider Phrase et sortie arborescente</h2>
<p>Phrase rejoint Crowdin et Lokalise comme provider de traduction supporté. Le configurer dans <code>config/packages/translation.yaml</code> et les commandes <code>translation:push</code> / <code>translation:pull</code> gèrent la synchronisation.</p>
<p><code>translation:pull</code> reçoit une option <code>--as-tree</code> qui écrit les fichiers de traduction en YAML imbriqué plutôt qu&rsquo;en clés à notation pointée plate. Si c&rsquo;est vraiment mieux dépend entièrement de l&rsquo;équipe.</p>
<p><code>LocaleSwitcher::runWithLocale()</code> passe maintenant la locale courante comme argument au callback, évitant un appel <code>getLocale()</code> à l&rsquo;intérieur :</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>$switcher<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">runWithLocale</span>(<span style="color:#e6db74">&#39;fr&#39;</span>, <span style="color:#66d9ef">function</span> (<span style="color:#a6e22e">string</span> $locale) <span style="color:#66d9ef">use</span> ($mailer) {
</span></span><span style="display:flex;"><span>    $mailer<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">send</span>($this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">buildEmail</span>($locale));
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><h2 id="quelques-choses-dans-serializer-et-domcrawler">Quelques choses dans Serializer et DomCrawler</h2>
<p>L&rsquo;attribut <code>Context</code> du Serializer peut maintenant cibler des classes spécifiques, donc un seul DTO peut se comporter différemment pendant la (dé)sérialisation selon quelle classe détient le contexte. <code>TranslatableNormalizer</code> arrive pour normaliser les objets qui implémentent <code>TranslatableInterface</code> : le translator est appelé pendant la normalisation, pas avant.</p>
<p><code>Crawler::attr()</code> reçoit un paramètre <code>$default</code>. Au lieu de null-checker la valeur de retour, on passe une valeur de repli :</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>$src <span style="color:#f92672">=</span> $crawler<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">attr</span>(<span style="color:#e6db74">&#39;src&#39;</span>, <span style="color:#e6db74">&#39;/placeholder.png&#39;</span>);
</span></span></code></pre></div><p><code>assertAnySelectorText()</code> et <code>assertAnySelectorTextContains()</code> rejoignent l&rsquo;ensemble d&rsquo;assertions DomCrawler. Ils passent si au moins un élément correspondant satisfait la condition, plutôt que d&rsquo;en exiger que tous correspondent.</p>
<h2 id="httpclient--réponses-har-pour-les-tests">HttpClient : réponses HAR pour les tests</h2>
<p><code>MockResponse</code> accepte maintenant les fichiers HAR (HTTP Archive). Enregistrer de vraies interactions HTTP dans le navigateur ou avec un proxy, déposer le fichier <code>.har</code> dans les fixtures de tests, et les rejouer :</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>$client <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">MockHttpClient</span>(<span style="color:#a6e22e">HarFileResponseFactory</span><span style="color:#f92672">::</span><span style="color:#a6e22e">createFromFile</span>(<span style="color:#66d9ef">__DIR__</span><span style="color:#f92672">.</span><span style="color:#e6db74">&#39;/fixtures/api.har&#39;</span>));
</span></span></code></pre></div><p>Bien mieux qu&rsquo;écrire des stubs de réponse à la main quand on traite avec une API complexe.</p>
]]></content:encoded></item></channel></rss>