<?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>Php on Guillaume Delré</title><link>https://guillaumedelre.github.io/fr/tags/php/</link><description>Recent content in Php on Guillaume Delré</description><generator>Hugo</generator><language>fr-FR</language><lastBuildDate>Sun, 04 Jan 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://guillaumedelre.github.io/fr/tags/php/index.xml" rel="self" type="application/rss+xml"/><item><title>PHP 8.5 : l'opérateur pipe, une bibliothèque URI et beaucoup de nettoyage</title><link>https://guillaumedelre.github.io/fr/2026/01/04/php-8.5-lop%C3%A9rateur-pipe-une-biblioth%C3%A8que-uri-et-beaucoup-de-nettoyage/</link><pubDate>Sun, 04 Jan 2026 00:00:00 +0000</pubDate><guid>https://guillaumedelre.github.io/fr/2026/01/04/php-8.5-lop%C3%A9rateur-pipe-une-biblioth%C3%A8que-uri-et-beaucoup-de-nettoyage/</guid><description>Part 11 of 11 in &amp;quot;Sorties PHP&amp;quot;: PHP 8.5 ajoute un opérateur pipe pour des pipelines fonctionnels lisibles et une classe URI native qui met fin au parsing fragile de strings.</description><category>php-releases</category><content:encoded><![CDATA[<p>PHP 8.5 est sorti le 20 novembre. Deux fonctionnalités définissent cette version : l&rsquo;opérateur pipe et l&rsquo;extension URI. Elles résolvent des problèmes différents, mais partagent la même motivation : rendre les opérations courantes moins maladroites à exprimer.</p>
<h2 id="lopérateur-pipe">L&rsquo;opérateur pipe</h2>
<p>Les pipelines fonctionnels en PHP ont toujours été un bazar. Enchaîner des transformations nécessitait soit d&rsquo;imbriquer les appels de fonctions à l&rsquo;envers, soit de les découper en variables intermédiaires :</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 — se lit de droite à gauche
</span></span></span><span style="display:flex;"><span>$result <span style="color:#f92672">=</span> <span style="color:#a6e22e">array_sum</span>(<span style="color:#a6e22e">array_map</span>(<span style="color:#e6db74">&#39;strlen&#39;</span>, <span style="color:#a6e22e">array_filter</span>($strings, <span style="color:#e6db74">&#39;strlen&#39;</span>)));
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// ou verbeux mais lisible
</span></span></span><span style="display:flex;"><span>$filtered   <span style="color:#f92672">=</span> <span style="color:#a6e22e">array_filter</span>($strings, <span style="color:#e6db74">&#39;strlen&#39;</span>);
</span></span><span style="display:flex;"><span>$lengths    <span style="color:#f92672">=</span> <span style="color:#a6e22e">array_map</span>(<span style="color:#e6db74">&#39;strlen&#39;</span>, $filtered);
</span></span><span style="display:flex;"><span>$result     <span style="color:#f92672">=</span> <span style="color:#a6e22e">array_sum</span>($lengths);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// après — se lit de gauche à droite
</span></span></span><span style="display:flex;"><span>$result <span style="color:#f92672">=</span> $strings
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">array_filter</span>(<span style="color:#f92672">?</span>, <span style="color:#e6db74">&#39;strlen&#39;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">array_map</span>(<span style="color:#e6db74">&#39;strlen&#39;</span>, <span style="color:#f92672">?</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">array_sum</span>(<span style="color:#f92672">?</span>);
</span></span></code></pre></div><p>L&rsquo;opérateur <code>|&gt;</code> passe la valeur de gauche dans l&rsquo;expression de droite. Le placeholder <code>?</code> marque où elle va. Les pipelines se lisent maintenant dans l&rsquo;ordre où les opérations se produisent : gauche à droite, haut en bas.</p>
<p>Ça se marie bien avec les callables de première classe de PHP 8.1. Les deux fonctionnalités se composent bien :</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>$result <span style="color:#f92672">=</span> $input <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">trim</span>(<span style="color:#f92672">...</span>) <span style="color:#f92672">|&gt;</span> <span style="color:#a6e22e">strtolower</span>(<span style="color:#f92672">...</span>) <span style="color:#f92672">|&gt;</span> $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">normalize</span>(<span style="color:#f92672">...</span>);
</span></span></code></pre></div><h2 id="lextension-uri">L&rsquo;extension URI</h2>
<p>Gérer les URIs en PHP a toujours signifié soit se tourner vers une bibliothèque tierce, soit assembler <code>parse_url()</code> (retourne un tableau, pas un objet), <code>http_build_query()</code>, et de la concaténation manuelle de strings.</p>
<p>La nouvelle extension <code>Uri</code> donne une vraie API orientée objet :</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>$uri <span style="color:#f92672">=</span> <span style="color:#a6e22e">Uri\Uri</span><span style="color:#f92672">::</span><span style="color:#a6e22e">parse</span>(<span style="color:#e6db74">&#39;https://example.com/path?query=value#fragment&#39;</span>);
</span></span><span style="display:flex;"><span>$modified <span style="color:#f92672">=</span> $uri<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">withPath</span>(<span style="color:#e6db74">&#39;/new-path&#39;</span>)<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">withQuery</span>(<span style="color:#e6db74">&#39;key=val&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> $modified; <span style="color:#75715e">// https://example.com/new-path?key=val#fragment
</span></span></span></code></pre></div><p>Des objets-valeur immutables, un parsing conforme aux RFC, modifier des composants individuels sans parser et reconstruire la string entière. Attendu depuis longtemps.</p>
<h2 id="nodiscard">#[\NoDiscard]</h2>
<p>Un nouvel attribut qui génère un avertissement quand la valeur de retour est ignorée :</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">#[\NoDiscard(&#34;Use the returned collection, the original is unchanged&#34;)]
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">public</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">filter</span>(<span style="color:#a6e22e">callable</span> $fn)<span style="color:#f92672">:</span> <span style="color:#66d9ef">static</span> { <span style="color:#f92672">...</span> }
</span></span></code></pre></div><p>Utile pour les méthodes immutables où ignorer la valeur de retour est presque certainement un bug. Courant dans d&rsquo;autres langages depuis des années, maintenant en PHP où ça appartient.</p>
<h2 id="clone-with">clone with</h2>
<p>Cloner un objet avec des propriétés modifiées sans utiliser de property hooks ni une méthode <code>with()</code> personnalisée :</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>$updated <span style="color:#f92672">=</span> <span style="color:#66d9ef">clone</span>($point) <span style="color:#a6e22e">with</span> { <span style="color:#a6e22e">x</span><span style="color:#f92672">:</span> <span style="color:#ae81ff">10</span>, <span style="color:#a6e22e">y</span><span style="color:#f92672">:</span> <span style="color:#ae81ff">20</span> };
</span></span></code></pre></div><p>Syntaxe propre pour un pattern que les objets readonly nécessitaient : on clone pour &ldquo;modifier&rdquo; puisque la mutation directe n&rsquo;est pas permise.</p>
<p>PHP 8.5 a une tendance fonctionnelle. L&rsquo;opérateur pipe et l&rsquo;extension URI ensemble rendent le code de transformation de données significativement plus lisible. Le langage continue dans une direction cohérente.</p>
<h2 id="les-closures-dans-les-expressions-constantes">Les closures dans les expressions constantes</h2>
<p>Une contrainte qui existait depuis PHP 5 : les expressions constantes (arguments d&rsquo;attributs, valeurs par défaut de propriétés, valeurs par défaut de paramètres, déclarations <code>const</code>) ne pouvaient pas contenir de closures ni de callables de première classe. 8.5 supprime ça.</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">#[Validate(fn($v) =&gt; $v &gt; 0)]
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">public</span> <span style="color:#a6e22e">int</span> $count <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#66d9ef">NORMALIZER</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">strtolower</span>(<span style="color:#f92672">...</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">Config</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">readonly</span> <span style="color:#a6e22e">Closure</span> $transform <span style="color:#f92672">=</span> <span style="color:#a6e22e">trim</span>(<span style="color:#f92672">...</span>),
</span></span><span style="display:flex;"><span>    ) {}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>C&rsquo;est la pièce manquante qui rend les attributs vraiment expressifs pour les règles de validation et de transformation. Avant 8.5, il fallait passer des noms de classes ou des références string aux attributs et laisser le framework les retrouver. Maintenant le callable vit directement dans l&rsquo;attribut.</p>
<h2 id="les-attributs-sur-les-constantes">Les attributs sur les constantes</h2>
<p>L&rsquo;attribut <code>#[\Deprecated]</code> de 8.4 ne pouvait pas être appliqué aux déclarations <code>const</code>. 8.5 ajoute le support des attributs pour les constantes en général :</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">const</span> <span style="color:#66d9ef">OLD_LIMIT</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">100</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#[\Deprecated(&#39;Use RATE_LIMIT instead&#39;, since: &#39;3.0&#39;)]
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#66d9ef">API_TIMEOUT</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">30</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#66d9ef">RATE_LIMIT</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">60</span>;
</span></span></code></pre></div><p><code>ReflectionConstant</code>, une nouvelle classe de réflexion dans 8.5, expose <code>getAttributes()</code> pour que les outils puissent les lire. Combiné avec les closures dans les expressions constantes, les attributs sur les constantes deviennent une vraie couche de métadonnées pour les valeurs à la compilation.</p>
<h2 id="override-sétend-aux-propriétés">#[\Override] s&rsquo;étend aux propriétés</h2>
<p>PHP 8.3 a apporté <code>#[\Override]</code> pour les méthodes. 8.5 l&rsquo;étend aux propriétés :</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">Base</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#a6e22e">string</span> $name <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;default&#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">class</span> <span style="color:#a6e22e">Derived</span> <span style="color:#66d9ef">extends</span> <span style="color:#a6e22e">Base</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">#[\Override]
</span></span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#a6e22e">string</span> $name <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;derived&#39;</span>;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Si la propriété n&rsquo;existe pas dans le parent, PHP lève une erreur. Particulièrement utile avec les property hooks de 8.4 : on peut maintenant signaler qu&rsquo;une propriété hookée surcharge intentionnellement celle d&rsquo;un parent.</p>
<h2 id="la-visibilité-asymétrique-statique">La visibilité asymétrique statique</h2>
<p>8.4 a introduit la visibilité asymétrique (<code>public private(set)</code>) pour les propriétés d&rsquo;instance. 8.5 l&rsquo;apporte aussi aux propriétés <code>static</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">class</span> <span style="color:#a6e22e">Registry</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">static</span> <span style="color:#66d9ef">private</span>(<span style="color:#a6e22e">set</span>) <span style="color:#66d9ef">array</span> $items <span style="color:#f92672">=</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">static</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">register</span>(<span style="color:#a6e22e">string</span> $key, <span style="color:#a6e22e">mixed</span> $value)<span style="color:#f92672">:</span> <span style="color:#a6e22e">void</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">self</span><span style="color:#f92672">::</span>$items[$key] <span style="color:#f92672">=</span> $value;
</span></span><span style="display:flex;"><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">echo</span> <span style="color:#a6e22e">Registry</span><span style="color:#f92672">::</span>$items[<span style="color:#e6db74">&#39;foo&#39;</span>]; <span style="color:#75715e">// lisible
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">Registry</span><span style="color:#f92672">::</span>$items[<span style="color:#e6db74">&#39;bar&#39;</span>] <span style="color:#f92672">=</span> <span style="color:#ae81ff">1</span>; <span style="color:#75715e">// Error: cannot write outside class
</span></span></span></code></pre></div><p>Pattern direct : exposer une collection statique en lecture, bloquer la mutation externe.</p>
<h2 id="la-promotion-de-constructeur-pour-les-propriétés-final">La promotion de constructeur pour les propriétés final</h2>
<p>La promotion de propriétés dans les constructeurs existe depuis PHP 8.0. Le modificateur <code>final</code> sur les propriétés promuées était la pièce manquante, 8.5 l&rsquo;ajoute :</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">ValueObject</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:#66d9ef">final</span> <span style="color:#a6e22e">readonly</span> <span style="color:#a6e22e">string</span> $id,
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">final</span> <span style="color:#a6e22e">readonly</span> <span style="color:#a6e22e">string</span> $name,
</span></span><span style="display:flex;"><span>    ) {}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Une sous-classe ne peut pas surcharger <code>$id</code> ni <code>$name</code> avec une propriété du même nom. La combinaison <code>final readonly</code> sur les propriétés promuées rend les objets-valeur aussi verrouillés que possible sans sceller la classe entière.</p>
<h2 id="les-casts-dans-les-expressions-constantes">Les casts dans les expressions constantes</h2>
<p>Autre lacune dans les expressions constantes : pas de casts de type. 8.5 les permet :</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">const</span> <span style="color:#66d9ef">PRECISION</span> <span style="color:#f92672">=</span> (<span style="color:#a6e22e">int</span>) <span style="color:#ae81ff">3.7</span>;      <span style="color:#75715e">// 3
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#66d9ef">THRESHOLD</span> <span style="color:#f92672">=</span> (<span style="color:#a6e22e">float</span>) <span style="color:#e6db74">&#39;1.5&#39;</span>;  <span style="color:#75715e">// 1.5
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#66d9ef">FLAG</span> <span style="color:#f92672">=</span> (<span style="color:#a6e22e">bool</span>) <span style="color:#ae81ff">1</span>;            <span style="color:#75715e">// true
</span></span></span></code></pre></div><p>Ça semble mineur jusqu&rsquo;à ce qu&rsquo;on ait des constantes de configuration dérivées de variables d&rsquo;environnement qui nécessitent une coercition de type directement à la déclaration.</p>
<h2 id="les-erreurs-fatales-incluent-les-backtraces">Les erreurs fatales incluent les backtraces</h2>
<p>Avant 8.5, une erreur fatale (mémoire insuffisante, dépassement de pile, erreur de type dans certains contextes) produisait un message sans contexte sur où dans le code ça s&rsquo;est passé. Trouver la cause signifiait insérer des logs de debug et reproduire.</p>
<p>8.5 ajoute des backtraces à la stack aux messages d&rsquo;erreur fatale, dans le même format que les backtraces d&rsquo;exceptions. Une nouvelle directive INI, <code>fatal_error_backtraces</code>, contrôle le comportement. Elle est activée par défaut.</p>
<h2 id="array_first-et-array_last">array_first() et array_last()</h2>
<p>PHP avait <code>reset()</code> et <code>end()</code> pour accéder au premier et dernier élément d&rsquo;un tableau depuis PHP 3. Les deux mutent le pointeur interne du tableau (pas sûr à appeler sur une référence), et ils retournent <code>false</code> pour les tableaux vides d&rsquo;une façon indiscernable d&rsquo;une valeur <code>false</code> stockée.</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>$values <span style="color:#f92672">=</span> [<span style="color:#ae81ff">10</span>, <span style="color:#ae81ff">20</span>, <span style="color:#ae81ff">30</span>];
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>$first <span style="color:#f92672">=</span> <span style="color:#a6e22e">array_first</span>($values);  <span style="color:#75715e">// 10
</span></span></span><span style="display:flex;"><span>$last  <span style="color:#f92672">=</span> <span style="color:#a6e22e">array_last</span>($values);   <span style="color:#75715e">// 30
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>$first <span style="color:#f92672">=</span> <span style="color:#a6e22e">array_first</span>([]);       <span style="color:#75715e">// null
</span></span></span></code></pre></div><p>Les nouvelles fonctions retournent <code>null</code> pour les tableaux vides, ne touchent pas le pointeur interne, et fonctionnent sur n&rsquo;importe quelle expression de tableau sans avoir besoin d&rsquo;une variable. <code>reset($this-&gt;getItems())</code> était un avertissement de dépréciation en attente de se produire.</p>
<h2 id="get_error_handler-et-get_exception_handler">get_error_handler() et get_exception_handler()</h2>
<p>PHP a <code>set_error_handler()</code> et <code>set_exception_handler()</code>. Obtenir le handler courant nécessitait soit de le stocker soi-même avant de le définir, soit d&rsquo;appeler <code>set_error_handler(null)</code> et de capturer ce qui revenait, ce qui effaçait aussi le handler au passage.</p>
<p>8.5 ajoute :</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>$current <span style="color:#f92672">=</span> <span style="color:#a6e22e">get_error_handler</span>();
</span></span><span style="display:flex;"><span>$current <span style="color:#f92672">=</span> <span style="color:#a6e22e">get_exception_handler</span>();
</span></span></code></pre></div><p>Pratique dans les chaînes de middleware où on veut envelopper le handler existant sans le perdre, ou dans les tests où on veut vérifier qu&rsquo;un handler a bien été installé.</p>
<h2 id="intllistformatter">IntlListFormatter</h2>
<p>Formater une liste avec les conjonctions appropriées à la locale nécessitait toujours un assemblage manuel de strings. 8.5 ajoute <code>IntlListFormatter</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>$formatter <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">IntlListFormatter</span>(<span style="color:#e6db74">&#39;en_US&#39;</span>, <span style="color:#a6e22e">IntlListFormatter</span><span style="color:#f92672">::</span><span style="color:#a6e22e">TYPE_AND</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> $formatter<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">format</span>([<span style="color:#e6db74">&#39;apples&#39;</span>, <span style="color:#e6db74">&#39;oranges&#39;</span>, <span style="color:#e6db74">&#39;pears&#39;</span>]);
</span></span><span style="display:flex;"><span><span style="color:#75715e">// &#34;apples, oranges, and pears&#34;
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>$formatter <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">IntlListFormatter</span>(<span style="color:#e6db74">&#39;fr_FR&#39;</span>, <span style="color:#a6e22e">IntlListFormatter</span><span style="color:#f92672">::</span><span style="color:#a6e22e">TYPE_OR</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> $formatter<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">format</span>([<span style="color:#e6db74">&#39;rouge&#39;</span>, <span style="color:#e6db74">&#39;bleu&#39;</span>, <span style="color:#e6db74">&#39;vert&#39;</span>]);
</span></span><span style="display:flex;"><span><span style="color:#75715e">// &#34;rouge, bleu ou vert&#34;
</span></span></span></code></pre></div><p>La classe enveloppe le <code>ListFormatter</code> d&rsquo;ICU. Trois types : <code>TYPE_AND</code>, <code>TYPE_OR</code>, <code>TYPE_UNITS</code>. Les constantes de largeur contrôlent si on obtient &ldquo;et&rdquo; ou &ldquo;&amp;&rdquo;. Gestion de la virgule d&rsquo;Oxford, placement de conjonction spécifique à la locale, tout géré par ICU.</p>
<h2 id="filter_throw_on_failure-pour-filter_var">FILTER_THROW_ON_FAILURE pour filter_var()</h2>
<p><code>filter_var()</code> retourne <code>false</code> en cas d&rsquo;échec de validation, ce qui produit l&rsquo;ambiguïté classique <code>false vs null vs 0</code> quand on filtre des entrées non fiables. Un nouveau flag change ça :</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">try</span> {
</span></span><span style="display:flex;"><span>    $email <span style="color:#f92672">=</span> <span style="color:#a6e22e">filter_var</span>($input, <span style="color:#a6e22e">FILTER_VALIDATE_EMAIL</span>, <span style="color:#a6e22e">FILTER_THROW_ON_FAILURE</span>);
</span></span><span style="display:flex;"><span>} <span style="color:#66d9ef">catch</span> (<span style="color:#a6e22e">Filter\FilterFailedException</span> $e) {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// explicitement invalide, pas faussement false
</span></span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Les classes <code>Filter\FilterFailedException</code> et <code>Filter\FilterException</code> sont nouvelles dans 8.5. Le flag ne peut pas être combiné avec <code>FILTER_NULL_ON_FAILURE</code> : les comportements sont mutuellement exclusifs.</p>
<h2 id="les-dépréciations-qui-nettoient-des-années-de-dette-technique">Les dépréciations qui nettoient des années de dette technique</h2>
<p>L&rsquo;opérateur backtick (<code>`commande`</code> comme alias de <code>shell_exec()</code>) est déprécié. C&rsquo;est une syntaxe obscure qui surprend quiconque lit le code et est incohérente avec tous les autres appels de fonctions PHP.</p>
<p>Les noms de cast non canoniques (<code>(boolean)</code>, <code>(integer)</code>, <code>(double)</code>, <code>(binary)</code>) sont dépréciés au profit de leurs formes courtes : <code>(bool)</code>, <code>(int)</code>, <code>(float)</code>, <code>(string)</code>. Les formes longues sont non documentées depuis des années ; 8.5 commence la suppression formelle.</p>
<p>Les instructions <code>case</code> terminées par un point-virgule sont dépréciées :</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">// déprécié
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">switch</span> ($x) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">case</span> <span style="color:#ae81ff">1</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">break</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:#75715e">// correct
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">switch</span> ($x) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">case</span> <span style="color:#ae81ff">1</span><span style="color:#f92672">:</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">break</span>;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>La forme avec point-virgule est syntaxiquement valide depuis PHP 4 mais personne ne l&rsquo;utilise intentionnellement. C&rsquo;est une faute de frappe que PHP acceptait.</p>
<p><code>__sleep()</code> et <code>__wakeup()</code> sont dépréciés au profit de <code>__serialize()</code> et <code>__unserialize()</code>, qui retournent et reçoivent des tableaux et se composent correctement avec l&rsquo;héritage. Les anciennes méthodes avaient une sémantique confuse autour de la visibilité des propriétés.</p>
<h2 id="max_memory_limit-plafonne-les-allocations-incontrôlées">max_memory_limit plafonne les allocations incontrôlées</h2>
<p>Une nouvelle directive INI accessible seulement au démarrage : <code>max_memory_limit</code>. Elle définit un plafond que <code>memory_limit</code> ne peut pas dépasser à l&rsquo;exécution. Si un script appelle <code>ini_set('memory_limit', '10G')</code> et que <code>max_memory_limit</code> est à <code>512M</code>, PHP avertit et plafonne la valeur.</p>
<p>Utile dans les environnements d&rsquo;hébergement partagé, ou partout où on veut s&rsquo;assurer qu&rsquo;un bug ou un payload malveillant ne peut pas convaincre PHP d&rsquo;élever sa propre limite et de dévorer toute la RAM de la machine.</p>
<h2 id="opcache-est-toujours-présent">Opcache est toujours présent</h2>
<p>Dans 8.5, Opcache est toujours compilé dans le binaire PHP et toujours chargé. L&rsquo;ancienne situation (Opcache comme extension chargeable qui pouvait ou non être présente selon la configuration de build) est révolue.</p>
<p>On peut toujours le désactiver : <code>opcache.enable=0</code> fonctionne bien. Ce qui change, c&rsquo;est la garantie que l&rsquo;API Opcache (<code>opcache_get_status()</code>, <code>opcache_invalidate()</code>, etc.) est toujours disponible, quelle que soit la façon dont PHP a été compilé. Tout code qui vérifie <code>extension_loaded('opcache')</code> avant d&rsquo;appeler les fonctions Opcache peut supprimer la vérification.</p>
]]></content:encoded></item><item><title>API Platform 4.2 : JSON streamer, ObjectMapper, et autoconfigure</title><link>https://guillaumedelre.github.io/fr/2025/09/18/api-platform-4.2-json-streamer-objectmapper-et-autoconfigure/</link><pubDate>Thu, 18 Sep 2025 00:00:00 +0000</pubDate><guid>https://guillaumedelre.github.io/fr/2025/09/18/api-platform-4.2-json-streamer-objectmapper-et-autoconfigure/</guid><description>Part 8 of 8 in &amp;quot;Sorties API Platform&amp;quot;: API Platform 4.2 diffuse les grandes collections JSON sans buffering, remplace le câblage DTO manuel par ObjectMapper, et autoconfigure les ressources depuis leurs attributs de classe.</description><category>api-platform-releases</category><content:encoded><![CDATA[<p>API Platform 4.2 est arrivé en septembre 2025. Trois changements se distinguent : un JSON streamer pour les grandes collections qui évite de bufferiser toute la réponse en mémoire, un ObjectMapper qui remplace le câblage manuel dans les flux DTO basés sur <code>stateOptions</code>, et l&rsquo;autoconfiguration de <code>#[ApiResource]</code> sans enregistrement de service explicite.</p>
<h2 id="json-streamer-pour-les-grandes-collections">JSON streamer pour les grandes collections</h2>
<p>Le serializer Symfony par défaut construit la réponse complète en mémoire avant de l&rsquo;écrire dans la sortie. Pour une collection de 10 000 éléments, cela signifie allouer un tableau PHP, le sérialiser en string, et garder les deux en mémoire jusqu&rsquo;au flush de la réponse. À grande échelle, c&rsquo;est la source des erreurs OOM qui forcent à ajouter de la pagination partout.</p>
<p>La 4.2 ajoute un encodeur JSON en streaming qui écrit la réponse de façon incrémentale :</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">api_platform</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">serializer</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">enable_json_streamer</span>: <span style="color:#66d9ef">true</span>
</span></span></code></pre></div><p>Avec le streaming activé, la réponse est écrite directement dans le buffer de sortie au fur et à mesure que chaque élément est sérialisé. L&rsquo;utilisation mémoire reste approximativement constante quelle que soit la taille de la collection. La contrepartie : on ne peut pas définir de headers de réponse après le début du streaming, et le code de statut HTTP doit être validé avant que le premier octet soit écrit.</p>
<h2 id="objectmapper-remplace-le-câblage-dto-manuel">ObjectMapper remplace le câblage DTO manuel</h2>
<p>La 3.1 avait introduit <code>stateOptions</code> avec <code>DoctrineOrmOptions</code> pour séparer la ressource API de l&rsquo;entité Doctrine. Le provider recevait des objets entité et le serializer les mappait sur le DTO. Ça fonctionnait, mais le mapping était implicite — le serializer utilisait les noms de propriétés pour faire correspondre les champs, et tout ce qui ne correspondait pas était soit ignoré soit causait une erreur de normalisation.</p>
<p>La 4.2 introduit <code>ObjectMapper</code>, une couche de mapping déclarative entre entités et DTOs :</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\ObjectMapper\Attribute\Map</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#[Map(source: BookEntity::class)]
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">BookDto</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#a6e22e">string</span> $title;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#a6e22e">string</span> $authorName;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>L&rsquo;attribut <code>#[Map]</code> indique à ObjectMapper que <code>BookDto</code> peut être peuplé depuis <code>BookEntity</code>. Les noms de champs sont mis en correspondance par convention ; les inadéquations sont déclarées explicitement au niveau de chaque proprié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">use</span> <span style="color:#a6e22e">Symfony\Component\ObjectMapper\Attribute\Map</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#[Map(source: BookEntity::class)]
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">BookDto</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">#[Map(source: &#39;author.fullName&#39;)]
</span></span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#a6e22e">string</span> $authorName;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>La notation pointée traverse les objets imbriqués. Le mapping s&rsquo;exécute avant la sérialisation et remplace le comportement implicite de correspondance de propriétés du serializer. Les champs non mappés lèvent une erreur au moment de la configuration, pas à l&rsquo;exécution.</p>
<p>ObjectMapper fonctionne avec <code>stateOptions</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">ApiPlatform\Doctrine\Orm\State\Options</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">ApiPlatform\Metadata\ApiResource</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">ApiPlatform\Metadata\GetCollection</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">Symfony\Component\ObjectMapper\Attribute\Map</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#[ApiResource(
</span></span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">operations</span><span style="color:#f92672">:</span> [
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">GetCollection</span>(
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">stateOptions</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Options</span>(<span style="color:#a6e22e">entityClass</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">BookEntity</span><span style="color:#f92672">::</span><span style="color:#a6e22e">class</span>),
</span></span><span style="display:flex;"><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:#75715e">#[Map(source: BookEntity::class)]
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">BookDto</span> {}
</span></span></code></pre></div><p>Le provider récupère les objets <code>BookEntity</code> depuis Doctrine. ObjectMapper les convertit en instances <code>BookDto</code>. Le serializer écrit le DTO. Trois étapes distinctes, chacune avec un contrat clair.</p>
<h2 id="intégration-typeinfo-dans-toute-la-pile">Intégration TypeInfo dans toute la pile</h2>
<p>Symfony 7.1 a introduit le <a href="https://symfony.com/doc/current/components/type_info.html" target="_blank" rel="noopener noreferrer">composant TypeInfo</a>
, une couche unifiée d&rsquo;introspection des types qui comprend les types union, intersection, les collections génériques et les types nullable à travers la reflection, PHPDoc et la syntaxe PHP 8.x.</p>
<p>La 4.2 remplace la résolution de types interne d&rsquo;API Platform par TypeInfo. Cela affecte la génération de schémas de filtres, l&rsquo;inférence de schéma OpenAPI, et la coercition de types du serializer. Le bénéfice visible est que les types qui généraient auparavant des schémas OpenAPI incorrects ou manquants — <code>Collection&lt;int, Book&gt;</code>, <code>list&lt;string&gt;</code>, types intersection — produisent maintenant des schémas précis sans annotations <code>@ApiProperty</code> manuelles.</p>
<h2 id="autoconfigure-apiresource">Autoconfigure <code>#[ApiResource]</code></h2>
<p>Avant la 4.2, ajouter <code>#[ApiResource]</code> à une classe suffisait pour qu&rsquo;Hugo la découvre uniquement si la classe était dans un chemin scanné par le chargeur de ressources d&rsquo;API Platform. En dehors de ce chemin, une configuration de service explicite était nécessaire.</p>
<p>La 4.2 se branche sur le système d&rsquo;autoconfigure de Symfony. Toute classe taguée avec <code>#[ApiResource]</code> est automatiquement enregistrée comme ressource indépendamment de son emplacement, tant qu&rsquo;elle est dans un répertoire couvert par le scan de composants de Symfony. Aucune entrée dans <code>config/services.yaml</code> nécessaire.</p>
<p>Pour Laravel, l&rsquo;équivalent utilise l&rsquo;autoloading du service provider de Laravel — les modèles Eloquent avec <code>#[ApiResource]</code> sont récupérés automatiquement sans enregistrement manuel.</p>
<h2 id="doctrine-existsfilter">Doctrine ExistsFilter</h2>
<p>L&rsquo;<code>ExistsFilter</code> contraint une collection selon qu&rsquo;une relation ou un champ nullable est défini :</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">#[ApiFilter(ExistsFilter::class, properties: [&#39;publishedAt&#39;])]
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Book</span> {}
</span></span></code></pre></div><p><code>GET /books?exists[publishedAt]=true</code> retourne les livres où <code>publishedAt</code> n&rsquo;est pas null. <code>exists[publishedAt]=false</code> retourne les livres où il l&rsquo;est.</p>
]]></content:encoded></item><item><title>API Platform 4.1 : paramètres de requête stricts, OpenAPI multi-spec, et limites GraphQL</title><link>https://guillaumedelre.github.io/fr/2025/02/28/api-platform-4.1-param%C3%A8tres-de-requ%C3%AAte-stricts-openapi-multi-spec-et-limites-graphql/</link><pubDate>Fri, 28 Feb 2025 00:00:00 +0000</pubDate><guid>https://guillaumedelre.github.io/fr/2025/02/28/api-platform-4.1-param%C3%A8tres-de-requ%C3%AAte-stricts-openapi-multi-spec-et-limites-graphql/</guid><description>Part 7 of 8 in &amp;quot;Sorties API Platform&amp;quot;: API Platform 4.1 formalise la validation stricte des paramètres de requête, introduit x-apiplatform-tag pour découper une API en plusieurs specs OpenAPI, et ajoute des limites de profondeur et complexité pour GraphQL.</description><category>api-platform-releases</category><content:encoded><![CDATA[<p>API Platform 4.1 est arrivé en février 2025 avec un lot de fonctionnalités moins axées sur de nouvelles capacités que sur la mise en production des existantes. La validation stricte des paramètres de requête gagne une propriété de première classe. OpenAPI gagne un mécanisme pour découper les grandes APIs en specs séparées. GraphQL obtient les contrôles de prévention des abus qui lui manquaient.</p>
<h2 id="validation-stricte-des-paramètres-de-requête">Validation stricte des paramètres de requête</h2>
<p>La 3.3 avait introduit la validation des paramètres de requête en opt-in. La 3.4 avait déprécié le comportement permissif. La 4.1 la formalise avec une propriété native <code>strictQueryParameterValidation</code> sur les ressources et opérations : quand elle est à <code>true</code>, les paramètres de requête inconnus renvoient 400.</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">ApiPlatform\Metadata\GetCollection</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">ApiPlatform\Metadata\QueryParameter</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#[GetCollection(
</span></span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">strictQueryParameterValidation</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">parameters</span><span style="color:#f92672">:</span> [
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">QueryParameter</span>(<span style="color:#a6e22e">key</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;utm_source&#39;</span>, <span style="color:#a6e22e">required</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">false</span>, <span style="color:#a6e22e">schema</span><span style="color:#f92672">:</span> [<span style="color:#e6db74">&#39;type&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#39;string&#39;</span>]),
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">QueryParameter</span>(<span style="color:#a6e22e">key</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;feature_flag&#39;</span>, <span style="color:#a6e22e">required</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">false</span>, <span style="color:#a6e22e">schema</span><span style="color:#f92672">:</span> [<span style="color:#e6db74">&#39;type&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#39;string&#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">class</span> <span style="color:#a6e22e">Book</span> {}
</span></span></code></pre></div><p>Les paramètres déclarés passent ; les paramètres non déclarés sont rejetés. Pour désactiver la validation stricte sur une opération spécifique quand elle est activée au niveau de la ressource, passer <code>strictQueryParameterValidation: false</code> sur cette opération.</p>
<h2 id="x-apiplatform-tag-pour-openapi-multi-spec"><code>x-apiplatform-tag</code> pour OpenAPI multi-spec</h2>
<p>Les grandes APIs ont souvent besoin de plusieurs specs OpenAPI : une par équipe, une par version d&rsquo;API, une interne et une publique. Avant la 4.1, la spec générée était un seul document, et la diviser nécessitait un post-traitement ou des instances API Platform séparées.</p>
<p>La 4.1 ajoute une extension vendor <code>x-apiplatform-tag</code> (sans <code>s</code> final). On tague les opérations avec des noms de groupes logiques via les <code>extensionProperties</code> d&rsquo;un objet <code>Operation</code> OpenAPI, puis on demande la spec filtrée sur un ou plusieurs groupes :</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">ApiPlatform\Metadata\GetCollection</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">ApiPlatform\OpenApi\Factory\OpenApiFactory</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">ApiPlatform\OpenApi\Model\Operation</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#[GetCollection(
</span></span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">openapi</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Operation</span>(
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">extensionProperties</span><span style="color:#f92672">:</span> [<span style="color:#a6e22e">OpenApiFactory</span><span style="color:#f92672">::</span><span style="color:#a6e22e">API_PLATFORM_TAG</span> <span style="color:#f92672">=&gt;</span> [<span style="color:#e6db74">&#39;public&#39;</span>, <span style="color:#e6db74">&#39;v2&#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">class</span> <span style="color:#a6e22e">Book</span> {}
</span></span></code></pre></div><p>Demander <code>/api/docs.json?filter_tags[]=public</code> retourne uniquement les opérations taguées <code>public</code>. La spec complète reste disponible sans filtre. Les groupes n&rsquo;affectent pas le comportement réel de l&rsquo;API — c&rsquo;est uniquement une préoccupation de couche documentation.</p>
<p>Cela rend faisable de maintenir une configuration API Platform unique tout en servant différentes vues de spec à différents consommateurs : un Swagger UI public, un portail partenaire, et un outil interne qui expose les endpoints d&rsquo;administration.</p>
<h2 id="authentification-http-dans-swagger-ui">Authentification HTTP dans Swagger UI</h2>
<p>Avant la 4.1, le Swagger UI fourni avec API Platform supportait l&rsquo;authentification par token Bearer via son dialogue &ldquo;Authorize&rdquo;. L&rsquo;authentification par clé API et HTTP Basic n&rsquo;étaient pas câblées.</p>
<p>La 4.1 ajoute le support de plusieurs schémas de sécurité dans le document OpenAPI généré. Les schémas de sécurité sont ajoutés en décorant l&rsquo;<code>OpenApiFactory</code> et en modifiant l&rsquo;objet <code>components.securitySchemes</code> de la spec. Chaque schéma déclaré apparaît ensuite dans le dialogue &ldquo;Authorize&rdquo; de Swagger UI et est appliqué aux requêtes faites depuis l&rsquo;UI. C&rsquo;est une amélioration de la documentation et de l&rsquo;expérience développeur — la logique d&rsquo;authentification réelle dans votre application n&rsquo;est pas affectée.</p>
<h2 id="limites-de-profondeur-et-complexité-des-requêtes-graphql">Limites de profondeur et complexité des requêtes GraphQL</h2>
<p>La structure de requête récursive de GraphQL rend triviale la construction d&rsquo;une requête petite en octets mais énorme en coût d&rsquo;exécution. Sans limites, une requête imbriquée sur quatre niveaux à travers une relation many-to-many peut frapper la base de données des centaines de fois.</p>
<p>La 4.1 ajoute des limites configurables de profondeur et complexité :</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">api_platform</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">graphql</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">max_query_depth</span>: <span style="color:#ae81ff">10</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">max_query_complexity</span>: <span style="color:#ae81ff">100</span>
</span></span></code></pre></div><p><code>max_query_depth</code> est le niveau d&rsquo;imbrication maximum. <code>max_query_complexity</code> assigne un coût à chaque champ et rejette les requêtes dont le coût total dépasse le seuil. Les requêtes qui dépassent l&rsquo;une ou l&rsquo;autre limite sont rejetées avant exécution avec une réponse 400.</p>
<p>Il n&rsquo;y a pas de valeur universellement correcte pour ces limites — elles dépendent de la forme de votre schéma et des patterns de requête attendus. Les valeurs par défaut sont intentionnellement permissives pour éviter de casser des APIs existantes lors de la mise à jour. Les resserrer est un choix de configuration délibéré.</p>
<h2 id="formats-de-sortie-au-niveau-opération">Formats de sortie au niveau opération</h2>
<p>La 4.0 et les versions antérieures configuraient les types de contenu acceptés et retournés au niveau de l&rsquo;API. La 4.1 permet de les restreindre par opération :</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">ApiPlatform\Metadata\GetCollection</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#[GetCollection(
</span></span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">outputFormats</span><span style="color:#f92672">:</span> [<span style="color:#e6db74">&#39;jsonld&#39;</span> <span style="color:#f92672">=&gt;</span> [<span style="color:#e6db74">&#39;application/ld+json&#39;</span>]],
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">inputFormats</span><span style="color:#f92672">:</span> [<span style="color:#e6db74">&#39;json&#39;</span> <span style="color:#f92672">=&gt;</span> [<span style="color:#e6db74">&#39;application/json&#39;</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">Book</span> {}
</span></span></code></pre></div><p>Les opérations qui ne spécifient pas de formats héritent de la configuration au niveau de l&rsquo;API. C&rsquo;est utile pour les endpoints qui doivent retourner un format spécifique (une export CSV, un flux binaire) sans changer les défauts pour le reste de l&rsquo;API.</p>
]]></content:encoded></item><item><title>La recherche full-text PostgreSQL avec Doctrine, sans une ligne de SQL brut</title><link>https://guillaumedelre.github.io/fr/2025/02/10/la-recherche-full-text-postgresql-avec-doctrine-sans-une-ligne-de-sql-brut/</link><pubDate>Mon, 10 Feb 2025 00:00:00 +0000</pubDate><guid>https://guillaumedelre.github.io/fr/2025/02/10/la-recherche-full-text-postgresql-avec-doctrine-sans-une-ligne-de-sql-brut/</guid><description>Comment nous avons superposé des types DBAL personnalisés et des wrappers DQL sur postgresql-for-doctrine pour intégrer la recherche full-text PostgreSQL dans un projet Symfony API Platform.</description><content:encoded><![CDATA[<p>Le champ de recherche de la médiathèque renvoyait des résultats en 800 millisecondes en staging. En production, il y avait quarante fois plus de lignes. Le plan d&rsquo;exécution révélait un sequential scan: aucun index sollicité, aucune façon d&rsquo;y remédier avec un B-tree classique. L&rsquo;équipe produit voulait aussi une recherche multi-mots: taper &ldquo;interview président&rdquo;, obtenir des résultats contenant les deux termes. Une requête <code>LIKE</code> avec des wildcards n&rsquo;a pas de manière propre d&rsquo;exprimer ça sans conditions indépendantes multiples, chacune nécessitant son propre scan.</p>
<p>PostgreSQL embarque une recherche full-text depuis plus de quinze ans. La plateforme tournait déjà sous PostgreSQL. Le hic: le projet utilise Doctrine ORM, et Doctrine ne sait pas nativement ce qu&rsquo;est un <code>tsvector</code>.</p>
<p>Une bibliothèque communautaire, <a href="https://github.com/martin-georgiev/postgresql-for-doctrine" target="_blank" rel="noopener noreferrer">postgresql-for-doctrine</a>, couvre une partie de cette lacune. Elle enregistre des fonctions DQL basiques comme <code>TO_TSQUERY</code>, <code>TO_TSVECTOR</code>, et l&rsquo;opérateur de correspondance <code>@@</code> en tant que pièces atomiques séparées. La fondation était là. Trois choses restaient à construire par-dessus.</p>
<h2 id="le-type-que-doctrine-na-jamais-vu">Le type que Doctrine n&rsquo;a jamais vu</h2>
<p><a href="https://www.postgresql.org/docs/current/datatype-textsearch.html" target="_blank" rel="noopener noreferrer">La recherche full-text de PostgreSQL</a> repose sur deux types: <code>tsvector</code> (une liste pré-traitée de tokens normalisés) et <code>tsquery</code> (une expression de recherche). On maintient une colonne <code>tsvector</code>, on l&rsquo;indexe avec GIN, et on interroge avec l&rsquo;opérateur <code>@@</code>.</p>
<p>Le DBAL de Doctrine ne livre aucun type <code>tsvector</code>. Déclarer <code>#[ORM\Column(type: 'tsvector')]</code> sans l&rsquo;enregistrer au préalable lève une <code>UnknownColumnTypeException</code>. La solution: un type DBAL personnalisé:</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">TsVector</span> <span style="color:#66d9ef">extends</span> <span style="color:#a6e22e">Type</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">final</span> <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">const</span> <span style="color:#66d9ef">string</span> <span style="color:#a6e22e">DBAL_TYPE</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;tsvector&#39;</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">getSQLDeclaration</span>(<span style="color:#66d9ef">array</span> $column, <span style="color:#a6e22e">AbstractPlatform</span> $platform)<span style="color:#f92672">:</span> <span style="color:#a6e22e">string</span>
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">self</span><span style="color:#f92672">::</span><span style="color:#a6e22e">DBAL_TYPE</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">getName</span>()<span style="color:#f92672">:</span> <span style="color:#a6e22e">string</span>
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">self</span><span style="color:#f92672">::</span><span style="color:#a6e22e">DBAL_TYPE</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">convertToDatabaseValueSQL</span>(<span style="color:#a6e22e">string</span> $sqlExpr, <span style="color:#a6e22e">AbstractPlatform</span> $platform)<span style="color:#f92672">:</span> <span style="color:#a6e22e">string</span>
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">sprintf</span>(<span style="color:#e6db74">&#34;to_tsvector(&#39;simple&#39;, %s)&#34;</span>, $sqlExpr);
</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">convertToDatabaseValue</span>(<span style="color:#a6e22e">mixed</span> $value, <span style="color:#a6e22e">AbstractPlatform</span> $platform)<span style="color:#f92672">:</span> <span style="color:#a6e22e">mixed</span>
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">is_array</span>($value) <span style="color:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">isset</span>($value[<span style="color:#e6db74">&#39;data&#39;</span>])) {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> $value[<span style="color:#e6db74">&#39;data&#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">return</span> <span style="color:#a6e22e">is_string</span>($value) <span style="color:#f92672">?</span> $value <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><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">getMappedDatabaseTypes</span>(<span style="color:#a6e22e">AbstractPlatform</span> $platform)<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 style="color:#a6e22e">self</span><span style="color:#f92672">::</span><span style="color:#a6e22e">DBAL_TYPE</span>];
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>La méthode intéressante est <code>convertToDatabaseValueSQL()</code>. Doctrine l&rsquo;appelle pour envelopper le placeholder SQL avant que la valeur n&rsquo;atteigne la base de données. La valeur écrite devient automatiquement <code>to_tsvector('simple', ?)</code> à la frontière DBAL, sans étape supplémentaire côté appelant.</p>
<p>On enregistre le type dans <code>doctrine.yaml</code>, puis on mappe la colonne sur l&rsquo;entité:</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">doctrine</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">dbal</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">types</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">tsvector</span>: <span style="color:#ae81ff">App\Doctrine\DBAL\Types\TsVector</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:#75715e">#[ORM\Column(type: &#39;tsvector&#39;, nullable: true)]
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">protected</span> <span style="color:#f92672">?</span><span style="color:#a6e22e">string</span> $textSearch <span style="color:#f92672">=</span> <span style="color:#66d9ef">null</span>;
</span></span></code></pre></div><p>Côté PHP, la valeur est une simple chaîne. La conversion en vrai <code>tsvector</code> se fait invisiblement au niveau DBAL.</p>
<p>Nous avons utilisé le dictionnaire <code>'simple'</code>, qui tokenise sur les espaces et la ponctuation sans stemming spécifique à une langue. La plateforme gère plusieurs langues, et les règles de stemming français auraient cassé l&rsquo;espagnol. Simple suffit largement pour la phonétique.</p>
<h2 id="garder-la-colonne-à-jour">Garder la colonne à jour</h2>
<p>Une colonne <code>tsvector</code> est une donnée dérivée: elle doit rester synchronisée avec les champs source chaque fois que l&rsquo;entité change. Un event listener Doctrine s&rsquo;en charge:</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">#[AsDoctrineListener(event: Events::prePersist)]
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#[AsDoctrineListener(event: Events::preUpdate)]
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">MediaTsVectorSubscriber</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">prePersist</span>(<span style="color:#a6e22e">PrePersistEventArgs</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">getObject</span>() <span style="color:#a6e22e">instanceof</span> <span style="color:#a6e22e">Media</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</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">updateTextSearch</span>($event<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">getObject</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">preUpdate</span>(<span style="color:#a6e22e">PreUpdateEventArgs</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">getObject</span>() <span style="color:#a6e22e">instanceof</span> <span style="color:#a6e22e">Media</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</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">updateTextSearch</span>($event<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">getObject</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">private</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">updateTextSearch</span>(<span style="color:#a6e22e">Media</span> $entity)<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>        $entity<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">setTextSearch</span>(
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">sprintf</span>(<span style="color:#e6db74">&#39;%s %s&#39;</span>, $entity<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">getTitle</span>(), $entity<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">getCaption</span>())
</span></span><span style="display:flex;"><span>        );
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Avant chaque persist et update, le subscriber concatène les champs qui doivent être recherchables dans <code>textSearch</code>. Doctrine flush la chaîne combinée, le type DBAL l&rsquo;enveloppe dans <code>to_tsvector('simple', ...)</code>, et PostgreSQL stocke la forme tokenisée.</p>
<p>Une subtilité: la valeur côté PHP est <code>&quot;title caption&quot;</code>, pas la sortie tsvector réelle. La base affiche <code>'caption' 'title'</code> (tokens triés), mais l&rsquo;entité contient une chaîne brute. C&rsquo;est attendu: la conversion est une responsabilité DBAL, pas PHP. Ça peut dérouter le débogage jusqu&rsquo;à ce qu&rsquo;on se souvienne où se situe la frontière.</p>
<h2 id="étendre-dql-avec-les-opérateurs-fts">Étendre DQL avec les opérateurs FTS</h2>
<p>Le DQL de Doctrine couvre les opérations SQL courantes, mais tout ce qui est spécifique à PostgreSQL est hors périmètre. C&rsquo;est là que <code>postgresql-for-doctrine</code> entre en jeu: il enregistre <code>TO_TSQUERY</code>, <code>TO_TSVECTOR</code>, et <code>TSMATCH</code> comme fonctions DQL individuelles. Écrire une requête full-text en DQL sans lui signifierait basculer en SQL natif.</p>
<p>Les fonctions de la bibliothèque sont atomiques, cependant. Chacune correspond à un appel SQL. Exprimer une vérification de correspondance complète en DQL ressemble à <code>TSMATCH(o.textSearch, TO_TSQUERY(:term))</code>. Assez lisible, mais l&rsquo;équipe voulait quelque chose de plus compact: une seule fonction DQL encodant à la fois l&rsquo;opérateur de correspondance et le type de requête, y compris <code>websearch_to_tsquery</code> que <code>postgresql-for-doctrine</code> ne fournissait pas.</p>
<p>La solution: des <a href="https://www.doctrine-project.org/projects/doctrine-orm/en/latest/cookbook/dql-user-defined-functions.html" target="_blank" rel="noopener noreferrer">fonctions DQL personnalisées</a> via <code>FunctionNode</code>. On parse la syntaxe DQL, puis on émet du SQL. Toutes les fonctions FTS partagent la même signature à deux arguments, donc une classe abstraite de base gère le parsing:</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">abstract</span> <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">TsFunction</span> <span style="color:#66d9ef">extends</span> <span style="color:#a6e22e">FunctionNode</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#a6e22e">PathExpression</span><span style="color:#f92672">|</span><span style="color:#a6e22e">Node</span><span style="color:#f92672">|</span><span style="color:#66d9ef">null</span> $ftsField <span style="color:#f92672">=</span> <span style="color:#66d9ef">null</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#a6e22e">PathExpression</span><span style="color:#f92672">|</span><span style="color:#a6e22e">Node</span><span style="color:#f92672">|</span><span style="color:#66d9ef">null</span> $queryString <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 style="color:#66d9ef">public</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">parse</span>(<span style="color:#a6e22e">Parser</span> $parser)<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>        $parser<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">match</span>(<span style="color:#a6e22e">TokenType</span><span style="color:#f92672">::</span><span style="color:#a6e22e">T_IDENTIFIER</span>);
</span></span><span style="display:flex;"><span>        $parser<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">match</span>(<span style="color:#a6e22e">TokenType</span><span style="color:#f92672">::</span><span style="color:#a6e22e">T_OPEN_PARENTHESIS</span>);
</span></span><span style="display:flex;"><span>        $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">ftsField</span> <span style="color:#f92672">=</span> $parser<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">StringPrimary</span>();
</span></span><span style="display:flex;"><span>        $parser<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">match</span>(<span style="color:#a6e22e">TokenType</span><span style="color:#f92672">::</span><span style="color:#a6e22e">T_COMMA</span>);
</span></span><span style="display:flex;"><span>        $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">queryString</span> <span style="color:#f92672">=</span> $parser<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">StringPrimary</span>();
</span></span><span style="display:flex;"><span>        $parser<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">match</span>(<span style="color:#a6e22e">TokenType</span><span style="color:#f92672">::</span><span style="color:#a6e22e">T_CLOSE_PARENTHESIS</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Chaque classe concrète implémente <code>getSql()</code> pour émettre son expression PostgreSQL:</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">// e.textSearch @@ websearch_to_tsquery(&#39;simple&#39;, :term)
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">TsWebsearchQueryFunction</span> <span style="color:#66d9ef">extends</span> <span style="color:#a6e22e">TsFunction</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">getSql</span>(<span style="color:#a6e22e">SqlWalker</span> $sqlWalker)<span style="color:#f92672">:</span> <span style="color:#a6e22e">string</span>
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">ftsField</span><span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">dispatch</span>($sqlWalker)
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">.</span><span style="color:#e6db74">&#34; @@ websearch_to_tsquery(&#39;simple&#39;, &#34;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">.</span>$this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">queryString</span><span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">dispatch</span>($sqlWalker)<span style="color:#f92672">.</span><span style="color:#e6db74">&#39;)&#39;</span>;
</span></span><span style="display:flex;"><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:#75715e">// ts_rank(e.textSearch, to_tsquery(:term)) pour le tri par pertinence
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">TsRankFunction</span> <span style="color:#66d9ef">extends</span> <span style="color:#a6e22e">TsFunction</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">getSql</span>(<span style="color:#a6e22e">SqlWalker</span> $sqlWalker)<span style="color:#f92672">:</span> <span style="color:#a6e22e">string</span>
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#39;ts_rank(&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">.</span>$this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">ftsField</span><span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">dispatch</span>($sqlWalker)
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">.</span><span style="color:#e6db74">&#39;, to_tsquery(&#39;</span><span style="color:#f92672">.</span>$this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">queryString</span><span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">dispatch</span>($sqlWalker)<span style="color:#f92672">.</span><span style="color:#e6db74">&#39;))&#39;</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><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-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">doctrine</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">orm</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">entity_managers</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">default</span>:
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">dql</span>:
</span></span><span style="display:flex;"><span>                    <span style="color:#f92672">string_functions</span>:
</span></span><span style="display:flex;"><span>                        <span style="color:#f92672">tswebsearchquery</span>: <span style="color:#ae81ff">App\Doctrine\ORM\Query\AST\Functions\TsWebsearchQueryFunction</span>
</span></span><span style="display:flex;"><span>                        <span style="color:#f92672">tsrank</span>: <span style="color:#ae81ff">App\Doctrine\ORM\Query\AST\Functions\TsRankFunction</span>
</span></span><span style="display:flex;"><span>                        <span style="color:#f92672">tsquery</span>: <span style="color:#ae81ff">App\Doctrine\ORM\Query\AST\Functions\TsQueryFunction</span>
</span></span><span style="display:flex;"><span>                        <span style="color:#f92672">tsplainquery</span>: <span style="color:#ae81ff">App\Doctrine\ORM\Query\AST\Functions\TsPlainQueryFunction</span>
</span></span></code></pre></div><p><code>websearch_to_tsquery</code> est le bon choix pour la recherche côté utilisateur: les espaces deviennent des AND, les chaînes entre guillemets deviennent des phrases, <code>-mot</code> exclut un terme. Inutile d&rsquo;apprendre aux utilisateurs à taper <code>interview &amp; président</code>. C&rsquo;est disponible depuis PostgreSQL 11. Sur les versions antérieures, <code>plainto_tsquery</code> est l&rsquo;équivalent le plus proche.</p>
<h2 id="le-filtre-api-platform-et-lindex-gin">Le filtre API Platform et l&rsquo;index GIN</h2>
<p>Avec les fonctions DQL enregistrées, le filtre API Platform est simple. Un <code>AbstractFilter</code> personnalisé appelle directement la fonction DQL dans le <code>QueryBuilder</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">class</span> <span style="color:#a6e22e">TextSearchFilter</span> <span style="color:#66d9ef">extends</span> <span style="color:#a6e22e">AbstractFilter</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">protected</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">filterProperty</span>(
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">string</span> $property,
</span></span><span style="display:flex;"><span>        $value,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">QueryBuilder</span> $queryBuilder,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">QueryNameGeneratorInterface</span> $queryNameGenerator,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">string</span> $resourceClass,
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">?</span><span style="color:#a6e22e">Operation</span> $operation <span style="color:#f92672">=</span> <span style="color:#66d9ef">null</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">array</span> $context <span style="color:#f92672">=</span> []
</span></span><span style="display:flex;"><span>    )<span style="color:#f92672">:</span> <span style="color:#a6e22e">void</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (<span style="color:#e6db74">&#39;textSearch&#39;</span> <span style="color:#f92672">!==</span> $property <span style="color:#f92672">||</span> <span style="color:#66d9ef">empty</span>($value)) {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        $queryBuilder
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">andWhere</span>(<span style="color:#e6db74">&#39;tswebsearchquery(o.textSearch, :value) = true&#39;</span>)
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">setParameter</span>(<span style="color:#e6db74">&#39;:value&#39;</span>, $value);
</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">getDescription</span>(<span style="color:#a6e22e">string</span> $resourceClass)<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></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>On l&rsquo;applique sur l&rsquo;entité avec la déclaration d&rsquo;index:</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">#[ORM\Index(
</span></span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">columns</span><span style="color:#f92672">:</span> [<span style="color:#e6db74">&#39;text_search&#39;</span>],
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">name</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;media_text_search_idx_gin&#39;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">options</span><span style="color:#f92672">:</span> [<span style="color:#e6db74">&#39;USING&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#39;gin (text_search)&#39;</span>]
</span></span><span style="display:flex;"><span>)]
</span></span><span style="display:flex;"><span><span style="color:#75715e">#[ApiFilter(TextSearchFilter::class, properties: [&#39;textSearch&#39; =&gt; &#39;partial&#39;])]
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Media</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:#75715e">#[ORM\Column(type: &#39;tsvector&#39;, nullable: true)]
</span></span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">protected</span> <span style="color:#f92672">?</span><span style="color:#a6e22e">string</span> $textSearch <span style="color:#f92672">=</span> <span style="color:#66d9ef">null</span>;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>L&rsquo;option <code>USING gin</code> n&rsquo;est pas négociable. Un index B-tree standard sur une colonne <code>tsvector</code> est inutile: PostgreSQL ne peut pas l&rsquo;utiliser pour les requêtes <code>@@</code>. GIN (Generalized Inverted Index) fonctionne différemment: il indexe chaque token individuellement, donc les recherches par n&rsquo;importe quel token sont en <code>O(log n)</code> plutôt que <code>O(n)</code>. Sans ça, on a construit un système qui donne l&rsquo;impression d&rsquo;être rapide mais qui fait quand même un full table scan.</p>
<p>Un <code>GET /media?textSearch=interview+president</code> touche maintenant l&rsquo;index GIN et répond en quelques millisecondes quel que soit la taille de la table.</p>
<h2 id="ce-que-la-répartition-ressemblait-vraiment">Ce que la répartition ressemblait vraiment</h2>
<p>La bibliothèque couvrait les fonctions atomiques bas niveau. Le code personnalisé couvrait les lacunes: un type DBAL <code>tsvector</code> que la bibliothèque ne fournissait pas, des wrappers DQL pratiques combinant <code>@@</code> et <code>websearch_to_tsquery</code> en un seul appel, et la colle applicative reliant tout ça au système d&rsquo;événements de Doctrine et à API Platform. Aucune requête native n&rsquo;a été nécessaire.</p>
<p>La répartition vaut d&rsquo;être notée en général: <code>postgresql-for-doctrine</code> donne les briques atomiques PostgreSQL, mais il faut quand même les composer en quelque chose que le reste du code peut utiliser sans y penser. Le pattern <code>FunctionNode</code> et le hook <code>convertToDatabaseValueSQL()</code> sont les deux points d&rsquo;extension qui rendent cette composition propre. Les deux valent d&rsquo;être connus, quelle que soit la bibliothèque de départ.</p>
]]></content:encoded></item><item><title>PHP 8.4 : les property hooks et la fin de la cérémonie getter/setter</title><link>https://guillaumedelre.github.io/fr/2025/01/05/php-8.4-les-property-hooks-et-la-fin-de-la-c%C3%A9r%C3%A9monie-getter/setter/</link><pubDate>Sun, 05 Jan 2025 00:00:00 +0000</pubDate><guid>https://guillaumedelre.github.io/fr/2025/01/05/php-8.4-les-property-hooks-et-la-fin-de-la-c%C3%A9r%C3%A9monie-getter/setter/</guid><description>Part 10 of 11 in &amp;quot;Sorties PHP&amp;quot;: PHP 8.4 apporte les property hooks : logique get/set directement sur les propriétés, remplaçant vingt ans de boilerplate getter/setter.</description><category>php-releases</category><content:encoded><![CDATA[<p>PHP 8.4 est sorti le 21 novembre. Les property hooks sont la fonctionnalité. Tout le reste, et il y en a beaucoup, est secondaire.</p>
<h2 id="les-property-hooks">Les property hooks</h2>
<p>Pendant vingt ans, si on voulait du comportement à l&rsquo;accès d&rsquo;une propriété en PHP, il fallait écrire des getters et setters :</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">User</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">private</span> <span style="color:#a6e22e">string</span> $_name;
</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">getName</span>()<span style="color:#f92672">:</span> <span style="color:#a6e22e">string</span> { <span style="color:#66d9ef">return</span> $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">_name</span>; }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">setName</span>(<span style="color:#a6e22e">string</span> $name)<span style="color:#f92672">:</span> <span style="color:#a6e22e">void</span> {
</span></span><span style="display:flex;"><span>        $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">_name</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">strtoupper</span>($name);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>PHP 8.4 ajoute des hooks directement sur la proprié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">class</span> <span style="color:#a6e22e">User</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#a6e22e">string</span> $name {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">set</span>(<span style="color:#a6e22e">string</span> $name) {
</span></span><span style="display:flex;"><span>            $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">name</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">strtoupper</span>($name);
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>On peut définir les hooks <code>get</code> et <code>set</code> indépendamment. Une propriété avec seulement un hook <code>get</code> est calculée à l&rsquo;accès :</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">Circle</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#a6e22e">float</span> $area {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">get</span> <span style="color:#f92672">=&gt;</span> <span style="color:#a6e22e">M_PI</span> <span style="color:#f92672">*</span> $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">radius</span> <span style="color:#f92672">**</span> <span style="color:#ae81ff">2</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">__construct</span>(<span style="color:#66d9ef">public</span> <span style="color:#a6e22e">float</span> $radius) {}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Pas de stockage backing, pas de méthode getter explicite, support IDE complet. Les interfaces peuvent aussi déclarer des propriétés avec des hooks, ce qui signifie que les contrats peuvent maintenant spécifier un comportement à l&rsquo;accès aux propriétés, quelque chose qui était tout simplement impossible avant.</p>
<h2 id="la-visibilité-asymétrique">La visibilité asymétrique</h2>
<p>Une option plus légère pour quand on veut juste une lecture publique et une écriture privée :</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">Version</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">private</span>(<span style="color:#a6e22e">set</span>) <span style="color:#a6e22e">string</span> $value <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;1.0.0&#39;</span>;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>$v <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Version</span>();
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> $v<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">value</span>;      <span style="color:#75715e">// fonctionne
</span></span></span><span style="display:flex;"><span>$v<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">value</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;2.0&#39;</span>;  <span style="color:#75715e">// Error
</span></span></span></code></pre></div><p>Élimine le pattern <code>private $x</code> + <code>public getX()</code> pour les propriétés publiques en lecture seule sans avoir besoin de la sémantique readonly complète.</p>
<h2 id="array_find-et-amis">array_find() et amis</h2>
<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>$first <span style="color:#f92672">=</span> <span style="color:#a6e22e">array_find</span>($users, <span style="color:#a6e22e">fn</span>($u) <span style="color:#f92672">=&gt;</span> $u<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">isActive</span>());
</span></span><span style="display:flex;"><span>$any   <span style="color:#f92672">=</span> <span style="color:#a6e22e">array_any</span>($users, <span style="color:#a6e22e">fn</span>($u) <span style="color:#f92672">=&gt;</span> $u<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">isPremium</span>());
</span></span><span style="display:flex;"><span>$all   <span style="color:#f92672">=</span> <span style="color:#a6e22e">array_all</span>($users, <span style="color:#a6e22e">fn</span>($u) <span style="color:#f92672">=&gt;</span> $u<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">isVerified</span>());
</span></span></code></pre></div><p>Ces fonctions existent dans la bibliothèque standard de chaque autre langage depuis des décennies. En PHP, il fallait utiliser <code>array_filter()</code> + accès par index ou écrire une boucle manuelle. Elles existent maintenant : <code>array_find()</code>, <code>array_find_key()</code>, <code>array_any()</code>, <code>array_all()</code>.</p>
<h2 id="instanciation-sans-parenthèses-supplémentaires">Instanciation sans parenthèses supplémentaires</h2>
<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:#66d9ef">new</span> <span style="color:#a6e22e">MyClass</span>())<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">method</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:#66d9ef">new</span> <span style="color:#a6e22e">MyClass</span>()<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">method</span>();
</span></span></code></pre></div><p>Une restriction syntaxique qui était toujours agaçante et jamais justifiée est supprimée.</p>
<h2 id="les-objets-paresseux">Les objets paresseux</h2>
<p>Des objets dont l&rsquo;initialisation est différée jusqu&rsquo;au premier accès à une proprié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>$user <span style="color:#f92672">=</span> $reflector<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">newLazyProxy</span>(<span style="color:#a6e22e">fn</span>() <span style="color:#f92672">=&gt;</span> $repository<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">find</span>($id));
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Pas d&#39;appel en base encore
</span></span></span><span style="display:flex;"><span>$user<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">name</span>; <span style="color:#75715e">// Maintenant le proxy s&#39;initialise
</span></span></span></code></pre></div><p>Le public direct est les auteurs de frameworks ORM et de conteneurs DI, pas les développeurs d&rsquo;applications. Mais l&rsquo;effet se fait sentir dans chaque application qui utilise Doctrine ou Symfony : le lazy loading implémenté au niveau du langage plutôt qu&rsquo;à travers la génération de code.</p>
<p>PHP 8.4 est un langage qui ressemble à peine au PHP 5 avec lequel la plupart d&rsquo;entre nous avons commencé. Les property hooks en particulier : ce ne sont pas des contournements, ce sont une fonctionnalité de conception.</p>
<h2 id="deprecated-pour-son-propre-code">#[\Deprecated] pour son propre code</h2>
<p>PHP émet des notices de dépréciation pour les fonctions intégrées depuis des années. 8.4 permet de câbler le même mécanisme dans son propre 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">class</span> <span style="color:#a6e22e">ApiClient</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">#[\Deprecated(
</span></span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">message</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;Use fetchJson() instead&#39;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">since</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;2.0&#39;</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">get</span>(<span style="color:#a6e22e">string</span> $url)<span style="color:#f92672">:</span> <span style="color:#a6e22e">string</span> { <span style="color:#f92672">...</span> }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Appeler une méthode dépréciée émet maintenant <code>E_USER_DEPRECATED</code>, exactement comme appeler <code>mysql_connect()</code>. Les IDEs le détectent, les analyseurs statiques le signalent, le log d&rsquo;erreurs le capture. Avant ça, la seule option était un commentaire PHPDoc <code>@deprecated</code> : bien pour les IDEs, complètement invisible pour le moteur.</p>
<h2 id="bcmathnumber-rend-la-précision-arbitraire-utilisable">BcMath\Number rend la précision arbitraire utilisable</h2>
<p>Les fonctions <code>bcmath</code> existent en PHP depuis toujours, mais leur API procédurale rend tout chaînage pénible. 8.4 ajoute <code>BcMath\Number</code>, un wrapper objet avec surcharge d&rsquo;opérateurs :</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>$a <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">BcMath\Number</span>(<span style="color:#e6db74">&#39;10.5&#39;</span>);
</span></span><span style="display:flex;"><span>$b <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">BcMath\Number</span>(<span style="color:#e6db74">&#39;3.2&#39;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>$result <span style="color:#f92672">=</span> $a <span style="color:#f92672">+</span> $b;             <span style="color:#75715e">// BcMath\Number(&#39;13.7&#39;)
</span></span></span><span style="display:flex;"><span>$result <span style="color:#f92672">=</span> $a <span style="color:#f92672">*</span> $b <span style="color:#f92672">-</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">BcMath\Number</span>(<span style="color:#e6db74">&#39;1&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> $result;                  <span style="color:#75715e">// 32.6
</span></span></span></code></pre></div><p>Les opérateurs <code>+</code>, <code>-</code>, <code>*</code>, <code>/</code>, <code>**</code>, <code>%</code> fonctionnent tous. L&rsquo;objet est immutable. L&rsquo;échelle se propage automatiquement à travers les opérations. Les calculs financiers, qui nécessitaient des chaînes de <code>bcadd(bcmul(...), ...)</code>, se lisent maintenant comme de l&rsquo;arithmétique.</p>
<p>De nouvelles fonctions procédurales complètent le tableau : <code>bcceil()</code>, <code>bcfloor()</code>, <code>bcround()</code>, <code>bcdivmod()</code>.</p>
<h2 id="lenum-roundingmode-remplace-les-constantes-php_round_">L&rsquo;enum RoundingMode remplace les constantes PHP_ROUND_*</h2>
<p><code>round()</code> a toujours pris un <code>$mode</code> entier depuis un ensemble de constantes <code>PHP_ROUND_*</code>. 8.4 les remplace par un enum <code>RoundingMode</code> avec des noms plus propres et quatre modes supplémentaires qui n&rsquo;étaient pas disponibles avant :</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">round</span>(<span style="color:#ae81ff">2.5</span>, <span style="color:#a6e22e">mode</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">RoundingMode</span><span style="color:#f92672">::</span><span style="color:#a6e22e">HalfAwayFromZero</span>);  <span style="color:#75715e">// 3
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">round</span>(<span style="color:#ae81ff">2.5</span>, <span style="color:#a6e22e">mode</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">RoundingMode</span><span style="color:#f92672">::</span><span style="color:#a6e22e">HalfTowardsZero</span>);   <span style="color:#75715e">// 2
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">round</span>(<span style="color:#ae81ff">2.5</span>, <span style="color:#a6e22e">mode</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">RoundingMode</span><span style="color:#f92672">::</span><span style="color:#a6e22e">HalfEven</span>);          <span style="color:#75715e">// 2 (arrondi du banquier)
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">round</span>(<span style="color:#ae81ff">2.5</span>, <span style="color:#a6e22e">mode</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">RoundingMode</span><span style="color:#f92672">::</span><span style="color:#a6e22e">HalfOdd</span>);           <span style="color:#75715e">// 3
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Les quatre nouveaux modes (disponibles uniquement via l&#39;enum)
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">round</span>(<span style="color:#ae81ff">2.3</span>, <span style="color:#a6e22e">mode</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">RoundingMode</span><span style="color:#f92672">::</span><span style="color:#a6e22e">TowardsZero</span>);       <span style="color:#75715e">// 2
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">round</span>(<span style="color:#ae81ff">2.7</span>, <span style="color:#a6e22e">mode</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">RoundingMode</span><span style="color:#f92672">::</span><span style="color:#a6e22e">AwayFromZero</span>);      <span style="color:#75715e">// 3
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">round</span>(<span style="color:#ae81ff">2.3</span>, <span style="color:#a6e22e">mode</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">RoundingMode</span><span style="color:#f92672">::</span><span style="color:#a6e22e">PositiveInfinity</span>);  <span style="color:#75715e">// 3
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">round</span>(<span style="color:#ae81ff">2.3</span>, <span style="color:#a6e22e">mode</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">RoundingMode</span><span style="color:#f92672">::</span><span style="color:#a6e22e">NegativeInfinity</span>);  <span style="color:#75715e">// 2
</span></span></span></code></pre></div><p>Les anciennes constantes <code>PHP_ROUND_*</code> fonctionnent encore. L&rsquo;enum est la voie à suivre.</p>
<h2 id="les-fonctions-de-string-multibyte-qui-auraient-dû-exister">Les fonctions de string multibyte qui auraient dû exister</h2>
<p><code>mb_trim()</code>, <code>mb_ltrim()</code>, <code>mb_rtrim()</code> : des fonctions de trim qui respectent les frontières de caractères multibyte, pas juste les espaces ASCII. Aussi nouvelles : <code>mb_ucfirst()</code> et <code>mb_lcfirst()</code> pour la mise en majuscule correcte des strings multibyte.</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>$s <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;\u{200B}hello\u{200B}&#34;</span>; <span style="color:#75715e">// Espaces de largeur nulle
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> <span style="color:#a6e22e">mb_trim</span>($s);              <span style="color:#75715e">// &#34;hello&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> <span style="color:#a6e22e">mb_ucfirst</span>(<span style="color:#e6db74">&#39;über&#39;</span>);       <span style="color:#75715e">// &#34;Über&#34;
</span></span></span></code></pre></div><p>Ces fonctions comblent des lacunes présentes depuis que <code>mbstring</code> a été introduit.</p>
<h2 id="request_parse_body-pour-les-requêtes-non-post">request_parse_body() pour les requêtes non-POST</h2>
<p>PHP parse automatiquement <code>application/x-www-form-urlencoded</code> et <code>multipart/form-data</code> dans <code>$_POST</code> et <code>$_FILES</code>, mais seulement pour les requêtes POST. Les requêtes PATCH et PUT avec les mêmes types de contenu nécessitaient un parsing manuel avec <code>file_get_contents('php://input')</code> et du code personnalisé.</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">// Dans un handler PATCH
</span></span></span><span style="display:flex;"><span>[$_POST, $_FILES] <span style="color:#f92672">=</span> <span style="color:#a6e22e">request_parse_body</span>();
</span></span></code></pre></div><p>La fonction retourne un tuple. Même logique de parsing que PHP utilise pour POST, maintenant disponible pour n&rsquo;importe quelle méthode HTTP.</p>
<h2 id="une-nouvelle-api-dom-qui-suit-la-spec">Une nouvelle API DOM qui suit la spec</h2>
<p>L&rsquo;API <code>DOMDocument</code> existante était construite sur une spec DOM level 3 plus ancienne avec des spécificités PHP superposées. 8.4 ajoute un namespace <code>Dom\</code> parallèle qui implémente le WHATWG Living Standard :</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>$doc <span style="color:#f92672">=</span> <span style="color:#a6e22e">Dom\HTMLDocument</span><span style="color:#f92672">::</span><span style="color:#a6e22e">createFromString</span>(<span style="color:#e6db74">&#39;&lt;p class=&#34;lead&#34;&gt;Hello&lt;/p&gt;&#39;</span>);
</span></span><span style="display:flex;"><span>$p <span style="color:#f92672">=</span> $doc<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">querySelector</span>(<span style="color:#e6db74">&#39;p&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> $p<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">classList</span>;  <span style="color:#75715e">// &#34;lead&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> $p<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">id</span>;         <span style="color:#75715e">// &#34;&#34;
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>$doc2 <span style="color:#f92672">=</span> <span style="color:#a6e22e">Dom\HTMLDocument</span><span style="color:#f92672">::</span><span style="color:#a6e22e">createFromFile</span>(<span style="color:#e6db74">&#39;page.html&#39;</span>);
</span></span></code></pre></div><p><code>Dom\HTMLDocument</code> parse correctement HTML5, tag soup inclus. <code>Dom\XMLDocument</code> gère le XML strict. Les nouvelles classes sont strictes sur les types, retournent les bons types de nœuds, et exposent des propriétés modernes comme <code>classList</code>, <code>id</code>, <code>className</code>. L&rsquo;ancien <code>DOMDocument</code> reste, inchangé, pour la compatibilité ascendante.</p>
<h2 id="pdo-reçoit-des-sous-classes-spécifiques-au-driver">PDO reçoit des sous-classes spécifiques au driver</h2>
<p><code>PDO::connect()</code> et l&rsquo;instanciation directe retournent maintenant des sous-classes spécifiques au driver quand elles sont disponibles :</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>$pdo <span style="color:#f92672">=</span> <span style="color:#a6e22e">PDO</span><span style="color:#f92672">::</span><span style="color:#a6e22e">connect</span>(<span style="color:#e6db74">&#39;mysql:host=localhost;dbname=test&#39;</span>, <span style="color:#e6db74">&#39;user&#39;</span>, <span style="color:#e6db74">&#39;pass&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#75715e">// $pdo est maintenant une instance Pdo\Mysql
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>$pdo <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Pdo\Pgsql</span>(<span style="color:#e6db74">&#39;pgsql:host=localhost;dbname=test&#39;</span>, <span style="color:#e6db74">&#39;user&#39;</span>, <span style="color:#e6db74">&#39;pass&#39;</span>);
</span></span></code></pre></div><p>Chaque sous-classe driver (<code>Pdo\Mysql</code>, <code>Pdo\Pgsql</code>, <code>Pdo\Sqlite</code>, <code>Pdo\Firebird</code>, <code>Pdo\Odbc</code>, <code>Pdo\DbLib</code>) peut exposer des méthodes spécifiques au driver sans polluer l&rsquo;interface <code>PDO</code> de base. Doctrine, Laravel et autres ORMs similaires peuvent maintenant type-hinter contre la classe de driver spécifique quand ils ont besoin d&rsquo;un comportement spécifique au driver.</p>
<h2 id="openssl-reçoit-le-support-des-clés-modernes">OpenSSL reçoit le support des clés modernes</h2>
<p><code>openssl_pkey_new()</code> et les fonctions associées supportent maintenant Curve25519 et Curve448, les courbes elliptiques modernes qui ont remplacé les anciennes courbes NIST dans la plupart des recommandations de sécurité :</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>$key <span style="color:#f92672">=</span> <span style="color:#a6e22e">openssl_pkey_new</span>([<span style="color:#e6db74">&#39;curve_name&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#39;ed25519&#39;</span>, <span style="color:#e6db74">&#39;private_key_type&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#a6e22e">OPENSSL_KEYTYPE_EC</span>]);
</span></span><span style="display:flex;"><span>$details <span style="color:#f92672">=</span> <span style="color:#a6e22e">openssl_pkey_get_details</span>($key);
</span></span></code></pre></div><p><code>x25519</code> et <code>x448</code> pour l&rsquo;échange de clés, <code>ed25519</code> et <code>ed448</code> pour les signatures. Les quatre fonctionnent maintenant avec <code>openssl_sign()</code> et <code>openssl_verify()</code>.</p>
<h2 id="pcre--lookbehind-de-longueur-variable">PCRE : lookbehind de longueur variable</h2>
<p>La mise à jour de la bibliothèque PCRE2 embarquée (10.44) apporte les assertions lookbehind de longueur variable, quelque chose que les moteurs regex de Perl et Python avaient et que PHP ne pouvait pas faire :</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">// Correspondre &#34;bar&#34; seulement quand précédé de &#34;foo&#34; ou &#34;foooo&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">preg_match</span>(<span style="color:#e6db74">&#39;/(?&lt;=foo+)bar/&#39;</span>, <span style="color:#e6db74">&#39;foooobar&#39;</span>, $matches);
</span></span></code></pre></div><p>Les assertions lookbehind nécessitaient auparavant un pattern de largeur fixe. Elles peuvent maintenant correspondre à des patterns de longueur variable. Le modificateur <code>r</code> (<code>PCRE2_EXTRA_CASELESS_RESTRICT</code>) est aussi nouveau : il empêche le mélange de caractères ASCII et non-ASCII dans les correspondances insensibles à la casse, fermant une classe d&rsquo;attaques de confusion Unicode.</p>
<h2 id="datetime-reçoit-les-microsecondes-et-une-factory-de-timestamp">DateTime reçoit les microsecondes et une factory de timestamp</h2>
<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>$dt <span style="color:#f92672">=</span> <span style="color:#a6e22e">DateTimeImmutable</span><span style="color:#f92672">::</span><span style="color:#a6e22e">createFromTimestamp</span>(<span style="color:#ae81ff">1700000000.5</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> $dt<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">getMicrosecond</span>(); <span style="color:#75715e">// 500000
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>$with_micros <span style="color:#f92672">=</span> $dt<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">setMicrosecond</span>(<span style="color:#ae81ff">123456</span>);
</span></span></code></pre></div><p><code>createFromTimestamp()</code> accepte un float pour une précision sous-seconde. <code>getMicrosecond()</code> et <code>setMicrosecond()</code> complètent l&rsquo;API pour le composant microseconde qui était à l&rsquo;intérieur de <code>DateTime</code> mais inaccessible directement.</p>
<h2 id="fpow-pour-la-conformité-ieee-754">fpow() pour la conformité IEEE 754</h2>
<p><code>pow(0, -2)</code> en PHP retournait historiquement une valeur définie par l&rsquo;implémentation. 8.4 déprécie <code>pow()</code> avec une base zéro et un exposant négatif et introduit <code>fpow()</code>, qui suit strictement IEEE 754 : <code>fpow(0, -2)</code> retourne <code>INF</code>, comme le standard le définit :</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">echo</span> <span style="color:#a6e22e">fpow</span>(<span style="color:#ae81ff">2.0</span>, <span style="color:#ae81ff">3.0</span>);   <span style="color:#75715e">// 8.0
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> <span style="color:#a6e22e">fpow</span>(<span style="color:#ae81ff">0.0</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">1.0</span>);  <span style="color:#75715e">// INF
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> <span style="color:#a6e22e">fpow</span>(<span style="color:#f92672">-</span><span style="color:#ae81ff">1.0</span>, <span style="color:#a6e22e">INF</span>);  <span style="color:#75715e">// 1.0
</span></span></span></code></pre></div><p>À retenir dans tout code faisant des calculs mathématiques où la conformité IEEE compte.</p>
<h2 id="le-coût-de-bcrypt-augmente">Le coût de bcrypt augmente</h2>
<p>Le coût par défaut pour <code>password_hash()</code> avec <code>PASSWORD_BCRYPT</code> est passé de <code>10</code> à <code>12</code>. Ça impacte tout code appelant <code>password_hash($pass, PASSWORD_BCRYPT)</code> sans coût explicite. L&rsquo;objectif est de maintenir le défaut grossièrement à &ldquo;quelques centaines de millisecondes sur du matériel moderne&rdquo; à mesure que le matériel devient plus rapide.</p>
<p>Si on stocke des hash bcrypt et qu&rsquo;on monte sur 8.4, les hash existants restent valides : <code>password_verify()</code> lit le coût depuis le hash lui-même. Les nouveaux hash utilisent le coût 12. <code>password_needs_rehash()</code> retourne true pour les anciens hash si on passe <code>['cost' =&gt; 12]</code>, donc on peut les mettre à jour à la prochaine connexion.</p>
<h2 id="les-dépréciations-qui-comptent">Les dépréciations qui comptent</h2>
<p>Les paramètres implicitement nullable sont dépréciés. Si un paramètre a un défaut de <code>null</code>, le type doit le dire explicitement :</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">// Déprécié dans 8.4
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">foo</span>(<span style="color:#a6e22e">string</span> $s <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 style="color:#75715e">// Correct
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">foo</span>(<span style="color:#f92672">?</span><span style="color:#a6e22e">string</span> $s <span style="color:#f92672">=</span> <span style="color:#66d9ef">null</span>) {}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">foo</span>(<span style="color:#a6e22e">string</span><span style="color:#f92672">|</span><span style="color:#66d9ef">null</span> $s <span style="color:#f92672">=</span> <span style="color:#66d9ef">null</span>) {}
</span></span></code></pre></div><p><code>trigger_error()</code> avec <code>E_USER_ERROR</code> est déprécié : le remplacer par une exception ou <code>exit()</code>. Le niveau <code>E_USER_ERROR</code> a toujours été un hybride maladroit entre une erreur récupérable et une erreur fatale, et personne n&rsquo;était sûr lequel.</p>
<p><code>lcg_value()</code> est aussi déprécié. Utiliser <code>Random\Randomizer::getFloat()</code> à la place. Le générateur LCG avait de mauvaises propriétés d&rsquo;aléatoire et aucun contrôle de graine.</p>
]]></content:encoded></item><item><title>API Platform 4.0 : support Laravel et PUT repensé</title><link>https://guillaumedelre.github.io/fr/2024/09/27/api-platform-4.0-support-laravel-et-put-repens%C3%A9/</link><pubDate>Fri, 27 Sep 2024 00:00:00 +0000</pubDate><guid>https://guillaumedelre.github.io/fr/2024/09/27/api-platform-4.0-support-laravel-et-put-repens%C3%A9/</guid><description>Part 6 of 8 in &amp;quot;Sorties API Platform&amp;quot;: API Platform 4.0 apporte le support Laravel de première classe avec Eloquent et les policies, et supprime PUT des opérations par défaut pour corriger une ambiguïté sémantique persistante.</description><category>api-platform-releases</category><content:encoded><![CDATA[<p>API Platform 4.0 est sorti neuf jours après la 3.4, fin septembre 2024. Le numéro de version est honnête : il n&rsquo;y a pas de nouvelle architecture, et la migration depuis la 3.4 est courte si on a résolu les dépréciations. Ce qui en fait un majeur, c&rsquo;est le changement de scope — API Platform n&rsquo;est plus un framework uniquement Symfony — et un défaut d&rsquo;opinion qui inverse six ans de comportement PUT.</p>
<h2 id="laravel-comme-cible-de-première-classe">Laravel comme cible de première classe</h2>
<p>Depuis sa première release, API Platform était construit sur Symfony. La couche HTTP, les métadonnées, le serializer et le bridge Doctrine supposaient tous le container de Symfony, l&rsquo;event dispatcher et le cycle de vie des requêtes. Les utilisateurs Laravel pouvaient faire tourner API Platform via un adaptateur léger, mais les filtres, la sécurité et l&rsquo;intégration Doctrine ne fonctionnaient pas avec Eloquent.</p>
<p>La 4.0 livre un bridge Laravel dédié. Il mappe la couche d&rsquo;état d&rsquo;API Platform sur le cycle de vie des requêtes de Laravel, s&rsquo;intègre directement avec les modèles Eloquent, et se branche sur le système d&rsquo;autorisation de Laravel :</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">ApiPlatform\Metadata\ApiResource</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">ApiPlatform\Metadata\Get</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">ApiPlatform\Metadata\GetCollection</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">Illuminate\Database\Eloquent\Model</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#[ApiResource(
</span></span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">operations</span><span style="color:#f92672">:</span> [<span style="color:#66d9ef">new</span> <span style="color:#a6e22e">GetCollection</span>(), <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Get</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">Book</span> <span style="color:#66d9ef">extends</span> <span style="color:#a6e22e">Model</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">protected</span> $fillable <span style="color:#f92672">=</span> [<span style="color:#e6db74">&#39;title&#39;</span>, <span style="color:#e6db74">&#39;author&#39;</span>];
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>L&rsquo;autorisation utilise les policies et gates de Laravel plutôt que les security voters de Symfony. Les opérations exposent un paramètre <code>policy</code> dédié qui correspond à un nom de méthode de policy :</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">#[Get(policy: &#39;view&#39;)]
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Book</span> <span style="color:#66d9ef">extends</span> <span style="color:#a6e22e">Model</span> {}
</span></span></code></pre></div><p>API Platform mappe la valeur de <code>policy</code> vers <code>Gate::allows()</code> de Laravel avec l&rsquo;instance du modèle. Les policies peuvent aussi être auto-détectées : si un modèle a une classe de policy enregistrée, API Platform infère la bonne méthode (<code>view</code>, <code>viewAny</code>, <code>create</code>, <code>update</code>, <code>delete</code>) selon le type d&rsquo;opération. Les filtres pour les collections Eloquent couvrent le même périmètre que leurs homologues Doctrine : <code>PartialSearchFilter</code>, <code>EqualsFilter</code>, <code>RangeFilter</code>, <code>OrderFilter</code>, <code>DateFilter</code>, et des variantes de recherche (<code>StartSearchFilter</code>, <code>EndSearchFilter</code>). La pagination, le tri et la validation fonctionnent via les mécanismes natifs de Laravel.</p>
<p>Ce n&rsquo;est pas un shim de compatibilité. Le bridge Laravel est maintenu aux côtés du bridge Symfony et est couvert par la même suite de tests. Les projets utilisant l&rsquo;un ou l&rsquo;autre framework ont la même API de définition des ressources.</p>
<h2 id="put-supprimé-des-opérations-par-défaut">PUT supprimé des opérations par défaut</h2>
<p>Depuis API Platform 1.0, <code>#[ApiResource]</code> sans tableau <code>operations</code> explicite générait des opérations CRUD incluant PUT. Le handler PUT mettait à jour les ressources existantes et, depuis la 3.1, pouvait aussi les créer via <code>allowCreate: true</code>.</p>
<p>La 4.0 supprime PUT de l&rsquo;ensemble par défaut. <code>#[ApiResource]</code> génère maintenant GET, POST, PATCH et DELETE. Pour utiliser PUT, il faut le déclarer explicitement :</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">ApiPlatform\Metadata\ApiResource</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">ApiPlatform\Metadata\Put</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#[ApiResource(
</span></span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">operations</span><span style="color:#f92672">:</span> [
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// ... autres opérations
</span></span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Put</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">class</span> <span style="color:#a6e22e">Book</span> {}
</span></span></code></pre></div><p>La motivation est la clarté sémantique. PATCH remplace PUT pour la plupart des cas d&rsquo;usage de mise à jour partielle. La sémantique de PUT — remplacer la représentation entière de la ressource — est rarement ce qu&rsquo;une API implémente réellement, mais le défaut le faisait apparaître dans toutes les APIs à moins de l&rsquo;enlever activement. Rendre PUT opt-in aligne les défauts sur la façon dont la sémantique HTTP est réellement utilisée en pratique.</p>
<h2 id="php-82-minimum">PHP 8.2 minimum</h2>
<p>La 4.0 abandonne PHP 8.0 et 8.1. PHP 8.2 est le nouveau minimum. La syntaxe de classe readonly, <code>AllowDynamicProperties</code> et les types DNF<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> introduits en 8.2 sont disponibles dans toute la codebase. Aucune fonctionnalité spécifique de 8.2 n&rsquo;est structurante pour la 4.0 — le bump de version concerne principalement l&rsquo;abandon du fardeau de maintenance plus ancien.</p>
<h2 id="symfony-64-et-doctrine-orm-217-minimum">Symfony 6.4+ et Doctrine ORM 2.17+ minimum</h2>
<p>Côté Symfony, la 4.0 requiert Symfony 6.4 ou 7.x et Doctrine ORM 2.17 ou 3.x. Les deux étaient déjà supportés en 3.4. La migration de la 3.4 vers la 4.0 sur la piste Symfony est : résoudre les dépréciations 3.4, vérifier qu&rsquo;on est sur Symfony 6.4+ et ORM 2.17+, puis mettre à jour. Aucun travail de migration supplémentaire n&rsquo;est nécessaire si c&rsquo;est déjà en place.</p>
<h2 id="ce-que-la-40-nest-pas">Ce que la 4.0 n&rsquo;est pas</h2>
<p>La 4.0 n&rsquo;est pas une nouvelle architecture. Les state providers, processors et le modèle de métadonnées de ressources de la 3.0 sont inchangés. Le bridge Laravel ajoute un nouveau contexte d&rsquo;exécution mais ne change pas la façon dont les ressources ou les opérations sont déclarées. La séparation est intentionnelle : si la 3.0 était le &ldquo;quoi&rdquo;, la 4.0 est le &ldquo;où&rdquo;.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Types en Forme Normale Disjonctive : types intersection combinés avec union, comme <code>(A&amp;B)|null</code>.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>API Platform 3.4 : BackedEnum comme ressources et support DBAL 4</title><link>https://guillaumedelre.github.io/fr/2024/09/18/api-platform-3.4-backedenum-comme-ressources-et-support-dbal-4/</link><pubDate>Wed, 18 Sep 2024 00:00:00 +0000</pubDate><guid>https://guillaumedelre.github.io/fr/2024/09/18/api-platform-3.4-backedenum-comme-ressources-et-support-dbal-4/</guid><description>Part 5 of 8 in &amp;quot;Sorties API Platform&amp;quot;: API Platform 3.4 fait des BackedEnum des ressources API complètes, ajoute un BackedEnumFilter, supporte les expressions de sécurité sur les paramètres, et ajoute le support DBAL 4.</description><category>api-platform-releases</category><content:encoded><![CDATA[<p>API Platform 3.4 est arrivé en septembre 2024 comme dernier mineur avant le saut vers la 4.0. La fonctionnalité principale est le BackedEnum comme ressource complète — pas juste un champ typé, mais un enum qui est lui-même un endpoint API.</p>
<h2 id="backedenum-comme-ressources-api">BackedEnum comme ressources API</h2>
<p>Depuis PHP 8.1, les classes BackedEnum ont un ensemble fixe de cas avec des valeurs de support string ou integer. API Platform 3.4 permet de mettre <code>#[ApiResource]</code> directement sur un BackedEnum :</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">ApiPlatform\Metadata\ApiResource</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">ApiPlatform\Metadata\GetCollection</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#[ApiResource(
</span></span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">operations</span><span style="color:#f92672">:</span> [<span style="color:#66d9ef">new</span> <span style="color:#a6e22e">GetCollection</span>()]
</span></span><span style="display:flex;"><span>)]
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">enum</span> <span style="color:#a6e22e">BookStatus</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">string</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">case</span> <span style="color:#a6e22e">Draft</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;draft&#39;</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">case</span> <span style="color:#a6e22e">Published</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;published&#39;</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">case</span> <span style="color:#a6e22e">Archived</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;archived&#39;</span>;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Un endpoint <code>GET /book_statuses</code> retourne la liste des cas. Chaque cas est sérialisé avec son nom et sa valeur. L&rsquo;endpoint est en lecture seule — les enums sont immuables par nature.</p>
<p>C&rsquo;est surtout utile pour les consommateurs frontend qui veulent une liste lisible par machine des valeurs valides sans les coder en dur. L&rsquo;alternative était un contrôleur personnalisé ou une ressource DTO dédiée listant manuellement les valeurs de l&rsquo;enum.</p>
<h2 id="backedenumfilter">BackedEnumFilter</h2>
<p>Le compagnon des ressources enum est <code>BackedEnumFilter</code>, un nouveau filtre pour les collections Doctrine qui contraint une requête par une propriété BackedEnum :</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">ApiPlatform\Doctrine\Orm\Filter\BackedEnumFilter</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">ApiPlatform\Metadata\ApiFilter</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">ApiPlatform\Metadata\ApiResource</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#[ApiResource]
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#[ApiFilter(BackedEnumFilter::class, properties: [&#39;status&#39;])]
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Book</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#a6e22e">BookStatus</span> $status;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><code>GET /books?status=published</code> filtre la collection aux livres où <code>status</code> est égal à <code>BookStatus::Published</code>. Les valeurs d&rsquo;enum invalides retournent une réponse 400. Avant ce filtre, on devait soit écrire un filtre personnalisé, soit utiliser <code>SearchFilter</code> et valider la valeur manuellement.</p>
<h2 id="expressions-de-sécurité-sur-les-paramètres">Expressions de sécurité sur les paramètres</h2>
<p>La 3.3 avait ajouté la sécurité aux liens et aux propriétés. La 3.4 étend cela aux paramètres de requête. Un paramètre peut déclarer une expression de sécurité qui contrôle s&rsquo;il est accepté :</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">ApiPlatform\Metadata\GetCollection</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">ApiPlatform\Metadata\QueryParameter</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#[GetCollection(
</span></span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">parameters</span><span style="color:#f92672">:</span> [
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">QueryParameter</span>(
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">key</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;includeDeleted&#39;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">security</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;is_granted(&#39;ROLE_ADMIN&#39;)&#34;</span>
</span></span><span style="display:flex;"><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">class</span> <span style="color:#a6e22e">Book</span> {}
</span></span></code></pre></div><p>Quand l&rsquo;expression de sécurité est false, le paramètre est rejeté avec un 403, pas silencieusement ignoré. C&rsquo;est plus explicite que vérifier le rôle de l&rsquo;utilisateur dans le provider après avoir reçu le paramètre.</p>
<h2 id="support-dbal-4-ajouté">Support DBAL 4 ajouté</h2>
<p>La 3.4 ajoute le support de Doctrine DBAL 4, qui apporte des changements au système de types qui affectent la façon dont les types personnalisés et le SQL spécifique à la plateforme fonctionnent. Les filtres Doctrine Orm et les extensions de requête dans API Platform ont été mis à jour pour fonctionner avec la nouvelle API de types DBAL 4.</p>
<p>DBAL 3 (<code>^3.4.0</code>) et DBAL 4 sont supportés simultanément en 3.4. C&rsquo;est la release à adopter pour migrer vers DBAL 4 tout en restant sur une branche stable API Platform 3.x.</p>
<h2 id="validateur-de-paramètres-de-requête-déprécié">Validateur de paramètres de requête déprécié</h2>
<p>La 3.3 avait ajouté le validateur strict de paramètres de requête en opt-in. La 3.4 déprécie l&rsquo;ancien comportement (paramètres inconnus silencieusement ignorés) en préparation pour rendre la validation stricte la valeur par défaut en 4.0. Les projets qui s&rsquo;appuient sur des paramètres de requête pass-through ont une release de plus pour les déclarer explicitement.</p>
<h2 id="dernier-arrêt-avant-la-40">Dernier arrêt avant la 4.0</h2>
<p>La 3.4 est la dernière release 3.x avec de nouvelles fonctionnalités. Tout ce qui était déprécié d&rsquo;ici la 3.4 disparaît en 4.0. Le chemin de migration de la 3.4 vers la 4.0 est intentionnellement court : résoudre les dépréciations, puis mettre à jour.</p>
]]></content:encoded></item><item><title>API Platform 3.3 : headers, sécurité des liens, et webhooks OpenAPI</title><link>https://guillaumedelre.github.io/fr/2024/04/29/api-platform-3.3-headers-s%C3%A9curit%C3%A9-des-liens-et-webhooks-openapi/</link><pubDate>Mon, 29 Apr 2024 00:00:00 +0000</pubDate><guid>https://guillaumedelre.github.io/fr/2024/04/29/api-platform-3.3-headers-s%C3%A9curit%C3%A9-des-liens-et-webhooks-openapi/</guid><description>Part 4 of 8 in &amp;quot;Sorties API Platform&amp;quot;: API Platform 3.3 ajoute la configuration déclarative des headers, la sécurité fine sur les liens de sous-ressources, et le support des webhooks OpenAPI.</description><category>api-platform-releases</category><content:encoded><![CDATA[<p>API Platform 3.3 est sorti en avril 2024 avec un ensemble d&rsquo;ajouts ciblés. Aucun d&rsquo;eux ne remodèle l&rsquo;architecture — la 3.2 avait déjà clos ce chapitre. Ce que la 3.3 apporte, c&rsquo;est du contrôle sur des choses qui étaient soit codées en dur soit nécessitaient un contournement : les headers de réponse, la visibilité des liens sur les sous-ressources, et les webhooks dans la spec générée.</p>
<h2 id="configuration-déclarative-des-headers">Configuration déclarative des headers</h2>
<p>Avant la 3.3, définir des headers de réponse personnalisés nécessitait soit un processor personnalisé qui modifiait l&rsquo;objet réponse, soit un event listener Symfony sur <code>kernel.response</code>. Les deux approches fonctionnaient mais vivaient en dehors de la définition de la ressource.</p>
<p>La 3.3 ajoute un paramètre <code>parameters</code> aux métadonnées d&rsquo;opération :</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">ApiPlatform\Metadata\Get</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">ApiPlatform\Metadata\HeaderParameter</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#[Get(
</span></span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">parameters</span><span style="color:#f92672">:</span> [
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#39;X-Custom-Header&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">HeaderParameter</span>(<span style="color:#a6e22e">description</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;A custom header&#39;</span>),
</span></span><span style="display:flex;"><span>    ]
</span></span><span style="display:flex;"><span>)]
</span></span></code></pre></div><p>Pour les headers qui varient par réponse (comme <code>Cache-Control</code> avec un max-age calculé), le processor peut encore les définir directement sur l&rsquo;objet réponse. Le paramètre <code>parameters</code> sert principalement à documenter les headers attendus dans la spec OpenAPI et pour les valeurs de headers statiques.</p>
<h2 id="sécurité-des-liens-sur-les-sous-ressources">Sécurité des liens sur les sous-ressources</h2>
<p>Quand une ressource expose des liens vers des ressources liées, ces liens apparaissent dans la sortie sérialisée indépendamment du fait que l&rsquo;utilisateur courant puisse accéder à la ressource liée. Cela crée un problème de divulgation : un utilisateur qui peut lire un livre mais pas le profil de son auteur voit quand même l&rsquo;URI de l&rsquo;auteur dans la réponse.</p>
<p>La 3.3 ajoute des expressions de sécurité au descripteur <code>Link</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">ApiPlatform\Metadata\ApiResource</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">ApiPlatform\Metadata\Get</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">ApiPlatform\Metadata\Link</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#[ApiResource]
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#[Get]
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Book</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">#[Link(
</span></span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">toClass</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">Author</span><span style="color:#f92672">::</span><span style="color:#a6e22e">class</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">security</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;is_granted(&#39;ROLE_ADMIN&#39;)&#34;</span>
</span></span><span style="display:flex;"><span>    )]
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#a6e22e">Author</span> $author;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Le lien est omis de la réponse quand l&rsquo;expression de sécurité évalue à false. La ressource liée elle-même n&rsquo;est pas affectée — seulement le fait que la réponse courante inclue la référence à elle.</p>
<h2 id="apipropertysecurity"><code>ApiProperty::security</code></h2>
<p>Le même mécanisme d&rsquo;expression de sécurité est disponible au niveau propriété via <code>ApiProperty::security</code>. Cela permet de cacher des champs individuels selon l&rsquo;utilisateur courant sans écrire un normalizer personnalisé :</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">ApiPlatform\Metadata\ApiProperty</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">Book</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">#[ApiProperty(security: &#34;is_granted(&#39;ROLE_ADMIN&#39;)&#34;)]
</span></span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#a6e22e">string</span> $internalNote;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>La propriété est exclue de la sérialisation quand l&rsquo;expression est false. C&rsquo;est plus propre qu&rsquo;un normalizer pour le cas courant de champs conditionnels par rôle.</p>
<h2 id="webhooks-openapi">Webhooks OpenAPI</h2>
<p><a href="https://spec.openapis.org/oas/v3.1.0" target="_blank" rel="noopener noreferrer">OpenAPI 3.1</a>
 supporte les webhooks — des appels HTTP sortants que votre API fait à des listeners enregistrés — dans le document spec lui-même. Avant la 3.3, il n&rsquo;y avait pas de moyen de les documenter dans la spec générée par API Platform.</p>
<p>La 3.3 ajoute une classe <code>Webhook</code> à passer au paramètre <code>openapi</code> d&rsquo;une opération. On déclare une classe PHP dédiée avec <code>#[ApiResource]</code> et on utilise <code>Webhook</code> sur chaque opération pour décrire la forme de l&rsquo;appel sortant :</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">ApiPlatform\Metadata\ApiResource</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">ApiPlatform\Metadata\Post</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">ApiPlatform\OpenApi\Attributes\Webhook</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">ApiPlatform\OpenApi\Model\Operation</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">ApiPlatform\OpenApi\Model\PathItem</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#[ApiResource(
</span></span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">operations</span><span style="color:#f92672">:</span> [
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Post</span>(
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">openapi</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Webhook</span>(
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">name</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;bookCreated&#39;</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">pathItem</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">PathItem</span>(
</span></span><span style="display:flex;"><span>                    <span style="color:#a6e22e">post</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Operation</span>(<span style="color:#a6e22e">summary</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;Un livre a été créé&#39;</span>),
</span></span><span style="display:flex;"><span>                ),
</span></span><span style="display:flex;"><span>            )
</span></span><span style="display:flex;"><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">class</span> <span style="color:#a6e22e">BookWebhook</span> {}
</span></span></code></pre></div><p>Les définitions de webhooks apparaissent dans la spec générée sous la clé <code>webhooks</code> aux côtés des paths réguliers. Swagger UI les affiche dans une section séparée.</p>
<h2 id="deep-linking-dans-swagger-ui">Deep linking dans Swagger UI</h2>
<p>Swagger UI supporte le deep linking — des URLs mémorisables qui ouvrent directement sur une opération spécifique dans l&rsquo;interface. Avant la 3.3, l&rsquo;intégration API Platform n&rsquo;activait pas cela. La 3.3 active l&rsquo;option <code>deepLinking</code> de Swagger UI, configurable via <code>swagger_ui_extra_configuration</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">api_platform</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">openapi</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">swagger_ui_extra_configuration</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">deepLinking</span>: <span style="color:#66d9ef">true</span>
</span></span></code></pre></div><p>Avec cette option activée, le fragment d&rsquo;URL se met à jour pendant la navigation dans l&rsquo;UI, et coller ou partager l&rsquo;URL ouvre la même opération. Utile quand on écrit de la doc qui pointe directement vers un endpoint spécifique.</p>
<h2 id="validation-stricte-des-paramètres-de-requête">Validation stricte des paramètres de requête</h2>
<p>La 3.3 renforce le validateur de paramètres de requête : les paramètres non déclarés sur l&rsquo;opération renvoient maintenant une réponse 400 au lieu d&rsquo;être silencieusement ignorés. Ce comportement est opt-in :</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">api_platform</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">validator</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">query_parameter_validation</span>: <span style="color:#66d9ef">true</span>
</span></span></code></pre></div><p>L&rsquo;intention est de détecter les fautes de frappe et les mauvaises utilisations de l&rsquo;API tôt. Si vous vous appuyez sur des paramètres de requête pass-through pour une logique personnalisée (logging, feature flags), vous devez les déclarer explicitement sur l&rsquo;opération avant d&rsquo;activer cela.</p>
]]></content:encoded></item><item><title>PHP 8.3 : les constantes typées et les petites victoires qui restent</title><link>https://guillaumedelre.github.io/fr/2024/01/07/php-8.3-les-constantes-typ%C3%A9es-et-les-petites-victoires-qui-restent/</link><pubDate>Sun, 07 Jan 2024 00:00:00 +0000</pubDate><guid>https://guillaumedelre.github.io/fr/2024/01/07/php-8.3-les-constantes-typ%C3%A9es-et-les-petites-victoires-qui-restent/</guid><description>Part 9 of 11 in &amp;quot;Sorties PHP&amp;quot;: PHP 8.3 ajoute les constantes de classe typées, une fonction json_validate, et une façon plus propre d&amp;#39;accéder dynamiquement aux constantes de classe.</description><category>php-releases</category><content:encoded><![CDATA[<p>PHP 8.3 est sorti le 23 novembre. Une version discrète par les standards PHP : pas de bouleversement à la taille des enums, pas de JIT. Ce qu&rsquo;elle apporte, c&rsquo;est un ensemble ciblé d&rsquo;améliorations qui comblent des lacunes de longue date dans le système de types et ajoutent des fonctions qui auraient dû exister depuis des années.</p>
<h2 id="les-constantes-de-classe-typées">Les constantes de classe typées</h2>
<p>Les constantes de classe n&rsquo;ont jamais été typées depuis leur introduction. PHP 8.3 corrige ça :</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">interface</span> <span style="color:#a6e22e">HasVersion</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#66d9ef">string</span> <span style="color:#a6e22e">VERSION</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">class</span> <span style="color:#a6e22e">App</span> <span style="color:#66d9ef">implements</span> <span style="color:#a6e22e">HasVersion</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#66d9ef">string</span> <span style="color:#a6e22e">VERSION</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;1.0.0&#39;</span>;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Sans constantes typées, une constante d&rsquo;interface pouvait être redéfinie avec un type complètement différent dans une classe implémentante sans que rien ne se plaigne. Les constantes typées comblent ce trou, et sur les bases de code pilotées par les interfaces, l&rsquo;impact est immédiat.</p>
<h2 id="laccès-dynamique-aux-constantes-de-classe">L&rsquo;accès dynamique aux constantes de classe</h2>
<p>Une lacune qui nécessitait un contournement depuis que les constantes existent :</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>$name <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;STATUS&#39;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> <span style="color:#a6e22e">MyClass</span><span style="color:#f92672">::</span>{$name}; <span style="color:#75715e">// ça marche maintenant
</span></span></span></code></pre></div><p>Avant, accéder à une constante avec un nom dynamique signifiait appeler <code>constant('MyClass::STATUS')</code>. La nouvelle syntaxe est cohérente avec la façon dont PHP gère déjà les variables variables et les appels de méthodes dynamiques.</p>
<h2 id="readonly-peut-maintenant-être-modifié-dans-clone">readonly peut maintenant être modifié dans clone</h2>
<p>Une limitation spécifique mais vraiment agaçante de 8.1 avec readonly : on ne pouvait pas cloner un objet et changer une propriété readonly. 8.3 ajoute la possibilité de réinitialiser les propriétés readonly pendant le clonage, ce qui rend les value objects immuables utilisables dans beaucoup plus de patterns.</p>
<h2 id="json_validate">json_validate()</h2>
<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">if</span> (<span style="color:#a6e22e">json_validate</span>($string)) {
</span></span><span style="display:flex;"><span>    $data <span style="color:#f92672">=</span> <span style="color:#a6e22e">json_decode</span>($string);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Avant 8.3, la seule façon de valider une chaîne JSON était de la décoder et de vérifier les erreurs. <code>json_validate()</code> vérifie sans allouer la structure décodée, ce qui compte quand on a juste besoin de savoir si la chaîne est du JSON valide, pas ce qu&rsquo;elle contient.</p>
<h2 id="améliorations-du-randomizer">Améliorations du Randomizer</h2>
<p><code>getBytesFromString()</code> génère une chaîne aléatoire composée uniquement de caractères d&rsquo;un ensemble donné :</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>$rng <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Random\Randomizer</span>();
</span></span><span style="display:flex;"><span>$token <span style="color:#f92672">=</span> $rng<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">getBytesFromString</span>(<span style="color:#e6db74">&#39;abcdefghijklmnopqrstuvwxyz0123456789&#39;</span>, <span style="color:#ae81ff">32</span>);
</span></span></code></pre></div><p>L&rsquo;approche précédente : <code>str_split</code>, <code>array_map</code>, sélection aléatoire, <code>implode</code>. Ça marchait, mais c&rsquo;était plus long que ça n&rsquo;avait le droit d&rsquo;être.</p>
<p>8.3 est pour les équipes qui adoptent les versions PHP rapidement et veulent les améliorations incrémentales. Les constantes typées seules valent le coup sur toute base de code avec des constantes d&rsquo;interface.</p>
<h2 id="override-rend-lhéritage-explicite">#[\Override] rend l&rsquo;héritage explicite</h2>
<p>Avant 8.3, rien n&rsquo;empêchait d&rsquo;écrire une méthode qu&rsquo;on croyait surcharger celle d&rsquo;un parent, alors qu&rsquo;on avait un typo dans le nom ou que le parent l&rsquo;avait silencieusement supprimée. Des bugs silencieux, zéro retour du moteur.</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">Cache</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">#[\Override]
</span></span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">get</span>(<span style="color:#a6e22e">string</span> $key)<span style="color:#f92672">:</span> <span style="color:#a6e22e">mixed</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Le moteur vérifie que cette méthode existe dans un parent ou une interface
</span></span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Si la méthode n&rsquo;existe dans aucune classe parente ou interface implémentée, PHP lève une erreur. Même concept que <code>@Override</code> en Java ou <code>override</code> en C#, enfin en PHP.</p>
<h2 id="final-sur-les-méthodes-de-trait">final sur les méthodes de trait</h2>
<p>Les traits ont toujours eu des aspérités dans le modèle OOP de PHP. Un problème spécifique : une classe utilisant un trait pouvait surcharger n&rsquo;importe laquelle de ses méthodes, sapant les garanties que le trait essayait de fournir. 8.3 laisse le trait lui-même marquer une méthode comme final :</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">trait</span> <span style="color:#a6e22e">Singleton</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">final</span> <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">static</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">getInstance</span>()<span style="color:#f92672">:</span> <span style="color:#66d9ef">static</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// ...
</span></span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Maintenant, une classe utilisant le trait ne peut pas surcharger <code>getInstance()</code>. La garantie tient.</p>
<h2 id="les-classes-anonymes-peuvent-être-readonly">Les classes anonymes peuvent être readonly</h2>
<p>PHP 8.1 avait apporté les classes readonly. Les classes anonymes avaient été laissées de côté pour une raison obscure. 8.3 corrige ça :</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>$point <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">readonly</span> <span style="color:#a6e22e">class</span>(<span style="color:#ae81ff">3</span>, <span style="color:#ae81ff">4</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">float</span> $x,
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">public</span> <span style="color:#a6e22e">float</span> $y,
</span></span><span style="display:flex;"><span>    ) {}
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p>Pratique quand on a besoin d&rsquo;un value object immuable jetable sans la cérémonie de lui donner un nom.</p>
<h2 id="les-initialiseurs-de-variables-statiques-acceptent-des-expressions">Les initialiseurs de variables statiques acceptent des expressions</h2>
<p>Une restriction petite mais ancienne : les initialiseurs de variables statiques n&rsquo;acceptaient que des expressions constantes, pas d&rsquo;appels de fonctions. 8.3 lève cette contrainte :</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">function</span> <span style="color:#a6e22e">connection</span>()<span style="color:#f92672">:</span> <span style="color:#a6e22e">PDO</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">static</span> $pdo <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">PDO</span>(<span style="color:#a6e22e">getenv</span>(<span style="color:#e6db74">&#39;DATABASE_URL&#39;</span>));
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> $pdo;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>L&rsquo;initialiseur s&rsquo;exécute une seule fois au premier appel, la variable statique persiste. Faisable avec un test null avant, c&rsquo;est juste plus propre.</p>
<h2 id="mb_str_pad-existe-enfin">mb_str_pad() existe enfin</h2>
<p><code>str_pad()</code> a toujours été conscient des octets, pas des caractères. Pour les chaînes multibyte (arabe, japonais, caractères accentués) il produisait une sortie incorrecte. 8.3 ajoute enfin la variante multibyte :</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>$padded <span style="color:#f92672">=</span> <span style="color:#a6e22e">mb_str_pad</span>(<span style="color:#e6db74">&#39;日本&#39;</span>, <span style="color:#ae81ff">10</span>, <span style="color:#e6db74">&#39;*&#39;</span>, <span style="color:#a6e22e">STR_PAD_BOTH</span>);
</span></span></code></pre></div><p>La fonction respecte les limites de caractères, pas les comptages d&rsquo;octets.</p>
<h2 id="str_increment-et-str_decrement">str_increment() et str_decrement()</h2>
<p>L&rsquo;opérateur <code>++</code> de PHP sur les chaînes a une histoire de bizarreries : il incrémente les séquences de lettres (<code>'a'</code> → <code>'b'</code>, <code>'z'</code> → <code>'aa'</code>), mais <code>--</code> n&rsquo;a jamais fonctionné symétriquement. Le comportement était suffisamment surprenant que 8.3 déprécie <code>++</code>/<code>--</code> sur les chaînes non alphanumériques et introduit des fonctions explicites :</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">echo</span> <span style="color:#a6e22e">str_increment</span>(<span style="color:#e6db74">&#39;a&#39;</span>);  <span style="color:#75715e">// b
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> <span style="color:#a6e22e">str_increment</span>(<span style="color:#e6db74">&#39;Az&#39;</span>); <span style="color:#75715e">// Ba
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> <span style="color:#a6e22e">str_decrement</span>(<span style="color:#e6db74">&#39;b&#39;</span>);  <span style="color:#75715e">// a
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> <span style="color:#a6e22e">str_decrement</span>(<span style="color:#e6db74">&#39;Ba&#39;</span>); <span style="color:#75715e">// Az
</span></span></span></code></pre></div><p>Les fonctions rendent l&rsquo;intention évidente et le comportement prévisible.</p>
<h2 id="randomrandomizer-gagne-le-support-des-flottants">Random\Randomizer gagne le support des flottants</h2>
<p>8.3 comble le côté flottant de l&rsquo;API Randomizer :</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>$rng <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Random\Randomizer</span>();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Un flottant dans [0.0, 1.0)
</span></span></span><span style="display:flex;"><span>$f <span style="color:#f92672">=</span> $rng<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">nextFloat</span>();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Un flottant dans une plage spécifique avec contrôle de l&#39;inclusion des bornes
</span></span></span><span style="display:flex;"><span>$f <span style="color:#f92672">=</span> $rng<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">getFloat</span>(<span style="color:#ae81ff">1.5</span>, <span style="color:#ae81ff">3.5</span>, <span style="color:#a6e22e">Random\IntervalBoundary</span><span style="color:#f92672">::</span><span style="color:#a6e22e">ClosedOpen</span>);
</span></span></code></pre></div><p><code>IntervalBoundary</code> est un nouvel enum avec quatre valeurs : <code>ClosedOpen</code>, <code>ClosedClosed</code>, <code>OpenClosed</code>, <code>OpenOpen</code>. C&rsquo;est important pour la justesse statistique : l&rsquo;approche naïve avec <code>rand() / getrandmax()</code> ne produit pas une distribution uniforme sur les flottants.</p>
<h2 id="la-hiérarchie-dexceptions-de-date">La hiérarchie d&rsquo;exceptions de Date</h2>
<p>Les erreurs de date/heure en PHP levaient des exceptions génériques sans moyen de distinguer &ldquo;chaîne malformée&rdquo; de &ldquo;timezone invalide&rdquo; sans parser le message. 8.3 ajoute une hiérarchie propre :</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">try</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">DateTimeImmutable</span>(<span style="color:#e6db74">&#39;not a date&#39;</span>);
</span></span><span style="display:flex;"><span>} <span style="color:#66d9ef">catch</span> (<span style="color:#a6e22e">DateMalformedStringException</span> $e) {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// spécifiquement un échec de parsing
</span></span></span><span style="display:flex;"><span>} <span style="color:#66d9ef">catch</span> (<span style="color:#a6e22e">DateException</span> $e) {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// autres erreurs liées aux dates
</span></span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>L&rsquo;arbre complet : <code>DateError</code> (niveau moteur), <code>DateException</code> (base), avec des sous-classes spécifiques pour timezone invalide, chaîne d&rsquo;intervalle malformée, chaîne de période malformée, et chaîne de date malformée.</p>
<h2 id="gc_status-en-dit-plus">gc_status() en dit plus</h2>
<p><code>gc_status()</code> retourne maintenant huit champs supplémentaires : <code>running</code>, <code>protected</code>, <code>full</code>, <code>buffer_size</code>, et des décompositions temporelles (<code>application_time</code>, <code>collector_time</code>, <code>destructor_time</code>, <code>free_time</code>). Si vous profilez la pression mémoire ou les pauses GC, ces données étaient auparavant inaccessibles sans passer par une extension.</p>
<h2 id="strrchr-gagne-un-argument-de-direction">strrchr() gagne un argument de direction</h2>
<p><code>strrchr()</code> (trouver la dernière occurrence d&rsquo;un caractère, retourner de là jusqu&rsquo;à la fin) accepte maintenant un booléen <code>$before_needle</code>, alignant son API sur celle de <code>strstr()</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>$path <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;/var/www/html/index.php&#39;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> <span style="color:#a6e22e">strrchr</span>($path, <span style="color:#e6db74">&#39;/&#39;</span>, <span style="color:#a6e22e">before_needle</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">true</span>);  <span style="color:#75715e">// /var/www/html
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> <span style="color:#a6e22e">strrchr</span>($path, <span style="color:#e6db74">&#39;/&#39;</span>);                        <span style="color:#75715e">// /index.php
</span></span></span></code></pre></div><p>Une fonction présente dans PHP depuis 1994, enfin cohérente avec sa voisine.</p>
<h2 id="dépréciations-à-noter">Dépréciations à noter</h2>
<p><code>get_class()</code> et <code>get_parent_class()</code> sans arguments émettent maintenant des avertissements de dépréciation. Les formes sans argument reposaient sur un contexte <code>$this</code> implicite, facile à mal lire. Passez l&rsquo;objet explicitement.</p>
<p><code>assert_options()</code> et les constantes <code>ASSERT_*</code> sont dépréciées au profit de la directive INI <code>zend.assertions</code>, qui est le bon outil pour contrôler le comportement des assertions selon les environnements.</p>
<p>Les opérateurs <code>++</code>/<code>--</code> sur les chaînes vides et les chaînes non numériques non alphanumériques émettent maintenant des avertissements de dépréciation. Le comportement était un territoire indéfini. 8.3 amorce la migration vers un comportement défini en 9.0.</p>
<h2 id="protection-contre-les-débordements-de-pile">Protection contre les débordements de pile</h2>
<p>Deux nouvelles directives INI : <code>zend.max_allowed_stack_size</code> fixe une limite dure sur la profondeur de pile de PHP, et <code>zend.reserved_stack_size</code> réserve un buffer pour le nettoyage après qu&rsquo;une limite soit atteinte. Avant 8.3, un code récursif profond pouvait tout simplement crasher au niveau OS. Maintenant PHP l&rsquo;intercepte et lève une <code>Error</code> avec un message utile.</p>
]]></content:encoded></item><item><title>API Platform 3.2 : les erreurs comme ressources et le retour des sous-ressources</title><link>https://guillaumedelre.github.io/fr/2023/10/12/api-platform-3.2-les-erreurs-comme-ressources-et-le-retour-des-sous-ressources/</link><pubDate>Thu, 12 Oct 2023 00:00:00 +0000</pubDate><guid>https://guillaumedelre.github.io/fr/2023/10/12/api-platform-3.2-les-erreurs-comme-ressources-et-le-retour-des-sous-ressources/</guid><description>Part 3 of 8 in &amp;quot;Sorties API Platform&amp;quot;: API Platform 3.2 fait des erreurs des ressources Problem Detail de première classe, rétablit les sous-ressources proprement, et rend les event listeners optionnels.</description><category>api-platform-releases</category><content:encoded><![CDATA[<p>API Platform 3.2 est arrivé en octobre 2023 avec trois changements qui ont fait avancer le modèle d&rsquo;état : les erreurs sont devenues des ressources, les sous-ressources sont revenues sous une forme qui s&rsquo;intègre vraiment dans l&rsquo;architecture, et le dernier point d&rsquo;extension hérité — les event listeners — a été formellement remplacé.</p>
<h2 id="les-erreurs-comme-ressources">Les erreurs comme ressources</h2>
<p>Avant la 3.2, la gestion des erreurs était en dehors du modèle de ressources. Les exceptions étaient interceptées par un event listener Symfony et converties en réponse, avec un contrôle limité sur la forme de la sortie.</p>
<p>La 3.2 fait des erreurs des classes <code>ApiResource</code> de première classe conformes à la <a href="https://www.rfc-editor.org/rfc/rfc9457" target="_blank" rel="noopener noreferrer">RFC 9457</a>
 (Problem Details for HTTP APIs). La classe d&rsquo;erreur intégrée est <code>ApiPlatform\ApiResource\Error</code>, et on peut créer la sienne :</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">ApiPlatform\Metadata\ApiResource</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">ApiPlatform\Metadata\ErrorResource</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">ApiPlatform\Metadata\Exception\ProblemExceptionInterface</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#[ApiResource]
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#[ErrorResource]
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">BookNotFoundError</span> <span style="color:#66d9ef">extends</span> <span style="color:#a6e22e">\RuntimeException</span> <span style="color:#66d9ef">implements</span> <span style="color:#a6e22e">ProblemExceptionInterface</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">readonly</span> <span style="color:#a6e22e">string</span> $bookId)
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">parent</span><span style="color:#f92672">::</span><span style="color:#a6e22e">__construct</span>(<span style="color:#e6db74">&#34;Book </span><span style="color:#e6db74">$bookId</span><span style="color:#e6db74"> not found&#34;</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">getType</span>()<span style="color:#f92672">:</span> <span style="color:#a6e22e">string</span>
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#39;/errors/book-not-found&#39;</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Quand cette exception est levée n&rsquo;importe où dans la couche d&rsquo;état, API Platform l&rsquo;intercepte, la sérialise comme une réponse Problem Detail, et génère un schéma OpenAPI approprié pour elle. Le type d&rsquo;erreur, le titre, le détail et le statut font tous partie du contrat de la ressource — pas des chaînes codées en dur dans un listener.</p>
<h2 id="les-sous-ressources-sans-les-contournements">Les sous-ressources sans les contournements</h2>
<p>Les sous-ressources existaient en 2.x mais ont été supprimées en 3.0 parce qu&rsquo;elles étaient étroitement couplées à l&rsquo;ancien modèle de data provider et ne pouvaient pas être proprement mappées à la nouvelle architecture orientée opération. La 3.2 les réintroduit d&rsquo;une façon qui s&rsquo;intègre.</p>
<p>Une sous-ressource est une ressource accessible via l&rsquo;URI d&rsquo;une ressource parente. En 3.2, elle est déclarée directement sur la ressource enfant en utilisant <code>uriTemplate</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">ApiPlatform\Metadata\ApiResource</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">ApiPlatform\Metadata\GetCollection</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#[ApiResource(
</span></span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">operations</span><span style="color:#f92672">:</span> [
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">GetCollection</span>(
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">uriTemplate</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;/books/{bookId}/reviews&#39;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">uriVariables</span><span style="color:#f92672">:</span> [
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#39;bookId&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Link</span>(<span style="color:#a6e22e">fromClass</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">Book</span><span style="color:#f92672">::</span><span style="color:#a6e22e">class</span>),
</span></span><span style="display:flex;"><span>            ],
</span></span><span style="display:flex;"><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">class</span> <span style="color:#a6e22e">Review</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></span></code></pre></div><p>Le descripteur <code>Link</code> rend la relation explicite. Le provider reçoit <code>bookId</code> dans <code>$uriVariables</code> et peut l&rsquo;utiliser pour délimiter la requête. Pas d&rsquo;inférence magique, pas de jointures implicites — la structure d&rsquo;URI et l&rsquo;accès aux données sont tous les deux déclarés.</p>
<h2 id="canonical_uri_template-pour-plusieurs-chemins-daccès"><code>canonical_uri_template</code> pour plusieurs chemins d&rsquo;accès</h2>
<p>Quand une ressource est accessible via plusieurs URI (un endpoint direct et un endpoint de sous-ressource), OpenAPI doit savoir quelle URI est canonique pour les liens <code>$ref</code>. La 3.2 utilise le <code>uriTemplate</code> de niveau supérieur sur <code>ApiResource</code> comme URI canonique par défaut. Pour plus de contrôle, l&rsquo;option <code>canonical_uri_template</code> peut être passée via <code>extraProperties</code> sur n&rsquo;importe quelle opération pour la définir explicitement.</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">#[ApiResource(
</span></span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">uriTemplate</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;/reviews/{id}&#39;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">operations</span><span style="color:#f92672">:</span> [
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Get</span>(),
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">GetCollection</span>(
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">uriTemplate</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;/books/{bookId}/reviews&#39;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">uriVariables</span><span style="color:#f92672">:</span> [<span style="color:#e6db74">&#39;bookId&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Link</span>(<span style="color:#a6e22e">fromClass</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">Book</span><span style="color:#f92672">::</span><span style="color:#a6e22e">class</span>)],
</span></span><span style="display:flex;"><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">class</span> <span style="color:#a6e22e">Review</span> {}
</span></span></code></pre></div><p>La spec OpenAPI générée utilise l&rsquo;URI canonique pour les références de schéma, gardant le document cohérent quand une ressource apparaît sous plusieurs chemins.</p>
<h2 id="types-union-et-intersection">Types union et intersection</h2>
<p>La 3.2 ajoute le support des types union et intersection PHP dans la couche de métadonnées. Une propriété déclarée comme <code>Book|Magazine</code> génère un schéma <code>oneOf</code> approprié dans OpenAPI. C&rsquo;était auparavant non supporté — on devait tomber sur un <code>mixed</code> non typé ou annoter la propriété manuellement.</p>
<h2 id="les-event-listeners-rendus-optionnels">Les event listeners rendus optionnels</h2>
<p>Le dernier shim de compatibilité venu de la 2.x était la possibilité d&rsquo;utiliser les event listeners Symfony sur les événements <code>kernel.request</code> et <code>kernel.view</code> pour intercepter le flux de données d&rsquo;API Platform. La 3.2 ne les supprime pas, mais introduit un moyen de s&rsquo;en passer : passer <code>event_listeners_backward_compatibility_layer: false</code> dans la configuration d&rsquo;API Platform désactive entièrement les hooks basés sur les événements. Le remplacement est un provider ou processor décoré par un autre provider ou processor. Le hook basé sur les événements était avec état, dépendant de l&rsquo;ordre, et court-circuitait entièrement le contexte d&rsquo;opération. Les providers décorés reçoivent l&rsquo;objet opération et peuvent appeler le provider interne quand ils sont prêts.</p>
<h2 id="le-modèle-détat-est-maintenant-complet">Le modèle d&rsquo;état est maintenant complet</h2>
<p>La 3.0 a introduit l&rsquo;architecture. La 3.1 a ajouté la séparation ressource/entité. La 3.2 ferme les lacunes restantes : les erreurs ont un contrat de ressource, les sous-ressources ont un modèle de déclaration propre, et la couche d&rsquo;état couvre désormais tous les points d&rsquo;extension que les event listeners géraient autrefois. Les shims 2.x existent encore, mais s&rsquo;en passer n&rsquo;est plus qu&rsquo;une ligne de configuration.</p>
]]></content:encoded></item><item><title>API Platform 3.1 : votre ressource n'a pas à être votre entité</title><link>https://guillaumedelre.github.io/fr/2023/01/23/api-platform-3.1-votre-ressource-na-pas-%C3%A0-%C3%AAtre-votre-entit%C3%A9/</link><pubDate>Mon, 23 Jan 2023 00:00:00 +0000</pubDate><guid>https://guillaumedelre.github.io/fr/2023/01/23/api-platform-3.1-votre-ressource-na-pas-%C3%A0-%C3%AAtre-votre-entit%C3%A9/</guid><description>Part 2 of 8 in &amp;quot;Sorties API Platform&amp;quot;: API Platform 3.1 découple les ressources API des entités Doctrine, introduit un PUT conforme à la spec, et collecte les erreurs de dénormalisation en liste.</description><category>api-platform-releases</category><content:encoded><![CDATA[<p>Quatre mois après la 3.0, API Platform 3.1 est arrivé avec le premier lot de fonctionnalités construites sur le nouveau modèle d&rsquo;état. Tous les changements ne sont pas spectaculaires, mais l&rsquo;un d&rsquo;eux résout un problème qui a engendré beaucoup de contournements alambiqués en 2.x : votre ressource API n&rsquo;a plus besoin d&rsquo;être votre entité Doctrine.</p>
<h2 id="la-séparation-ressourceentité">La séparation ressource/entité</h2>
<p>En 2.x, API Platform fonctionnait mieux quand votre ressource API et votre modèle de persistance étaient la même classe. Utiliser un DTO comme surface API était possible via le système Input/Output DTO, mais ce système a été supprimé en 3.0 — il compliquait le modèle d&rsquo;état sans apporter suffisamment de bénéfices.</p>
<p>La 3.1 le remplace par quelque chose de plus propre. Le paramètre <code>stateOptions</code> d&rsquo;une opération accepte un objet <code>DoctrineOrmOptions</code> qui pointe vers une entité différente :</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">ApiPlatform\Metadata\ApiResource</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">ApiPlatform\Metadata\GetCollection</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">ApiPlatform\Doctrine\Orm\State\Options</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#[ApiResource(
</span></span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">operations</span><span style="color:#f92672">:</span> [
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">GetCollection</span>(
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">stateOptions</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Options</span>(<span style="color:#a6e22e">entityClass</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">BookEntity</span><span style="color:#f92672">::</span><span style="color:#a6e22e">class</span>),
</span></span><span style="display:flex;"><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">class</span> <span style="color:#a6e22e">BookDto</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#a6e22e">string</span> $title;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#a6e22e">string</span> $author;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Le provider reçoit les objets <code>BookEntity</code> de Doctrine et la couche de sérialisation les mappe sur <code>BookDto</code>. Les filtres Doctrine, la pagination et le tri fonctionnent tous sur <code>BookEntity</code>. La surface API expose <code>BookDto</code>. Les deux peuvent évoluer indépendamment.</p>
<p>Ça compte plus qu&rsquo;il n&rsquo;y paraît. Votre modèle de persistance accumule des champs internes, des relations et des colonnes qui n&rsquo;ont aucune raison d&rsquo;apparaître dans votre API. Avant la 3.1, on les exposait quand même ou on construisait un normaliseur élaboré pour les cacher. Maintenant on déclare ce que l&rsquo;API expose comme classe distincte et on laisse le framework gérer la correspondance.</p>
<h2 id="un-put-conforme-à-la-spec">Un PUT conforme à la spec</h2>
<p>Depuis la version 1.0, le handler PUT d&rsquo;API Platform mettait à jour des ressources existantes. Créer une ressource via PUT — ce que la spec HTTP autorise explicitement — n&rsquo;était pas supporté. La 3.1 ajoute la création basée sur l&rsquo;<code>uriTemplate</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:#75715e">#[Put(
</span></span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">uriTemplate</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;/books/{id}&#39;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">allowCreate</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>)]
</span></span></code></pre></div><p>Avec <code>allowCreate: true</code>, un PUT vers une URI qui n&rsquo;existe pas crée la ressource au lieu de retourner une 404. L&rsquo;identifiant vient de l&rsquo;URI, pas du corps de la requête. C&rsquo;est ce que la RFC 9110 décrit pour PUT : &ldquo;Si la ressource cible n&rsquo;a pas de représentation courante et que le PUT en crée une avec succès, le serveur d&rsquo;origine DOIT informer le user agent en envoyant une réponse 201 (Created).&rdquo;</p>
<p>C&rsquo;est un petit paramètre, mais il ouvre API Platform à des cas d&rsquo;usage — création idempotente, identifiants assignés par le client — qui nécessitaient auparavant un contrôleur personnalisé.</p>
<h2 id="les-erreurs-de-dénormalisation-collectées-pas-jetées">Les erreurs de dénormalisation collectées, pas jetées</h2>
<p>Avant la 3.1, les erreurs de désérialisation s&rsquo;arrêtaient au premier problème. Envoyer un corps de requête avec cinq champs invalides renvoyait une erreur sur le premier. On le corrigeait, on renvoyait, on trouvait le deuxième. À répéter cinq fois.</p>
<p>La 3.1 ajoute une option <code>collect_denormalization_errors</code> sur l&rsquo;opération qui change ce comportement :</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">#[Post(collectDenormalizationErrors: true)]
</span></span></span></code></pre></div><p>Avec cette option activée, API Platform intercepte toutes les erreurs de type et les violations de contraintes pendant la désérialisation et les retourne sous forme de liste structurée dans la réponse, formatée de la même façon que les erreurs de validation. Un seul aller-retour, une vue complète.</p>
<h2 id="apiresourceopenapi-remplace-openapicontext"><code>ApiResource::openapi</code> remplace <code>openapiContext</code></h2>
<p>L&rsquo;ancien paramètre <code>openapiContext</code> acceptait un tableau brut fusionné dans le schéma OpenAPI généré — pratique mais non typé. La 3.1 introduit un paramètre <code>openapi</code> de premier ordre qui accepte un objet <code>OpenApiOperation</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">ApiPlatform\OpenApi\Model\Operation</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">ApiPlatform\OpenApi\Model\RequestBody</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#[Post(
</span></span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">openapi</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Operation</span>(
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">requestBody</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">RequestBody</span>(
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">description</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;Créer un livre&#39;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">required</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>        ),
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">summary</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;Créer une nouvelle entrée livre&#39;</span>,
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>)]
</span></span></code></pre></div><p>L&rsquo;ancien tableau <code>openapiContext</code> fonctionne encore mais est déprécié. La nouvelle approche est typée, compatible avec l&rsquo;autocomplétion IDE, et se valide à la construction plutôt qu&rsquo;à la génération du schéma. Les backed enums PHP 8.1 obtiennent également une génération de schéma OpenAPI correcte en 3.1 — un champ typé comme backed enum produit un schéma avec des valeurs <code>enum</code> et le type correct, sans annotation supplémentaire.</p>
<h2 id="le-pattern-est-clair">Le pattern est clair</h2>
<p>La 3.0 a établi l&rsquo;architecture. La 3.1 montre ce que cette architecture permet : une séparation propre ressource/entité sans système DTO parallèle, une sémantique HTTP correcte selon la RFC, un meilleur rapport d&rsquo;erreurs. Aucune de ces fonctionnalités n&rsquo;aurait été aussi propre à implémenter sur le modèle data provider de la 2.x. Les features de la 3.1 sont la première preuve que la réécriture était la bonne décision.</p>
]]></content:encoded></item><item><title>PHP 8.2 : les classes readonly et la dépréciation qui compte vraiment</title><link>https://guillaumedelre.github.io/fr/2023/01/22/php-8.2-les-classes-readonly-et-la-d%C3%A9pr%C3%A9ciation-qui-compte-vraiment/</link><pubDate>Sun, 22 Jan 2023 00:00:00 +0000</pubDate><guid>https://guillaumedelre.github.io/fr/2023/01/22/php-8.2-les-classes-readonly-et-la-d%C3%A9pr%C3%A9ciation-qui-compte-vraiment/</guid><description>Part 8 of 11 in &amp;quot;Sorties PHP&amp;quot;: PHP 8.2 introduit les classes readonly, déprécie les propriétés dynamiques, et ajoute les types en forme normale disjonctive.</description><category>php-releases</category><content:encoded><![CDATA[<p>PHP 8.2 est sorti le 8 décembre. Les classes readonly font les gros titres. La dépréciation des propriétés dynamiques, elle, demande votre attention concrète.</p>
<h2 id="les-propriétés-dynamiques-dépréciées">Les propriétés dynamiques dépréciées</h2>
<p>PHP a toujours permis d&rsquo;ajouter des propriétés à des objets sans les déclarer dans la classe :</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">User</span> {}
</span></span><span style="display:flex;"><span>$user <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">User</span>();
</span></span><span style="display:flex;"><span>$user<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">name</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;Alice&#39;</span>; <span style="color:#75715e">// aucune déclaration, aucune erreur... jusqu&#39;ici
</span></span></span></code></pre></div><p>En 8.2, ça déclenche un avertissement de dépréciation. En PHP 9.0, ce sera une erreur fatale. Le délai de grâce existe, mais le compteur tourne.</p>
<p>La logique est solide : les propriétés dynamiques sont une source classique de typos qui passent silencieusement (écrivez <code>$user-&gt;nmae</code> et PHP crée simplement une nouvelle propriété au lieu de se plaindre). Des déclarations explicites rendent le contrat de la classe lisible et donnent aux outils matière à travailler.</p>
<p>La migration est essentiellement mécanique : déclarez les propriétés, ou posez <code>#[AllowDynamicProperties]</code> sur les classes legacy que vous ne pouvez pas encore toucher.</p>
<h2 id="les-classes-readonly">Les classes readonly</h2>
<p>8.1 avait ajouté <code>readonly</code> sur les propriétés individuelles. 8.2 l&rsquo;ajoute sur la déclaration de classe elle-même :</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">Point</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">float</span> $x,
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">public</span> <span style="color:#a6e22e">float</span> $y,
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">public</span> <span style="color:#a6e22e">float</span> $z,
</span></span><span style="display:flex;"><span>    ) {}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Toutes les propriétés promues et déclarées explicitement deviennent readonly automatiquement. Les value objects (coordonnées, montants monétaires, identifiants) sont la cible évidente. La syntaxe est propre et l&rsquo;intention se lit clairement.</p>
<p>Une contrainte : les classes readonly ne peuvent pas avoir de propriétés non typées, ce qui était déjà une mauvaise idée avec readonly de toute façon.</p>
<h2 id="les-types-dnf">Les types DNF</h2>
<p>Les types en Forme Normale Disjonctive permettent de combiner types union et types intersection :</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">function</span> <span style="color:#a6e22e">process</span>(<span style="color:#a6e22e">Countable</span><span style="color:#f92672">&amp;</span><span style="color:#a6e22e">Iterator</span><span style="color:#f92672">|</span><span style="color:#66d9ef">null</span> $collection)<span style="color:#f92672">:</span> <span style="color:#a6e22e">void</span> { <span style="color:#f92672">...</span> }
</span></span></code></pre></div><p><code>(Countable&amp;Iterator)|null</code> : un objet qui implémente les deux interfaces, ou null. Cela couvre des expressions de type que les unions de 8.0 et les intersections de 8.1 approchaient chacune sans pouvoir les représenter ensemble.</p>
<h2 id="lextension-random">L&rsquo;extension Random</h2>
<p>Une extension <code>Random</code> dédiée remplace les fonctions éparpillées <code>rand()</code>, <code>mt_rand()</code>, <code>random_int()</code> par une API orientée objet :</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>$rng <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Random\Randomizer</span>();
</span></span><span style="display:flex;"><span>$rng<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">getInt</span>(<span style="color:#ae81ff">1</span>, <span style="color:#ae81ff">100</span>);
</span></span><span style="display:flex;"><span>$rng<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">shuffleArray</span>($items);
</span></span></code></pre></div><p>Les moteurs sont interchangeables : <code>Mersenne Twister</code>, <code>PCG64</code>, <code>Xoshiro256StarStar</code>, ou <code>CryptoSafeEngine</code> pour les contextes sensibles à la sécurité. Même code, moteur déterministe avec seed dans les tests, moteur cryptographique en production.</p>
<p>8.2 est une version de consolidation. La dépréciation des propriétés dynamiques est la seule décision à prendre maintenant.</p>
<h2 id="null-false-et-true-comme-types-autonomes"><code>null</code>, <code>false</code>, et <code>true</code> comme types autonomes</h2>
<p>PHP avait les types nullable depuis 7.1 et les types union depuis 8.0, mais <code>null</code> comme déclaration de type autonome n&rsquo;était pas valide. 8.2 corrige ça :</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">function</span> <span style="color:#a6e22e">alwaysNull</span>()<span style="color:#f92672">:</span> <span style="color:#66d9ef">null</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">null</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">function</span> <span style="color:#a6e22e">disabled</span>()<span style="color:#f92672">:</span> <span style="color:#66d9ef">false</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">false</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">function</span> <span style="color:#a6e22e">enabled</span>()<span style="color:#f92672">:</span> <span style="color:#66d9ef">true</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><code>false</code> et <code>true</code> comme types autonomes sont utiles quand on veut être précis sur ce qu&rsquo;une fonction peut réellement retourner. C&rsquo;est étroit mais juste : une fonction qui retourne <code>false</code> en cas d&rsquo;échec et une chaîne en cas de succès devrait déclarer <code>string|false</code>, et maintenant les deux côtés de cette union sont de vrais types.</p>
<h2 id="les-constantes-dans-les-traits">Les constantes dans les traits</h2>
<p>Les traits pouvaient contenir des propriétés et des méthodes. Les constantes étaient le trou dans la raquette. 8.2 le comble :</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">trait</span> <span style="color:#a6e22e">Timestamps</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">const</span> <span style="color:#66d9ef">DATE_FORMAT</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;Y-m-d H:i:s&#39;</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">formatCreatedAt</span>()<span style="color:#f92672">:</span> <span style="color:#a6e22e">string</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">createdAt</span><span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">format</span>(<span style="color:#a6e22e">self</span><span style="color:#f92672">::</span><span style="color:#a6e22e">DATE_FORMAT</span>);
</span></span><span style="display:flex;"><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">class</span> <span style="color:#a6e22e">Article</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">use</span> <span style="color:#a6e22e">Timestamps</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">echo</span> <span style="color:#a6e22e">Article</span><span style="color:#f92672">::</span><span style="color:#a6e22e">DATE_FORMAT</span>; <span style="color:#75715e">// &#39;Y-m-d H:i:s&#39;
</span></span></span></code></pre></div><p>La constante appartient à la classe qui utilise le trait, pas au trait lui-même, donc on ne peut pas accéder à <code>Timestamps::DATE_FORMAT</code> directement. Comportement de scope attendu, cohérent avec le fonctionnement des méthodes de trait.</p>
<h2 id="sensitiveparameter"><code>#[SensitiveParameter]</code></h2>
<p>Les stack traces ont toujours été un risque : les arguments de fonction sont loggés verbatim, ce qui veut dire que les mots de passe et les tokens se retrouvent dans les logs d&rsquo;erreur et les dashboards de monitoring. 8.2 ajoute un attribut pour stopper ça :</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">function</span> <span style="color:#a6e22e">authenticate</span>(
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">string</span> $user,
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">#[\SensitiveParameter] string $password,
</span></span></span><span style="display:flex;"><span>)<span style="color:#f92672">:</span> <span style="color:#a6e22e">bool</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// si ça lève une exception, la stack trace affiche :
</span></span></span><span style="display:flex;"><span>    <span style="color:#75715e">// authenticate(&#39;alice&#39;, Object(SensitiveParameterValue))
</span></span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">hash</span>(<span style="color:#e6db74">&#39;sha256&#39;</span>, $password) <span style="color:#f92672">===</span> <span style="color:#a6e22e">getStoredHash</span>($user);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>La valeur du paramètre dans la trace est remplacée par un objet <code>SensitiveParameterValue</code>. Un attribut, zéro excuse pour ne pas l&rsquo;ajouter sur chaque fonction qui touche à des credentials.</p>
<h2 id="les-syntaxes-dinterpolation-de-chaînes-dépréciées">Les syntaxes d&rsquo;interpolation de chaînes dépréciées</h2>
<p>Deux façons d&rsquo;interpoler des expressions dans des chaînes sont dépréciées en 8.2 :</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>$name <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;world&#39;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Celles-ci sont dépréciées :
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> <span style="color:#e6db74">&#34;Hello </span><span style="color:#e6db74">${</span>name<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>;       <span style="color:#75715e">// utiliser &#34;$name&#34; ou &#34;{$name}&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> <span style="color:#e6db74">&#34;Hello </span><span style="color:#e6db74">${</span>getName()<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>;  <span style="color:#75715e">// utiliser &#34;{$this-&gt;getName()}&#34;
</span></span></span></code></pre></div><p>Les formes <code>${...}</code> créaient une ambiguïté entre les variables variables et les expressions. La syntaxe plus propre <code>{$...}</code> a toujours été là et fait la même chose. C&rsquo;est essentiellement un travail de recherche-remplacement sur les bases de code qui ont adopté les formes dépréciées par habitude.</p>
<h2 id="utf8_encode-et-utf8_decode-dépréciées"><code>utf8_encode()</code> et <code>utf8_decode()</code> dépréciées</h2>
<p>Ces deux fonctions sont dépréciées en 8.2 et disparaissent en 9.0. Leur comportement a toujours été plus étroit que ce que les noms suggéraient : <code>utf8_encode()</code> convertit ISO-8859-1 en UTF-8, pas &ldquo;n&rsquo;importe quel encodage en UTF-8&rdquo;.</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">// Déprécié en 8.2 :
</span></span></span><span style="display:flex;"><span>$utf8 <span style="color:#f92672">=</span> <span style="color:#a6e22e">utf8_encode</span>($latin1String);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Utiliser à la place :
</span></span></span><span style="display:flex;"><span>$utf8 <span style="color:#f92672">=</span> <span style="color:#a6e22e">mb_convert_encoding</span>($latin1String, <span style="color:#e6db74">&#39;UTF-8&#39;</span>, <span style="color:#e6db74">&#39;ISO-8859-1&#39;</span>);
</span></span></code></pre></div><p><code>mb_convert_encoding()</code> ou <code>iconv()</code> gèrent le cas général. Si vous traitez vraiment des entrées en Latin-1, le remplacement est direct.</p>
<h2 id="les-fonctions-de-chaîne-indépendantes-de-la-locale">Les fonctions de chaîne indépendantes de la locale</h2>
<p>Plusieurs fonctions de chaîne variaient silencieusement leur comportement selon la locale système, produisant des résultats différents en production par rapport à un container de dev. En 8.2, elles sont indépendantes de la locale et ne gèrent que l&rsquo;ASCII :</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">// strtolower, strtoupper, stristr, stripos, strripos,
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">// lcfirst, ucfirst, ucwords, str_ireplace font maintenant une conversion
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">// de casse ASCII uniquement.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">// Pour un comportement sensible à la locale, utiliser les équivalents mb_* :
</span></span></span><span style="display:flex;"><span>$lowered <span style="color:#f92672">=</span> <span style="color:#a6e22e">mb_strtolower</span>($text, <span style="color:#e6db74">&#39;UTF-8&#39;</span>);
</span></span></code></pre></div><p>C&rsquo;est un correctif de cohérence. Si votre code reposait sur le comportement sensible à la locale de ces fonctions, il était déjà cassé sur des systèmes avec des configurations de locale différentes. 8.2 rend le comportement déterministe partout, ce qui est ce qu&rsquo;on voulait vraiment.</p>
<h2 id="str_split-sur-une-chaîne-vide"><code>str_split()</code> sur une chaîne vide</h2>
<p>Un changement de comportement discret à noter :</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">// PHP 8.1 : str_split(&#39;&#39;) === [&#39;&#39;]
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">// PHP 8.2 : str_split(&#39;&#39;) === []
</span></span></span></code></pre></div><p>Le nouveau comportement a plus de sens : découper rien ne produit rien. Si vous vérifiez <code>count(str_split($input))</code>, une entrée vide ne produit plus un count de 1.</p>
]]></content:encoded></item><item><title>API Platform 3.0 : un nouveau modèle d'état et la fin des DataProviders</title><link>https://guillaumedelre.github.io/fr/2022/11/18/api-platform-3.0-un-nouveau-mod%C3%A8le-d%C3%A9tat-et-la-fin-des-dataproviders/</link><pubDate>Fri, 18 Nov 2022 00:00:00 +0000</pubDate><guid>https://guillaumedelre.github.io/fr/2022/11/18/api-platform-3.0-un-nouveau-mod%C3%A8le-d%C3%A9tat-et-la-fin-des-dataproviders/</guid><description>Part 1 of 8 in &amp;quot;Sorties API Platform&amp;quot;: API Platform 3.0 a remplacé les DataProviders et DataPersisters par un modèle d&amp;#39;état qui rend les opérations HTTP explicites — et a exigé PHP 8.1 et Symfony 6 pour y arriver.</description><category>api-platform-releases</category><content:encoded><![CDATA[<p>API Platform 3.0 est arrivé en septembre 2022 avec Symfony 6.1 comme prérequis strict et une architecture de base qui ne ressemblait en rien à la 2.x. Le guide de migration est long. La raison pour laquelle il est long est intéressante.</p>
<p>L&rsquo;ancien modèle avait une fuite conceptuelle. <code>DataProviderInterface</code> et <code>DataPersisterInterface</code> étaient appelés pour chaque requête HTTP, mais le provider recevait le contexte de l&rsquo;opération comme un indice — pas comme un contrat. Un provider de collection et un provider d&rsquo;item étaient des interfaces distinctes, mais les deux vivaient dans le même seau mental : &ldquo;choses qui retournent des données.&rdquo; La couche HTTP savait ce qui était demandé ; le provider devait reconstruire cette connaissance à partir d&rsquo;indices passés dans le tableau <code>$context</code>.</p>
<p>La 3.0 inverse le modèle. Les opérations sont déclarées en premier. L&rsquo;accès aux données est câblé aux opérations.</p>
<h2 id="les-state-providers-remplacent-les-data-providers">Les state providers remplacent les data providers</h2>
<p>L&rsquo;ancien <code>DataProviderInterface</code> a disparu. Le remplaçant est <code>ProviderInterface</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">ApiPlatform\State\ProviderInterface</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">ApiPlatform\Metadata\Operation</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">BookProvider</span> <span style="color:#66d9ef">implements</span> <span style="color:#a6e22e">ProviderInterface</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">provide</span>(<span style="color:#a6e22e">Operation</span> $operation, <span style="color:#66d9ef">array</span> $uriVariables <span style="color:#f92672">=</span> [], <span style="color:#66d9ef">array</span> $context <span style="color:#f92672">=</span> [])<span style="color:#f92672">:</span> <span style="color:#a6e22e">object</span><span style="color:#f92672">|</span><span style="color:#66d9ef">array</span><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 style="color:#66d9ef">if</span> ($operation <span style="color:#a6e22e">instanceof</span> <span style="color:#a6e22e">CollectionOperationInterface</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">repository</span><span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">findAll</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">return</span> $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">repository</span><span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">find</span>($uriVariables[<span style="color:#e6db74">&#39;id&#39;</span>]);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>La différence n&rsquo;est pas syntaxique. En 2.x, on enregistrait un provider et API Platform l&rsquo;appelait pour toute ressource correspondante. En 3.0, on lie un provider à une opération spécifique. Le provider n&rsquo;a plus à deviner ce qui l&rsquo;a déclenché — l&rsquo;objet opération qu&rsquo;il reçoit est le contrat.</p>
<h2 id="les-state-processors-remplacent-les-data-persisters">Les state processors remplacent les data persisters</h2>
<p><code>DataPersisterInterface</code> avait le même problème côté écriture : une seule classe gérant la création, la mise à jour et la suppression, les distinguant en inspectant la méthode HTTP ou l&rsquo;état de l&rsquo;objet. <code>ProcessorInterface</code> reçoit l&rsquo;opération comme argument typé :</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">ApiPlatform\State\ProcessorInterface</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">ApiPlatform\Metadata\Operation</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">BookProcessor</span> <span style="color:#66d9ef">implements</span> <span style="color:#a6e22e">ProcessorInterface</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">process</span>(<span style="color:#a6e22e">mixed</span> $data, <span style="color:#a6e22e">Operation</span> $operation, <span style="color:#66d9ef">array</span> $uriVariables <span style="color:#f92672">=</span> [], <span style="color:#66d9ef">array</span> $context <span style="color:#f92672">=</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">entityManager</span><span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">persist</span>($data);
</span></span><span style="display:flex;"><span>        $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">entityManager</span><span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">flush</span>();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> $data;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Plus utile encore : on peut lier un processor différent par opération. L&rsquo;opération de suppression en reçoit un qui supprime. L&rsquo;opération de création en reçoit un qui valide et stocke. Pas de switch, pas d&rsquo;inspection de méthode, pas de classe partagée qui essaie d&rsquo;être trois choses à la fois.</p>
<h2 id="les-opérations-déclarées-explicitement-en-attributs-php-81">Les opérations déclarées explicitement en attributs PHP 8.1</h2>
<p>L&rsquo;autre moitié de la 3.0 est la couche de métadonnées. Les annotations Doctrine sont remplacées par des attributs natifs PHP 8.1, et chaque opération est déclarée explicitement sur la classe ressource :</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">ApiPlatform\Metadata\ApiResource</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">ApiPlatform\Metadata\Get</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">ApiPlatform\Metadata\GetCollection</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">ApiPlatform\Metadata\Post</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#[ApiResource(
</span></span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">operations</span><span style="color:#f92672">:</span> [
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">GetCollection</span>(<span style="color:#a6e22e">provider</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">BookProvider</span><span style="color:#f92672">::</span><span style="color:#a6e22e">class</span>),
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Get</span>(<span style="color:#a6e22e">provider</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">BookProvider</span><span style="color:#f92672">::</span><span style="color:#a6e22e">class</span>),
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Post</span>(<span style="color:#a6e22e">processor</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">BookProcessor</span><span style="color:#f92672">::</span><span style="color:#a6e22e">class</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">class</span> <span style="color:#a6e22e">Book</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></span></code></pre></div><p>C&rsquo;est plus verbeux que <code>@ApiResource</code> avec ses valeurs par défaut magiques. C&rsquo;est aussi explicite. On sait exactement quelles opérations HTTP existent pour cette ressource, ce qui récupère les données, ce qui les écrit, et où vit la logique. Les valeurs par défaut de la 2.x étaient pratiques jusqu&rsquo;au jour où il fallait en surcharger une sans réussir à déterminer quel service décorer sans lire le code source.</p>
<h2 id="php-81-nétait-pas-un-hasard">PHP 8.1 n&rsquo;était pas un hasard</h2>
<p>L&rsquo;exigence stricte pour PHP 8.1 est structurante. Les callables de première classe rendent l&rsquo;enregistrement des filtres plus propre. L&rsquo;immuabilité des métadonnées d&rsquo;opération est assurée par un pattern de clonage (méthodes <code>withX()</code>) qui s&rsquo;appuie sur les arguments nommés et les propriétés de constructeur promues — des fondations PHP 8.0 sur lesquelles l&rsquo;architecture s&rsquo;appuie massivement.</p>
<p>Plus concrètement : l&rsquo;expression complète de l&rsquo;architecture 3.0 — opérations typées, providers scopés à l&rsquo;opération, métadonnées explicites — avait besoin de la 8.1 pour ne pas ressembler à des contournements. Abandonner PHP 7.x et 8.0 n&rsquo;était pas une décision de nettoyage.</p>
<h2 id="la-migration-est-un-vrai-travail">La migration est un vrai travail</h2>
<p>Le passage de 2.x à 3.0 n&rsquo;est pas un simple bump de version. Chaque <code>DataProvider</code> devient un <code>ProviderInterface</code>. Chaque <code>DataPersister</code> devient un <code>ProcessorInterface</code>. Les annotations deviennent des attributs. Les normaliseurs et filtres personnalisés peuvent nécessiter une restructuration. Le guide de mise à jour documente tout cela, mais &ldquo;documenté&rdquo; ne veut pas dire &ldquo;rapide.&rdquo;</p>
<p>Ce qu&rsquo;on obtient de l&rsquo;autre côté est une architecture qui passe à l&rsquo;échelle sans la complexité ambiante de la 2.x : plus besoin de deviner quelle interface implémenter, plus de chaînes <code>$this-&gt;supports()</code>, plus de valeurs par défaut invisibles qui surchargent silencieusement la config explicite.</p>
<p>La 3.0 est l&rsquo;API Platform qu&rsquo;on concevrait de zéro en sachant ce qu&rsquo;on sait après des années de 2.x. Le prix est la migration. Le numéro de version est honnête là-dessus.</p>
]]></content:encoded></item><item><title>PHP 8.1 : enums, fibers, et un système de types qui grandit</title><link>https://guillaumedelre.github.io/fr/2022/01/09/php-8.1-enums-fibers-et-un-syst%C3%A8me-de-types-qui-grandit/</link><pubDate>Sun, 09 Jan 2022 00:00:00 +0000</pubDate><guid>https://guillaumedelre.github.io/fr/2022/01/09/php-8.1-enums-fibers-et-un-syst%C3%A8me-de-types-qui-grandit/</guid><description>Part 7 of 11 in &amp;quot;Sorties PHP&amp;quot;: PHP 8.1 apporte les enums natifs, les fibers pour la concurrence coopérative, les propriétés readonly, et les types d&amp;#39;intersection.</description><category>php-releases</category><content:encoded><![CDATA[<p>PHP 8.1 est sorti le 25 novembre. Il fait suite à la refonte massive de 8.0 avec quelque chose de différent : moins de fonctionnalités, mais chacune vraiment réfléchie plutôt que greffée à la va-vite.</p>
<h2 id="les-enums">Les enums</h2>
<p>C&rsquo;est la nouveauté qui change les bases de code dès la mise à jour. Avant 8.1, les énumérations en PHP se résumaient à des constantes de classe, des chaînes ou des entiers sans rien pour les faire respecter :</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 : rien n&#39;empêche de passer Status::INVALID
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#66d9ef">ACTIVE</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;active&#39;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#66d9ef">INACTIVE</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;inactive&#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><span style="color:#a6e22e">enum</span> <span style="color:#a6e22e">Status</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">string</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">case</span> <span style="color:#a6e22e">Active</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;active&#39;</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">case</span> <span style="color:#a6e22e">Inactive</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;inactive&#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">function</span> <span style="color:#a6e22e">activate</span>(<span style="color:#a6e22e">Status</span> $status)<span style="color:#f92672">:</span> <span style="color:#a6e22e">void</span> { <span style="color:#f92672">...</span> }
</span></span></code></pre></div><p>Les enums PHP sont des objets, pas des scalaires. Ils supportent les méthodes, les interfaces et les constantes. Les backed enums (avec une valeur string ou int) se sérialisent proprement et se mappent naturellement aux colonnes de base de données. Les pure enums (sans type de backing) expriment des concepts métier sans se soucier de la sérialisation.</p>
<p>L&rsquo;effet immédiat : chaque champ de statut, chaque ensemble fini d&rsquo;états dans toutes les bases de code que je maintiens est devenu un candidat à l&rsquo;enum. Le système de types a enfin un moyen natif d&rsquo;exprimer ce que chaque projet PHP simulait depuis des années.</p>
<h2 id="les-fibers">Les fibers</h2>
<p>Les fibers sont une primitive de concurrence coopérative : vous pouvez suspendre et reprendre l&rsquo;exécution d&rsquo;une fonction, en cédant le contrôle sans recourir aux threads.</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>$fiber <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Fiber</span>(<span style="color:#66d9ef">function</span>()<span style="color:#f92672">:</span> <span style="color:#a6e22e">void</span> {
</span></span><span style="display:flex;"><span>    $value <span style="color:#f92672">=</span> <span style="color:#a6e22e">Fiber</span><span style="color:#f92672">::</span><span style="color:#a6e22e">suspend</span>(<span style="color:#e6db74">&#39;first&#39;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">echo</span> <span style="color:#e6db74">&#34;Repris avec : </span><span style="color:#e6db74">{</span>$value<span style="color:#e6db74">}</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>;
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>$result <span style="color:#f92672">=</span> $fiber<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">start</span>();    <span style="color:#75715e">// &#39;first&#39;
</span></span></span><span style="display:flex;"><span>$fiber<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">resume</span>(<span style="color:#e6db74">&#39;hello&#39;</span>);      <span style="color:#75715e">// &#34;Repris avec : hello&#34;
</span></span></span></code></pre></div><p>Les fibers sont la fondation dont les bibliothèques async comme ReactPHP et Amp avaient besoin depuis un moment du côté du runtime. Pour la plupart des développeurs d&rsquo;applications, l&rsquo;API directe compte moins que les bibliothèques construites par-dessus, mais comprendre les fibers explique ce que font ces bibliothèques en coulisses.</p>
<h2 id="pencil2-les-propriétés-readonly">:pencil2: Les propriétés readonly</h2>
<p>8.0 avait apporté la promotion des paramètres du constructeur. 8.1 ajoute <code>readonly</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">class</span> <span style="color:#a6e22e">User</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">readonly</span> <span style="color:#a6e22e">int</span> $id,
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">public</span> <span style="color:#a6e22e">readonly</span> <span style="color:#a6e22e">string</span> $name,
</span></span><span style="display:flex;"><span>    ) {}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Une propriété <code>readonly</code> ne peut être écrite qu&rsquo;une seule fois, lors de l&rsquo;initialisation. Après ça, toute écriture lève une <code>Error</code>. Combiné avec la promotion des paramètres, les value objects et les DTOs deviennent concis et signifient réellement ce qu&rsquo;ils annoncent.</p>
<h2 id="la-syntaxe-callable-de-première-classe">La syntaxe callable de première classe</h2>
<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>$fn <span style="color:#f92672">=</span> <span style="color:#a6e22e">strlen</span>(<span style="color:#f92672">...</span>);
</span></span><span style="display:flex;"><span>$fn <span style="color:#f92672">=</span> $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">process</span>(<span style="color:#f92672">...</span>);
</span></span><span style="display:flex;"><span>$fn <span style="color:#f92672">=</span> <span style="color:#a6e22e">MyClass</span><span style="color:#f92672">::</span><span style="color:#a6e22e">create</span>(<span style="color:#f92672">...</span>);
</span></span></code></pre></div><p><code>...</code> après un callable crée une <code>Closure</code> sans le boilerplate de <code>Closure::fromCallable()</code>. Utile quand on passe des méthodes comme callbacks.</p>
<p>8.1 est précis. Les enums justifient à eux seuls la mise à jour.</p>
<h2 id="les-types-dintersection">Les types d&rsquo;intersection</h2>
<p>Les types union ont débarqué en 8.0. Les types d&rsquo;intersection suivent en 8.1. Là où un union dit « l&rsquo;un ou l&rsquo;autre », une intersection dit « tous à la fois » :</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">function</span> <span style="color:#a6e22e">process</span>(<span style="color:#a6e22e">Countable</span><span style="color:#f92672">&amp;</span><span style="color:#a6e22e">Iterator</span> $collection)<span style="color:#f92672">:</span> <span style="color:#a6e22e">void</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">foreach</span> ($collection <span style="color:#66d9ef">as</span> $item) { <span style="color:#75715e">/* ... */</span> }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">echo</span> <span style="color:#a6e22e">count</span>($collection);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Une contrainte : les types d&rsquo;intersection ne peuvent pas être mélangés avec les types union dans la même déclaration (ça arrivera en 8.2 avec les DNF types). Mais ça débloque déjà une vérification de types précise pour les objets qui doivent satisfaire plusieurs interfaces à la fois, un pattern que les frameworks utilisent constamment et qui devait rester sans typage jusqu&rsquo;ici.</p>
<h2 id="le-type-de-retour-never">Le type de retour <code>never</code></h2>
<p>Une fonction qui ne retourne jamais (elle lève toujours une exception ou sort) a maintenant un type pour le dire :</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">function</span> <span style="color:#a6e22e">redirect</span>(<span style="color:#a6e22e">string</span> $url)<span style="color:#f92672">:</span> <span style="color:#a6e22e">never</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">header</span>(<span style="color:#e6db74">&#34;Location: </span><span style="color:#e6db74">{</span>$url<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">exit</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">function</span> <span style="color:#a6e22e">fail</span>(<span style="color:#a6e22e">string</span> $message)<span style="color:#f92672">:</span> <span style="color:#a6e22e">never</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">throw</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">\RuntimeException</span>($message);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>L&rsquo;avantage concret : les analyseurs statiques peuvent prouver que le code après une fonction <code>never</code> est inatteignable, et les appelants savent qu&rsquo;il n&rsquo;y a pas de valeur de retour à gérer. Avant ça, ça vivait dans des docblocks sans enforcement.</p>
<h2 id="les-constantes-de-classe-finales">Les constantes de classe finales</h2>
<p>Avant 8.1, n&rsquo;importe quelle sous-classe pouvait silencieusement surcharger la constante de classe d&rsquo;un parent. Maintenant vous pouvez y mettre un terme :</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">Base</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">final</span> <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">const</span> <span style="color:#66d9ef">VERSION</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;1.0&#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">class</span> <span style="color:#a6e22e">Child</span> <span style="color:#66d9ef">extends</span> <span style="color:#a6e22e">Base</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Fatal error: Cannot override final constant Base::VERSION
</span></span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">const</span> <span style="color:#66d9ef">VERSION</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;2.0&#39;</span>;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Parallèlement, les constantes d&rsquo;interface sont désormais surchargeables par les classes implémentant l&rsquo;interface par défaut. Un correctif de comportement qui était incohérent depuis l&rsquo;introduction des interfaces.</p>
<h2 id="new-dans-les-initialiseurs"><code>new</code> dans les initialiseurs</h2>
<p>Les valeurs par défaut des paramètres étaient autrefois limitées aux scalaires et aux tableaux. 8.1 lève cette restriction :</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">Logger</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">Handler</span> $handler <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">NullHandler</span>(),
</span></span><span style="display:flex;"><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">function</span> <span style="color:#a6e22e">createUser</span>(
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Validator</span> $validator <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">DefaultValidator</span>(),
</span></span><span style="display:flex;"><span>)<span style="color:#f92672">:</span> <span style="color:#a6e22e">User</span> { <span style="color:#75715e">/* ... */</span> }
</span></span></code></pre></div><p>Idem pour les arguments d&rsquo;attributs et les initialiseurs de variables statiques. Ce qui signifie que l&rsquo;injection de dépendances avec des valeurs par défaut sensées ne nécessite plus une vérification de null et une instanciation paresseuse dans le corps de la méthode.</p>
<h2 id="le-déballage-de-tableaux-avec-des-clés-string">Le déballage de tableaux avec des clés string</h2>
<p>Le déballage de tableau via l&rsquo;opérateur spread ne fonctionnait qu&rsquo;avec des tableaux à clés entières avant 8.1. Les clés string fonctionnent aussi maintenant :</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>$defaults <span style="color:#f92672">=</span> [<span style="color:#e6db74">&#39;color&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#39;red&#39;</span>, <span style="color:#e6db74">&#39;size&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#39;M&#39;</span>];
</span></span><span style="display:flex;"><span>$custom <span style="color:#f92672">=</span> [<span style="color:#e6db74">&#39;size&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#39;L&#39;</span>, <span style="color:#e6db74">&#39;weight&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#39;200g&#39;</span>];
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>$merged <span style="color:#f92672">=</span> [<span style="color:#f92672">...</span>$defaults, <span style="color:#f92672">...</span>$custom];
</span></span><span style="display:flex;"><span><span style="color:#75715e">// [&#39;color&#39; =&gt; &#39;red&#39;, &#39;size&#39; =&gt; &#39;L&#39;, &#39;weight&#39; =&gt; &#39;200g&#39;]
</span></span></span></code></pre></div><p>Les clés ultérieures écrasent les précédentes. Même comportement que <code>array_merge()</code>, mais exprimé inline. La différence de performance est marginale ; la différence de lisibilité, elle, ne l&rsquo;est pas.</p>
<h2 id="fsync-et-fdatasync"><code>fsync</code> et <code>fdatasync</code></h2>
<p>Deux fonctions qui n&rsquo;avaient aucune bonne raison d&rsquo;être absentes d&rsquo;un langage orienté système de fichiers :</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>$fp <span style="color:#f92672">=</span> <span style="color:#a6e22e">fopen</span>(<span style="color:#e6db74">&#39;/tmp/important.dat&#39;</span>, <span style="color:#e6db74">&#39;w&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">fwrite</span>($fp, $data);
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">fsync</span>($fp);   <span style="color:#75715e">// vide les buffers OS vers le stockage physique
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">fclose</span>($fp);
</span></span></code></pre></div><p><code>fdatasync()</code> fait la même chose mais saute la synchronisation des métadonnées quand on ne se soucie que de la durabilité des données. Les deux retournent <code>false</code> en cas d&rsquo;échec. Si vous écrivez quoi que ce soit qui nécessite une sécurité en cas de crash, vous aviez besoin de ça.</p>
<h2 id="passer-null-aux-paramètres-non-nullables-des-fonctions-internes">Passer <code>null</code> aux paramètres non-nullables des fonctions internes</h2>
<p>Un changement plus discret mais aux conséquences réelles : les fonctions internes qui acceptent des chaînes, des entiers, etc. ont toujours avalé silencieusement <code>null</code> et l&rsquo;ont coercé. En 8.1, ça commence à émettre un avertissement de dépréciation.</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">str_contains</span>(<span style="color:#e6db74">&#34;foobar&#34;</span>, <span style="color:#66d9ef">null</span>);
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Deprecated: Passing null to parameter #2 ($needle) of type string is deprecated
</span></span></span></code></pre></div><p>Ça aligne les fonctions internes sur les fonctions définies par l&rsquo;utilisateur, qui refusaient déjà les arguments nullable pour des paramètres non-nullables. PHP 9.0 transforme ça en erreur fatale. Si vous passez <code>null</code> dans des fonctions de chaînes, c&rsquo;est maintenant un meilleur moment pour le corriger que pendant un incident de production.</p>
<h2 id="mysqli-lève-des-exceptions-par-défaut">MySQLi lève des exceptions par défaut</h2>
<p>Avant 8.1, MySQLi échouait silencieusement sauf si vous appeliez explicitement <code>mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT)</code>. C&rsquo;est maintenant la valeur par défaut :</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">// Ceci lève \mysqli_sql_exception en cas d&#39;échec de connexion en 8.1
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">// Auparavant retournait false et définissait une erreur que vous deviez vérifier manuellement
</span></span></span><span style="display:flex;"><span>$connection <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">mysqli</span>(<span style="color:#e6db74">&#39;localhost&#39;</span>, <span style="color:#e6db74">&#39;user&#39;</span>, <span style="color:#e6db74">&#39;wrong_password&#39;</span>, <span style="color:#e6db74">&#39;db&#39;</span>);
</span></span></code></pre></div><p>Toute base de code qui attrape les erreurs MySQLi en vérifiant les valeurs de retour doit être revue. Les échecs silencieux qui causaient des bugs difficiles à diagnostiquer lèvent maintenant des exceptions, ce qui est le bon comportement, même si ça peut surprendre si vous l&rsquo;attrapez en pleine mise à jour.</p>
]]></content:encoded></item><item><title>PHP 8.0 : match, arguments nommés, attributs et JIT</title><link>https://guillaumedelre.github.io/fr/2021/01/10/php-8.0-match-arguments-nomm%C3%A9s-attributs-et-jit/</link><pubDate>Sun, 10 Jan 2021 00:00:00 +0000</pubDate><guid>https://guillaumedelre.github.io/fr/2021/01/10/php-8.0-match-arguments-nomm%C3%A9s-attributs-et-jit/</guid><description>Part 6 of 11 in &amp;quot;Sorties PHP&amp;quot;: PHP 8.0 remodèle le langage : compilateur JIT, arguments nommés, expressions match, types union et opérateur nullsafe.</description><category>php-releases</category><content:encoded><![CDATA[<p>PHP 8.0 est sorti le 26 novembre. Je le fais tourner depuis six semaines sur un projet perso et un nouveau service au boulot. C&rsquo;est la version PHP la plus significative depuis 7.0, et à certains égards plus impactante, parce que les changements se renforcent mutuellement de façon utile.</p>
<h2 id="jit">JIT</h2>
<p>Le compilateur Just-In-Time était l&rsquo;annonce principale. La réalité en production est plus nuancée : pour les applications web typiques (requêtes en base, appels HTTP, rendu de templates) les gains sont modestes, parce que ces workloads sont limités par les I/O, pas par le calcul. Là où le JIT brille vraiment, c&rsquo;est le code intensif en CPU : manipulation d&rsquo;images, transformation de données, calcul mathématique.</p>
<p>Pour la plupart des applications web, l&rsquo;amélioration de performance vient du travail sur le moteur en général dans 8.0, pas du JIT spécifiquement. Vaut quand même la peine de l&rsquo;activer : ça ne coûte rien sur les workloads I/O-bound.</p>
<h2 id="les-expressions-match">Les expressions match</h2>
<p><code>switch</code> a trois problèmes : il utilise la comparaison souple, il tombe en cascade par défaut, et il ne peut pas être utilisé comme expression. <code>match</code> règle les trois :</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>$result <span style="color:#f92672">=</span> <span style="color:#a6e22e">match</span>($status) {
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#39;active&#39;</span>, <span style="color:#e6db74">&#39;pending&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#39;processing&#39;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#39;done&#39;</span>             <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#39;finished&#39;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">default</span>            <span style="color:#f92672">=&gt;</span> <span style="color:#66d9ef">throw</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">\UnexpectedValueException</span>($status),
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p>Comparaison stricte. Pas de cascade. Expression qui retourne une valeur. Un match non exhaustif lève une exception. Après une semaine avec <code>match</code>, j&rsquo;ai arrêté d&rsquo;écrire des <code>switch</code>.</p>
<h2 id="les-arguments-nommés">Les arguments nommés</h2>
<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">array_slice</span>(<span style="color:#66d9ef">array</span><span style="color:#f92672">:</span> $users, <span style="color:#a6e22e">offset</span><span style="color:#f92672">:</span> <span style="color:#ae81ff">0</span>, <span style="color:#a6e22e">length</span><span style="color:#f92672">:</span> <span style="color:#ae81ff">10</span>, <span style="color:#a6e22e">preserve_keys</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">true</span>);
</span></span></code></pre></div><p>Les arguments nommés permettent de passer les arguments dans n&rsquo;importe quel ordre et d&rsquo;en sauter des optionnels. Le gain évident : la lisibilité sur les fonctions avec plusieurs flags booléens. Le gain moins évident : les arguments nommés survivent aux mises à jour de PHP même quand l&rsquo;ordre des paramètres change, parce qu&rsquo;on nomme ce qu&rsquo;on veut dire.</p>
<h2 id="les-attributs">Les attributs</h2>
<p>Place aux docblock annotations (le style <code>@Route</code>, <code>@ORM\Column</code> sur lequel les frameworks se sont appuyés pendant des années), bienvenue à la syntaxe PHP de première classe :</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">#[Route(&#39;/users&#39;, methods: [&#39;GET&#39;])]
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#[IsGranted(&#39;ROLE_ADMIN&#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">list</span>()<span style="color:#f92672">:</span> <span style="color:#a6e22e">Response</span> { <span style="color:#f92672">...</span> }
</span></span></code></pre></div><p>Les attributs sont validés par le moteur, pas parsés depuis des strings. Le support IDE fonctionne directement, sans magie de plugin. Pour les utilisateurs de Symfony et Doctrine, c&rsquo;est le vrai gain quotidien de PHP 8.0.</p>
<h2 id="la-promotion-de-constructeur">La promotion de constructeur</h2>
<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">User</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">readonly</span> <span style="color:#a6e22e">int</span> $id,
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">public</span> <span style="color:#a6e22e">string</span> $name,
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">private</span> <span style="color:#f92672">?</span><span style="color:#a6e22e">string</span> $email <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><p>Propriétés déclarées et assignées en une ligne dans la signature du constructeur. Le gain de refactoring le plus immédiat dans 8.0 : chaque classe de données que j&rsquo;ai touchée depuis la mise à jour fait moitié moins de lignes qu&rsquo;avant.</p>
<h2 id="lopérateur-nullsafe">L&rsquo;opérateur nullsafe</h2>
<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>$city <span style="color:#f92672">=</span> $user<span style="color:#f92672">?-&gt;</span><span style="color:#a6e22e">getAddress</span>()<span style="color:#f92672">?-&gt;</span><span style="color:#a6e22e">getCity</span>()<span style="color:#f92672">?-&gt;</span><span style="color:#a6e22e">getName</span>();
</span></span></code></pre></div><p><code>null</code> à n&rsquo;importe quel point de la chaîne court-circuite le reste et retourne <code>null</code>. L&rsquo;alternative était des null checks imbriqués ou une chaîne de retours anticipés. Ça se compose naturellement.</p>
<h2 id="les-types-union">Les types union</h2>
<p>Les arguments nommés rendent les signatures de fonctions plus explicites au site d&rsquo;appel. Les types union les rendent plus honnêtes au site de déclaration :</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">function</span> <span style="color:#a6e22e">processInput</span>(<span style="color:#a6e22e">int</span><span style="color:#f92672">|</span><span style="color:#a6e22e">float</span><span style="color:#f92672">|</span><span style="color:#a6e22e">string</span> $value)<span style="color:#f92672">:</span> <span style="color:#a6e22e">string</span><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:#66d9ef">if</span> (<span style="color:#a6e22e">is_string</span>($value)) {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">strlen</span>($value);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> (<span style="color:#a6e22e">int</span>) <span style="color:#a6e22e">round</span>($value);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>L&rsquo;union <code>int|float|string</code> est un OU littéral. Le moteur l&rsquo;impose à l&rsquo;entrée et à la sortie. Avant 8.0, &ldquo;ce paramètre accepte int ou float&rdquo; vivait dans un docblock que rien n&rsquo;imposait. Il y a aussi <code>null</code> comme composant de type : <code>?string</code> est juste du sucre syntaxique pour <code>string|null</code>, les deux sont valides.</p>
<p>Un cas spécial : <code>false</code>. PHP a un tas de fonctions natives qui retournent une valeur typée en cas de succès et <code>false</code> en cas d&rsquo;échec. Le système de types de 8.0 accommode ça : <code>array|false</code>, <code>string|false</code>. C&rsquo;est une reconnaissance honnête que la codebase ne peut pas être réécrite du jour au lendemain.</p>
<h2 id="le-type-de-retour-static">Le type de retour static</h2>
<p><code>static</code> comme type de retour était possible de manière informelle via les docblocks, mais 8.0 le rend officiel. La distinction entre <code>self</code> et <code>static</code> compte dans l&rsquo;héritage :</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">Builder</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">protected</span> <span style="color:#66d9ef">array</span> $config <span style="color:#f92672">=</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">set</span>(<span style="color:#a6e22e">string</span> $key, <span style="color:#a6e22e">mixed</span> $value)<span style="color:#f92672">:</span> <span style="color:#66d9ef">static</span> {
</span></span><span style="display:flex;"><span>        $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">config</span>[$key] <span style="color:#f92672">=</span> $value;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> $this;
</span></span><span style="display:flex;"><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">class</span> <span style="color:#a6e22e">SpecialBuilder</span> <span style="color:#66d9ef">extends</span> <span style="color:#a6e22e">Builder</span> {}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>$result <span style="color:#f92672">=</span> (<span style="color:#66d9ef">new</span> <span style="color:#a6e22e">SpecialBuilder</span>())<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">set</span>(<span style="color:#e6db74">&#39;foo&#39;</span>, <span style="color:#e6db74">&#39;bar&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#75715e">// $result est SpecialBuilder, pas Builder
</span></span></span></code></pre></div><p>Avec <code>self</code> comme type de retour, cette chaîne retournerait <code>Builder</code>, cassant les interfaces fluides dans les sous-classes. <code>static</code> fait fonctionner correctement les APIs fluides à travers les hiérarchies d&rsquo;héritage sans surcharges manuelles.</p>
<h2 id="le-type-mixed">Le type mixed</h2>
<p><code>mixed</code> était une convention de docblock pendant des années. 8.0 en fait un vrai type qui apparaît dans les signatures :</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">function</span> <span style="color:#a6e22e">debug</span>(<span style="color:#a6e22e">mixed</span> $value)<span style="color:#f92672">:</span> <span style="color:#a6e22e">void</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">var_dump</span>($value);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Il accepte tout : <code>null</code>, objets, ressources, scalaires, tableaux. Sémantiquement c&rsquo;est la même chose que n&rsquo;avoir aucune déclaration de type, mais c&rsquo;est explicite plutôt qu&rsquo;absent. La différence entre &ldquo;ce paramètre est non typé&rdquo; et &ldquo;ce paramètre accepte intentionnellement n&rsquo;importe quoi.&rdquo; Vaut la peine de l&rsquo;utiliser quand on écrit un utilitaire généraliste qui serait malhonnête avec un type plus étroit.</p>
<h2 id="throw-comme-expression">throw comme expression</h2>
<p>Avant 8.0, <code>throw</code> était une instruction. Ça semble une distinction pédante jusqu&rsquo;à ce qu&rsquo;on tombe sur les endroits où on veut vraiment une expression :</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">// Dans un ternaire :
</span></span></span><span style="display:flex;"><span>$value <span style="color:#f92672">=</span> $input <span style="color:#f92672">??</span> <span style="color:#66d9ef">throw</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">\InvalidArgumentException</span>(<span style="color:#e6db74">&#39;input required&#39;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Dans une arrow function :
</span></span></span><span style="display:flex;"><span>$getId <span style="color:#f92672">=</span> <span style="color:#a6e22e">fn</span>(<span style="color:#a6e22e">User</span> $u) <span style="color:#f92672">=&gt;</span> $u<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">id</span> <span style="color:#f92672">??</span> <span style="color:#66d9ef">throw</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">\RuntimeException</span>(<span style="color:#e6db74">&#39;no id&#39;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Dans un bras match (qui est déjà une expression) :
</span></span></span><span style="display:flex;"><span>$status <span style="color:#f92672">=</span> <span style="color:#a6e22e">match</span>($code) {
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">200</span>     <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#39;ok&#39;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">404</span>     <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#39;not found&#39;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">default</span> <span style="color:#f92672">=&gt;</span> <span style="color:#66d9ef">throw</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">\UnexpectedValueException</span>(<span style="color:#e6db74">&#34;unknown code: </span><span style="color:#e6db74">$code</span><span style="color:#e6db74">&#34;</span>),
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p>Le dernier est particulièrement utile : un match sans default lancera <code>UnhandledMatchError</code> automatiquement, mais parfois on veut contrôler le type d&rsquo;exception et le message.</p>
<h2 id="catch-sans-variable">catch sans variable</h2>
<p>Petite amélioration de qualité de vie. Quand on attrape une exception mais qu&rsquo;on n&rsquo;utilise pas réellement l&rsquo;objet, 8.0 permet d&rsquo;omettre la variable :</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">try</span> {
</span></span><span style="display:flex;"><span>    $result <span style="color:#f92672">=</span> $cache<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">get</span>($key);
</span></span><span style="display:flex;"><span>} <span style="color:#66d9ef">catch</span> (<span style="color:#a6e22e">CacheMissException</span>) {
</span></span><span style="display:flex;"><span>    $result <span style="color:#f92672">=</span> $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">compute</span>($key);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Avant 8.0, il fallait écrire <code>catch (CacheMissException $e)</code> et ensuite soit utiliser <code>$e</code> soit vivre avec l&rsquo;avertissement IDE sur la variable inutilisée. Aucune des deux options n&rsquo;était satisfaisante.</p>
<h2 id="les-fonctions-string-qui-auraient-dû-exister-depuis-des-années">Les fonctions string qui auraient dû exister depuis des années</h2>
<p>Trois fonctions que chaque développeur PHP a écrites manuellement au moins une fois :</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">str_contains</span>(<span style="color:#e6db74">&#39;hello world&#39;</span>, <span style="color:#e6db74">&#39;world&#39;</span>);  <span style="color:#75715e">// true
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">str_starts_with</span>(<span style="color:#e6db74">&#39;hello world&#39;</span>, <span style="color:#e6db74">&#39;hell&#39;</span>); <span style="color:#75715e">// true
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">str_ends_with</span>(<span style="color:#e6db74">&#39;hello world&#39;</span>, <span style="color:#e6db74">&#39;world&#39;</span>); <span style="color:#75715e">// true
</span></span></span></code></pre></div><p>Avant 8.0, les approches habituelles étaient <code>strpos() !== false</code>, <code>strncmp()</code>, ou <code>substr() ===</code>, qui nécessitent toutes de s&rsquo;arrêter pour se souvenir de la sémantique. Ces nouvelles fonctions sont juste directes et lisibles. Pas de regex, pas d&rsquo;arithmétique d&rsquo;offset.</p>
<h2 id="un-tri-stable">Un tri stable</h2>
<p>Les fonctions de tri de PHP n&rsquo;étaient pas stables avant 8.0. &ldquo;Pas stable&rdquo; signifie que les éléments qui se comparent comme égaux pouvaient se retrouver dans n&rsquo;importe quel ordre les uns par rapport aux autres. En pratique, ça causait des bugs subtils dans le code UI qui avait besoin d&rsquo;un ordre cohérent, une pagination qui changeait entre les chargements, et des tests qui ne passaient que par chance.</p>
<p>8.0 garantit la stabilité à travers toutes les fonctions de tri : <code>sort()</code>, <code>usort()</code>, <code>array_multisort()</code>, et le reste. Les éléments égaux conservent leur position relative originale. C&rsquo;est le comportement que la plupart des gens supposaient déjà être là.</p>
<h2 id="weakmap">WeakMap</h2>
<p>7.4 apportait <code>WeakReference</code> pour les objets simples. 8.0 apporte <code>WeakMap</code> : une map où les clés (des objets) et leurs données associées peuvent être ramassées par le GC quand aucune autre référence à l&rsquo;objet-clé n&rsquo;existe :</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">RequestCache</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">private</span> <span style="color:#a6e22e">WeakMap</span> $cache;
</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>        $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">cache</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">WeakMap</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">get</span>(<span style="color:#a6e22e">Request</span> $request)<span style="color:#f92672">:</span> <span style="color:#a6e22e">Response</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">cache</span>[$request] <span style="color:#f92672">??=</span> $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">compute</span>($request);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Dès que <code>$request</code> n&rsquo;est plus référencé ailleurs, l&rsquo;entrée disparaît de la map. Pas de nettoyage manuel nécessaire. C&rsquo;est le bon pattern pour la mémoïsation et les caches de propriétés calculées où on ne veut pas être la seule raison qu&rsquo;un objet reste vivant.</p>
<h2 id="les-nouveaux-types-dexception">Les nouveaux types d&rsquo;exception</h2>
<p><code>ValueError</code> est levée quand une fonction reçoit le bon type mais une valeur invalide, par opposition à <code>TypeError</code> qui se déclenche sur les mauvais 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-php" data-lang="php"><span style="display:flex;"><span><span style="color:#a6e22e">array_chunk</span>([], <span style="color:#f92672">-</span><span style="color:#ae81ff">5</span>); <span style="color:#75715e">// ValueError: array_chunk(): Argument #2 ($length) must be greater than 0
</span></span></span></code></pre></div><p>Avant 8.0, beaucoup de ces cas étaient des warnings qui retournaient <code>false</code> ou <code>null</code>. Maintenant ils lèvent des exceptions. Le moteur est plus strict, ce qui signifie qu&rsquo;on attrape les problèmes plus tôt plutôt que d&rsquo;obtenir des résultats bizarres quelque part en aval.</p>
<h2 id="get_debug_type-et-fdiv">get_debug_type() et fdiv()</h2>
<p>Deux fonctions utilitaires à connaître.</p>
<p><code>get_debug_type()</code> retourne une représentation string normalisée de n&rsquo;importe quelle valeur, pratique pour les messages d&rsquo;erreur :</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">get_debug_type</span>(<span style="color:#ae81ff">1</span>);          <span style="color:#75715e">// &#34;int&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">get_debug_type</span>(<span style="color:#ae81ff">1.0</span>);        <span style="color:#75715e">// &#34;float&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">get_debug_type</span>(<span style="color:#66d9ef">null</span>);       <span style="color:#75715e">// &#34;null&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">get_debug_type</span>(<span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Foo</span>());  <span style="color:#75715e">// &#34;Foo&#34; (pas &#34;object&#34;)
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">get_debug_type</span>([]);         <span style="color:#75715e">// &#34;array&#34;
</span></span></span></code></pre></div><p>La différence avec <code>gettype()</code> : elle retourne les noms de classes pour les objets et utilise des noms normalisés (<code>&quot;int&quot;</code> pas <code>&quot;integer&quot;</code>). Exactement ce qu&rsquo;on veut pour construire un message d&rsquo;exception qui dit ce qu&rsquo;on a reçu versus ce qu&rsquo;on attendait.</p>
<p><code>fdiv()</code> effectue une division en virgule flottante suivant IEEE 754, ce qui signifie que la division par zéro retourne <code>INF</code>, <code>-INF</code>, ou <code>NAN</code> au lieu d&rsquo;un warning :</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">fdiv</span>(<span style="color:#ae81ff">10</span>, <span style="color:#ae81ff">0</span>);   <span style="color:#75715e">// INF
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">fdiv</span>(<span style="color:#f92672">-</span><span style="color:#ae81ff">10</span>, <span style="color:#ae81ff">0</span>);  <span style="color:#75715e">// -INF
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">fdiv</span>(<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">0</span>);    <span style="color:#75715e">// NAN
</span></span></span></code></pre></div><h2 id="les-changements-qui-cassent-des-choses">Les changements qui cassent des choses</h2>
<p>8.0 inclut aussi quelques changements qui ne sont pas des fonctionnalités, ce sont des corrections.</p>
<p>Le plus important : <code>0 == &quot;foo&quot;</code> est maintenant <code>false</code>. En PHP 7, comparer un entier à une string non numérique castait la string en 0, donc <code>0 == &quot;n'importe-quoi-non-numérique&quot;</code> s&rsquo;évaluait à <code>true</code>. C&rsquo;était une source persistante de bugs et de maux de tête de sécurité. PHP 8 l&rsquo;inverse : l&rsquo;entier est casté en string à la place :</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">var_dump</span>(<span style="color:#ae81ff">0</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;foo&#34;</span>);  <span style="color:#75715e">// bool(false) en 8.0, bool(true) en 7.x
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">var_dump</span>(<span style="color:#ae81ff">0</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;&#34;</span>);     <span style="color:#75715e">// bool(false) en 8.0, bool(true) en 7.x
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">var_dump</span>(<span style="color:#ae81ff">0</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;0&#34;</span>);    <span style="color:#75715e">// bool(true) dans les deux (&#34;0&#34; est numérique)
</span></span></span></code></pre></div><p>Si on s&rsquo;appuyait sur ça intentionnellement, on savait déjà que c&rsquo;était douteux. Si on ne savait pas qu&rsquo;on s&rsquo;en appuyait, 8.0 va trouver ces chemins de code.</p>
<p>Plusieurs fonctions qui retournaient des ressources retournent maintenant des objets propres : <code>curl_init()</code> retourne un <code>CurlHandle</code>, <code>imagecreate()</code> retourne un <code>GdImage</code>, <code>xml_parser_create()</code> retourne un <code>XMLParser</code>. Le code qui vérifie <code>is_resource($curl)</code> va casser, parce que <code>is_resource()</code> retourne <code>false</code> pour ces objets. La correction consiste à vérifier contre <code>false</code> (la valeur de retour en cas d&rsquo;échec) plutôt que de vérifier le type du cas de succès.</p>
<p>PHP 8.0 est le genre de version où les fonctionnalités se renforcent mutuellement. Les attributs se marient bien avec la promotion de constructeur. Match s&rsquo;associe naturellement avec les types union. Les fonctions string réduisent le bruit qui cachait l&rsquo;intention. Les corrections sont parfois cassantes, mais elles poussent le langage vers une cohérence qu&rsquo;il aurait dû avoir depuis des années.</p>
]]></content:encoded></item><item><title>Élagage des révisions avec des window functions et des logarithmes, quand DQL ne suffisait plus</title><link>https://guillaumedelre.github.io/fr/2020/09/27/%C3%A9lagage-des-r%C3%A9visions-avec-des-window-functions-et-des-logarithmes-quand-dql-ne-suffisait-plus/</link><pubDate>Sun, 27 Sep 2020 00:00:00 +0000</pubDate><guid>https://guillaumedelre.github.io/fr/2020/09/27/%C3%A9lagage-des-r%C3%A9visions-avec-des-window-functions-et-des-logarithmes-quand-dql-ne-suffisait-plus/</guid><description>Comment un score logarithmique et ROW_NUMBER() OVER PARTITION BY ont résolu la croissance incontrôlable d&amp;#39;une table de révisions après que DQL a atteint ses limites.</description><content:encoded><![CDATA[<p>Chaque mise à jour de contenu sur la plateforme crée une révision. C&rsquo;est délibéré : les éditeurs ont besoin d&rsquo;un historique sur lequel ils peuvent revenir, et la plateforme a besoin d&rsquo;une piste d&rsquo;audit. Ce que personne n&rsquo;avait anticipé, c&rsquo;était le rythme. Certains articles passent par quarante sauvegardes en un seul après-midi. Une pièce à fort trafic accumule des centaines de révisions sur sa durée de vie. Après quelques mois, la table de révisions avait plusieurs millions de lignes.</p>
<p>Les supprimer naïvement n&rsquo;était pas une option. &ldquo;Garder les 50 dernières&rdquo; perd tout contexte historique pour les articles qui n&rsquo;ont pas été touchés depuis un an. &ldquo;Garder une par jour&rdquo; perd tous les détails pour le contenu qui est activement édité. Ce dont on avait besoin, c&rsquo;était une distribution qui correspondait à la façon dont les révisions sont réellement utilisées : couverture dense pour l&rsquo;historique récent, couverture clairsemée pour l&rsquo;ancien.</p>
<p>C&rsquo;est une distribution logarithmique. Et la construire nécessitait du SQL brut.</p>
<h2 id="pourquoi-les-stratégies-simples-échouent">Pourquoi les stratégies simples échouent</h2>
<p>L&rsquo;attrait d&rsquo;une fenêtre fixe est évident : garder les N révisions les plus récentes et supprimer le reste. C&rsquo;est une ligne de SQL et zéro maths. Le problème, c&rsquo;est qu&rsquo;elle traite une révision d&rsquo;hier et une révision d&rsquo;il y a trois ans comme également précieuses, ce qu&rsquo;elles ne sont pas. Un éditeur qui ouvre un article de 2017 n&rsquo;a pas besoin de ses 50 dernières versions ; il pourrait avoir besoin d&rsquo;une par trimestre. Un article qui a été publié ce matin pourrait avoir besoin de chaque sauvegarde de la dernière heure.</p>
<p>Une stratégie temporelle (une révision par jour calendaire) a le problème inverse : elle est trop agressive pour le contenu actif. Si un article reçoit 30 sauvegardes entre 09h00 et 10h00, toutes sauf une disparaissent. Ce n&rsquo;est pas de l&rsquo;histoire, c&rsquo;est de l&rsquo;effacement.</p>
<p>Ni l&rsquo;une ni l&rsquo;autre ne peut exprimer &ldquo;garder plus de détails pour le contenu récent, moins pour le vieux&rdquo;. Cette relation est logarithmique.</p>
<h2 id="lidée-de-score">L&rsquo;idée de score</h2>
<p>L&rsquo;algorithme assigne à chaque révision un score basé sur son âge, puis garde seulement une révision par bucket de score. La formule de score produit des valeurs hautes et bien espacées pour les révisions récentes, et des valeurs petites et regroupées pour les anciennes.</p>
<p>L&rsquo;expression centrale, simplifiée, ressemble à ça :</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-sql" data-lang="sql"><span style="display:flex;"><span>(
</span></span><span style="display:flex;"><span>  ln( <span style="color:#66d9ef">EXTRACT</span>(epoch <span style="color:#66d9ef">FROM</span> (now() <span style="color:#f92672">-</span> created_at)) )
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">/</span>
</span></span><span style="display:flex;"><span>  ( <span style="color:#66d9ef">EXTRACT</span>(epoch <span style="color:#66d9ef">FROM</span> (now() <span style="color:#f92672">-</span> created_at)) <span style="color:#f92672">/</span> <span style="color:#ae81ff">6000</span> )
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span><span style="color:#f92672">*</span> ( <span style="color:#ae81ff">1</span> <span style="color:#f92672">/</span> (<span style="color:#66d9ef">EXTRACT</span>(epoch <span style="color:#66d9ef">FROM</span> (now() <span style="color:#f92672">-</span> created_at)) <span style="color:#f92672">/</span> <span style="color:#ae81ff">60</span> <span style="color:#f92672">/</span> <span style="color:#ae81ff">1440</span>) )
</span></span><span style="display:flex;"><span><span style="color:#f92672">*</span> <span style="color:#ae81ff">1000</span>
</span></span></code></pre></div><p>Soit <code>s</code> l&rsquo;âge en secondes. La formule est grossièrement <code>ln(s) / s * C</code>, où le logarithme au numérateur et <code>s</code> au dénominateur font diminuer le résultat rapidement à mesure que <code>s</code> augmente.</p>
<p>Converti en entier, l&rsquo;effet est le suivant : une révision sauvegardée il y a 10 minutes pourrait scorer 8432, une sauvegardée il y a 11 minutes score 8431. Elles sont dans des buckets différents. Une révision d&rsquo;il y a six mois score 2, une d&rsquo;il y a huit mois score aussi 2. Même bucket. La window function choisit ensuite la révision la plus récente de chaque bucket et supprime le reste.</p>
<p>Le résultat est automatique : les sauvegardes récentes sont toutes gardées parce que chacune a un score distinct ; les anciennes sont élagées parce que beaucoup partagent le même score.</p>
<h2 id="la-tentative-dql-qui-na-pas-abouti">La tentative DQL qui n&rsquo;a pas abouti</h2>
<p>Les window functions ne font pas partie de DQL. Le langage de requête de Doctrine n&rsquo;a pas de syntaxe pour <code>OVER</code>, <code>PARTITION BY</code> ou <code>ROW_NUMBER()</code>. Avant de passer au SQL brut, l&rsquo;équipe a essayé de les ajouter.</p>
<p>L&rsquo;approche <code>FunctionNode</code> fonctionne pour les fonctions SQL simples, comme on l&rsquo;avait déjà vu avec la FTS. Un nœud <code>RowNumber</code> émettant <code>ROW_NUMBER()</code> est trivial :</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">RowNumber</span> <span style="color:#66d9ef">extends</span> <span style="color:#a6e22e">FunctionNode</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">getSql</span>(<span style="color:#a6e22e">SqlWalker</span> $sqlWalker)<span style="color:#f92672">:</span> <span style="color:#a6e22e">string</span>
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#39;ROW_NUMBER()&#39;</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>La partie plus difficile est <code>OVER(PARTITION BY ... ORDER BY ...)</code>. Un nœud de fonction <code>Over</code> a été ébauché, avec un nœud AST <code>PartitionByClause</code> personnalisé pour gérer la clause <code>PARTITION BY</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">class</span> <span style="color:#a6e22e">Over</span> <span style="color:#66d9ef">extends</span> <span style="color:#a6e22e">FunctionNode</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">protected</span> <span style="color:#f92672">?</span><span style="color:#a6e22e">PartitionByClause</span> $partitionByClause <span style="color:#f92672">=</span> <span style="color:#66d9ef">null</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">protected</span> <span style="color:#f92672">?</span><span style="color:#a6e22e">OrderByClause</span> $orderByClause <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 style="color:#66d9ef">public</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">getSql</span>(<span style="color:#a6e22e">SqlWalker</span> $sqlWalker)<span style="color:#f92672">:</span> <span style="color:#a6e22e">string</span>
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#39;OVER(&#39;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">.</span>($this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">partitionByClause</span>
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">?</span> $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">partitionByClause</span><span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">dispatch</span>($sqlWalker)
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">:</span> ($this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">orderByClause</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#f92672">?</span> $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">orderByClause</span><span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">dispatch</span>($sqlWalker)
</span></span><span style="display:flex;"><span>                    <span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;&#39;</span>))
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">.</span><span style="color:#e6db74">&#39;)&#39;</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Ça n&rsquo;a jamais été terminé. Les classes ont été livrées marquées <code>@deprecated</code> et &ldquo;NOT TESTED YET&rdquo;. Le problème est la composabilité : <code>FunctionNode</code> de DQL fonctionne bien pour les fonctions qui apparaissent dans les clauses WHERE ou les expressions SELECT. Une window function comme <code>ROW_NUMBER() OVER (PARTITION BY ...)</code> est une structure différente : elle apparaît dans une position SELECT, modifie la sémantique de la requête englobante, et exige que le parseur gère <code>PARTITION BY</code> comme une extension de la grammaire DQL. Rendre ça suffisamment robuste pour être fiable en production est un investissement significatif. Passer à DBAL et écrire le SQL directement a pris un après-midi.</p>
<h2 id="la-requête-couche-par-couche">La requête, couche par couche</h2>
<p>L&rsquo;implémentation finale est trois requêtes imbriquées :</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-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">DELETE</span> <span style="color:#66d9ef">FROM</span> revision
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">WHERE</span> iri <span style="color:#f92672">=</span> <span style="color:#f92672">?</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">AND</span> id <span style="color:#66d9ef">NOT</span> <span style="color:#66d9ef">IN</span> (
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">SELECT</span> id <span style="color:#66d9ef">FROM</span> (
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">SELECT</span>
</span></span><span style="display:flex;"><span>            row_number() OVER (
</span></span><span style="display:flex;"><span>                PARTITION <span style="color:#66d9ef">BY</span> num, iri
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">ORDER</span> <span style="color:#66d9ef">BY</span> num <span style="color:#66d9ef">DESC</span>, created_at <span style="color:#66d9ef">DESC</span>
</span></span><span style="display:flex;"><span>            ) <span style="color:#66d9ef">AS</span> lines,
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">*</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">FROM</span> (
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">SELECT</span>
</span></span><span style="display:flex;"><span>                (
</span></span><span style="display:flex;"><span>                    ( ln( <span style="color:#66d9ef">EXTRACT</span>(epoch <span style="color:#66d9ef">FROM</span> (now() <span style="color:#f92672">-</span> created_at)) )
</span></span><span style="display:flex;"><span>                      <span style="color:#f92672">/</span> ( <span style="color:#66d9ef">EXTRACT</span>(epoch <span style="color:#66d9ef">FROM</span> (now() <span style="color:#f92672">-</span> created_at)) <span style="color:#f92672">/</span> <span style="color:#ae81ff">6000</span> ) )
</span></span><span style="display:flex;"><span>                    <span style="color:#f92672">*</span> ( <span style="color:#ae81ff">1</span> <span style="color:#f92672">/</span> (<span style="color:#66d9ef">EXTRACT</span>(epoch <span style="color:#66d9ef">FROM</span> (now() <span style="color:#f92672">-</span> created_at)) <span style="color:#f92672">/</span> <span style="color:#ae81ff">60</span> <span style="color:#f92672">/</span> <span style="color:#ae81ff">1440</span>) )
</span></span><span style="display:flex;"><span>                    <span style="color:#f92672">*</span> <span style="color:#ae81ff">1000</span>
</span></span><span style="display:flex;"><span>                )::numeric::integer <span style="color:#66d9ef">AS</span> num,
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">*</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">FROM</span> revision
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">WHERE</span> iri <span style="color:#f92672">=</span> <span style="color:#f92672">?</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">ORDER</span> <span style="color:#66d9ef">BY</span> created_at <span style="color:#66d9ef">DESC</span>
</span></span><span style="display:flex;"><span>        ) <span style="color:#66d9ef">AS</span> lst
</span></span><span style="display:flex;"><span>    ) <span style="color:#66d9ef">AS</span> rst
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">WHERE</span> lines <span style="color:#f92672">=</span> <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>);
</span></span></code></pre></div><p><strong>Requête intérieure :</strong> calcule <code>num</code>, le score entier, pour chaque révision de l&rsquo;IRI donnée. Les lignes sont triées par <code>created_at DESC</code> à ce stade.</p>
<p><strong>Requête intermédiaire :</strong> exécute <code>ROW_NUMBER() OVER (PARTITION BY num, iri ORDER BY num DESC, created_at DESC)</code>. Dans chaque bucket de score (<code>num</code>), les révisions sont numérotées à partir de 1 dans l&rsquo;ordre décroissant d&rsquo;âge. La révision la plus récente de chaque bucket obtient <code>lines = 1</code>.</p>
<p><strong>Filtre extérieur :</strong> ne garde que les lignes <code>lines = 1</code>, une révision par bucket de score.</p>
<p><strong>DELETE :</strong> supprime chaque révision pour cet IRI qui n&rsquo;est pas dans l&rsquo;ensemble gardé.</p>
<p>Le <code>PARTITION BY num, iri</code> est redondant sur l&rsquo;IRI (toute la requête est déjà filtrée sur un IRI), mais rend l&rsquo;intention explicite et garde la logique correcte si la requête est un jour réutilisée dans un contexte plus large.</p>
<p>La méthode est appelée depuis une requête complémentaire qui identifie quels IRIs ont accumulé plus qu&rsquo;un seuil de révisions :</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">function</span> <span style="color:#a6e22e">getIrisWithMoreRevisionThan</span>(<span style="color:#a6e22e">int</span> $maxRevisionsCount, <span style="color:#a6e22e">int</span> $limit <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>, <span style="color:#f92672">?</span><span style="color:#a6e22e">int</span> $retencyDay <span style="color:#f92672">=</span> <span style="color:#66d9ef">null</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>    $queryBuilder <span style="color:#f92672">=</span> $this
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">createQueryBuilder</span>(<span style="color:#e6db74">&#39;revision&#39;</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">select</span>(<span style="color:#e6db74">&#39;revision.iri&#39;</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">groupBy</span>(<span style="color:#e6db74">&#39;revision.iri&#39;</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">having</span>(<span style="color:#e6db74">&#39;COUNT(1) &gt; :maxRevisions&#39;</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">orderBy</span>(<span style="color:#e6db74">&#39;COUNT(1)&#39;</span>, <span style="color:#a6e22e">Order</span><span style="color:#f92672">::</span><span style="color:#a6e22e">Descending</span><span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">value</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">setParameter</span>(<span style="color:#e6db74">&#39;maxRevisions&#39;</span>, $maxRevisionsCount);
</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></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">array_column</span>($queryBuilder<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">getQuery</span>()<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">getResult</span>(), <span style="color:#e6db74">&#39;iri&#39;</span>);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Les deux méthodes tournent ensemble dans un nettoyage planifié : trouver les IRIs au-dessus du seuil, élaguer chacun.</p>
<h2 id="le-câbler-à-une-commande-planifiée">Le câbler à une commande planifiée</h2>
<p>La requête d&rsquo;élagage ne s&rsquo;exécute pas dans une requête HTTP. Elle tourne derrière une commande Symfony, appelée sur un planning.</p>
<p>La commande prend quelques options pour contrôler son agressivité :</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">#[AsCommand(&#39;app:purge:revision&#39;, &#39;Remove useless revisions&#39;)]
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">final</span> <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">PurgeRevisionCommand</span> <span style="color:#66d9ef">extends</span> <span style="color:#a6e22e">Command</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">protected</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">configure</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></span><span style="display:flex;"><span>            <span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">addOption</span>(<span style="color:#e6db74">&#39;max-revisions&#39;</span>, <span style="color:#e6db74">&#39;m&#39;</span>, <span style="color:#a6e22e">InputOption</span><span style="color:#f92672">::</span><span style="color:#a6e22e">VALUE_REQUIRED</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#39;Seuil de révisions au-dessus duquel un IRI est élaguée&#39;</span>, <span style="color:#ae81ff">30</span>)
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">addOption</span>(<span style="color:#e6db74">&#39;limit&#39;</span>, <span style="color:#e6db74">&#39;l&#39;</span>, <span style="color:#a6e22e">InputOption</span><span style="color:#f92672">::</span><span style="color:#a6e22e">VALUE_REQUIRED</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#39;Nombre max d\&#39;IRIs à traiter par exécution&#39;</span>)
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">addOption</span>(<span style="color:#e6db74">&#39;delay&#39;</span>, <span style="color:#e6db74">&#39;w&#39;</span>, <span style="color:#a6e22e">InputOption</span><span style="color:#f92672">::</span><span style="color:#a6e22e">VALUE_REQUIRED</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#39;Délai en secondes entre chaque IRI&#39;</span>)
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">addOption</span>(<span style="color:#e6db74">&#39;retencyDay&#39;</span>, <span style="color:#e6db74">&#39;r&#39;</span>, <span style="color:#a6e22e">InputOption</span><span style="color:#f92672">::</span><span style="color:#a6e22e">VALUE_OPTIONAL</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#39;Ne traiter que les IRIs dont la dernière révision est plus vieille que N jours&#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">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>        $iris <span style="color:#f92672">=</span> $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">revisionRepository</span><span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">getIrisWithMoreRevisionThan</span>(
</span></span><span style="display:flex;"><span>            (<span style="color:#a6e22e">int</span>) $input<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">getOption</span>(<span style="color:#e6db74">&#39;max-revisions&#39;</span>),
</span></span><span style="display:flex;"><span>            (<span style="color:#a6e22e">int</span>) $input<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">getOption</span>(<span style="color:#e6db74">&#39;limit&#39;</span>),
</span></span><span style="display:flex;"><span>            (<span style="color:#a6e22e">int</span>) $input<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">getOption</span>(<span style="color:#e6db74">&#39;retencyDay&#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">foreach</span> ($iris <span style="color:#66d9ef">as</span> $iri) {
</span></span><span style="display:flex;"><span>            $totalDeleted <span style="color:#f92672">+=</span> $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">revisionRepository</span><span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">deleteOldRevisionForIri</span>($iri);
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">usleep</span>((<span style="color:#a6e22e">int</span>) $input<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">getOption</span>(<span style="color:#e6db74">&#39;delay&#39;</span>) <span style="color:#f92672">*</span> <span style="color:#ae81ff">1_000_000</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">return</span> <span style="color:#a6e22e">Command</span><span style="color:#f92672">::</span><span style="color:#a6e22e">SUCCESS</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>L&rsquo;option <code>--delay</code> mérite attention : sur une base de données chargée, marteler une centaine d&rsquo;instructions <code>DELETE</code> dos à dos peut provoquer de la contention de verrous. Un petit sleep entre les itérations empêche l&rsquo;élagage d&rsquo;entrer en concurrence avec le trafic de production.</p>
<p>La commande tourne derrière deux entrées crontab avec des seuils différents :</p>
<pre tabindex="0"><code># Horaire : garder 30 révisions par IRI, traiter 100 IRIs par exécution
0 * * * * php bin/console app:purge:revision --max-revisions 30 --limit 100

# Nocturne : pour le contenu non touché depuis un an, garder seulement 3
0 0 * * * php bin/console app:purge:revision --max-revisions 3 --limit 100 --retencyDay 365
</code></pre><p>La stratégie à deux niveaux est importante. Le job horaire garde 30 révisions par IRI, ce qui est un plafond raisonnable pour le contenu activement édité. Le job nocturne cible seulement les IRIs non mis à jour depuis plus d&rsquo;un an et n&rsquo;en garde que 3. Un article qui n&rsquo;a pas bougé depuis douze mois n&rsquo;a pas besoin de trente versions dans son historique.</p>
<h2 id="ce-que-ça-donne-en-pratique">Ce que ça donne en pratique</h2>
<p>Un article sauvegardé 200 fois gardera typiquement 20 à 30 révisions après élagage : la plupart des sauvegardes récentes, quelques-unes du mois dernier, une ou deux de chaque trimestre de l&rsquo;année précédente. Le décompte exact dépend de la distribution d&rsquo;âge des sauvegardes, pas d&rsquo;un plafond arbitraire.</p>
<p>Un article mis à jour pour la dernière fois il y a deux ans pourrait se retrouver avec 5 ou 6 révisions. Les modifications récentes sont toutes là ; l&rsquo;ancien historique est compressé mais pas effacé.</p>
<p>Ce n&rsquo;est pas un historique parfait. C&rsquo;est un historique utile.</p>
<h2 id="la-frontière-entre-dql-et-sql-brut">La frontière entre DQL et SQL brut</h2>
<p>La tentative window function n&rsquo;est pas un échec à cacher. C&rsquo;est une donnée utile : <code>FunctionNode</code> fonctionne bien pour les fonctions scalaires dans les positions WHERE et SELECT, mais composer une expression complète <code>ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...)</code> en DQL est plus difficile qu&rsquo;il n&rsquo;y paraît. L&rsquo;extension de grammaire, les nœuds AST, l&rsquo;intégration du SQL walker : c&rsquo;est une quantité non triviale de code pour quelque chose que le SQL natif gère en trois lignes.</p>
<p>La frontière pratique est grossièrement celle-ci : si une fonctionnalité PostgreSQL correspond à un appel de fonction d&rsquo;arité fixe, le DQL personnalisé convient. Si elle nécessite une nouvelle syntaxe de clause (frames de fenêtre, CTEs, lateral joins), le DBAL natif est généralement le meilleur compromis.</p>
]]></content:encoded></item><item><title>PHP 7.4 : les propriétés typées et les arrow functions qu'on attendait</title><link>https://guillaumedelre.github.io/fr/2020/01/12/php-7.4-les-propri%C3%A9t%C3%A9s-typ%C3%A9es-et-les-arrow-functions-quon-attendait/</link><pubDate>Sun, 12 Jan 2020 00:00:00 +0000</pubDate><guid>https://guillaumedelre.github.io/fr/2020/01/12/php-7.4-les-propri%C3%A9t%C3%A9s-typ%C3%A9es-et-les-arrow-functions-quon-attendait/</guid><description>Part 5 of 11 in &amp;quot;Sorties PHP&amp;quot;: PHP 7.4 apporte les propriétés typées et les arrow functions concises — dernière version 7.x et prévisualisation la plus claire de PHP 8.</description><category>php-releases</category><content:encoded><![CDATA[<p>PHP 7.4 est sorti le 28 novembre. C&rsquo;est la dernière version 7.x avant PHP 8.0, et ça se sent. Les fonctionnalités sont suffisamment substantielles pour tenir debout seules, mais elles ressemblent aussi à des fondations pour ce qui arrive.</p>
<h2 id="les-propriétés-typées">Les propriétés typées</h2>
<p>C&rsquo;est la grande nouveauté. Depuis PHP 7.0, on pouvait typer les paramètres de fonctions et les valeurs de retour. Mais les propriétés de classe ? Toujours non typées :</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">User</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#a6e22e">int</span> $id;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#a6e22e">string</span> $name;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#f92672">?</span><span style="color:#a6e22e">DateTimeInterface</span> $deletedAt;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>7.4 change ça. Les propriétés typées font respecter les types à l&rsquo;affectation, pas seulement au niveau des sites d&rsquo;appel. Les classes deviennent auto-documentées d&rsquo;une façon que les docblocks n&rsquo;ont jamais vraiment réussi à faire, et le moteur attrape les erreurs de type avant qu&rsquo;elles ne se propagent dans la moitié de la stack.</p>
<p>Une subtilité : les propriétés typées sont <code>uninitialized</code> par défaut (pas <code>null</code>). Accéder à une propriété non initialisée lève une <code>Error</code>. C&rsquo;est un piège classique : <code>?string</code> n&rsquo;implique pas un défaut de <code>null</code>. Il faut encore un <code>= null</code> explicite pour ça.</p>
<h2 id="les-arrow-functions">Les arrow functions</h2>
<p>Les closures en PHP ont toujours nécessité d&rsquo;importer explicitement les variables de la portée extérieure avec <code>use</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>$multiplier <span style="color:#f92672">=</span> <span style="color:#ae81ff">3</span>;
</span></span><span style="display:flex;"><span>$fn <span style="color:#f92672">=</span> <span style="color:#a6e22e">fn</span>($x) <span style="color:#f92672">=&gt;</span> $x <span style="color:#f92672">*</span> $multiplier; <span style="color:#75715e">// pas besoin de use()
</span></span></span></code></pre></div><p>Les arrow functions capturent automatiquement la portée englobante. Expression unique, retour implicite, pas de boilerplate. Elles ne remplacent pas les closures complètes pour la logique complexe, mais pour les callbacks courts, elles éliminent une catégorie de bruit qui s&rsquo;accumulait depuis des années.</p>
<h2 id="le-préchargement-opcache">Le préchargement opcache</h2>
<p>Pour les setups PHP-FPM à longue durée de vie, le préchargement permet à un script de charger et compiler des fichiers PHP en mémoire opcache au démarrage du serveur. Ces fichiers sont disponibles pour toutes les requêtes sans overhead de compilation.</p>
<p>Le gain varie selon l&rsquo;application. Sur les grands frameworks où les mêmes fichiers sont chargés à chaque requête, c&rsquo;est réel. Sur les petites applications, négligeable. Vaut la peine de benchmarker avant d&rsquo;ajouter la complexité de configuration.</p>
<h2 id="les-petites-choses-qui-saccumulent">Les petites choses qui s&rsquo;accumulent</h2>
<p>Les fonctionnalités mentionnées en passant méritent plus qu&rsquo;une ligne. L&rsquo;opérateur d&rsquo;affectation null-coalescente <code>??=</code> résout un pattern suffisamment agaçant à écrire à chaque fois, mais jamais assez pour se donner la peine de l&rsquo;abstraire :</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>$config[<span style="color:#e6db74">&#39;timeout&#39;</span>] <span style="color:#f92672">??=</span> <span style="color:#ae81ff">30</span>;
</span></span><span style="display:flex;"><span><span style="color:#75715e">// équivalent à : $config[&#39;timeout&#39;] = $config[&#39;timeout&#39;] ?? 30;
</span></span></span></code></pre></div><p>L&rsquo;opérateur spread dans les littéraux de tableau fait ce qu&rsquo;on attend de la version pour les appels de fonctions — dépacker un itérable dans un littéral de tableau :</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>$defaults <span style="color:#f92672">=</span> [<span style="color:#e6db74">&#39;color&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#39;blue&#39;</span>, <span style="color:#e6db74">&#39;size&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#39;M&#39;</span>];
</span></span><span style="display:flex;"><span>$options <span style="color:#f92672">=</span> [<span style="color:#e6db74">&#39;size&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#39;L&#39;</span>, <span style="color:#f92672">...</span>$defaults, <span style="color:#e6db74">&#39;weight&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#ae81ff">1.2</span>];
</span></span><span style="display:flex;"><span><span style="color:#75715e">// [&#39;size&#39; =&gt; &#39;M&#39;, &#39;color&#39; =&gt; &#39;blue&#39;, &#39;weight&#39; =&gt; 1.2]
</span></span></span></code></pre></div><p>Note : les clés string n&rsquo;étaient pas supportées dans 7.4 pour le dépaquet de tableau. Ça viendra plus tard.</p>
<p>Les types de retour covariants et les types de paramètres contravariants comblent un vide qui rendait certains patterns d&rsquo;héritage inutilement maladroits. Une classe enfant peut maintenant affiner son type de retour vers un sous-type de celui du parent, sans erreur fatale :</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">Producer</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">get</span>()<span style="color:#f92672">:</span> <span style="color:#a6e22e">Iterator</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">ChildProducer</span> <span style="color:#66d9ef">extends</span> <span style="color:#a6e22e">Producer</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">get</span>()<span style="color:#f92672">:</span> <span style="color:#a6e22e">ArrayIterator</span> {} <span style="color:#75715e">// ArrayIterator implémente Iterator
</span></span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h2 id="lire-des-nombres-à-3h-du-matin">Lire des nombres à 3h du matin</h2>
<p>Le séparateur de littéraux numériques est une de ces fonctionnalités dont on ne sait pas qu&rsquo;on la voulait jusqu&rsquo;à la première fois qu&rsquo;on écrit une grande constante et qu&rsquo;on perd immédiatement le sens de l&rsquo;ordre de grandeur :</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>$earthMass    <span style="color:#f92672">=</span> <span style="color:#ae81ff">5_972_168_000_000_000_000_000_000</span>; <span style="color:#75715e">// kg
</span></span></span><span style="display:flex;"><span>$lightSpeed   <span style="color:#f92672">=</span> <span style="color:#ae81ff">299_792_458</span>;                        <span style="color:#75715e">// m/s
</span></span></span><span style="display:flex;"><span>$planck       <span style="color:#f92672">=</span> <span style="color:#ae81ff">6.626</span><span style="color:#a6e22e">_070_15e</span><span style="color:#f92672">-</span><span style="color:#ae81ff">34</span>;                  <span style="color:#75715e">// J·s
</span></span></span><span style="display:flex;"><span>$hexMask      <span style="color:#f92672">=</span> <span style="color:#ae81ff">0xFF_EC_D5_08</span>;
</span></span><span style="display:flex;"><span>$binaryFlags  <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span><span style="color:#a6e22e">b0001_1111_0010_0000</span>;
</span></span></code></pre></div><p>L&rsquo;underscore est purement syntaxique. Le moteur le supprime avant de parser la valeur. On peut le mettre n&rsquo;importe où entre les chiffres, bien que la convention suive le groupement naturel du système numérique utilisé.</p>
<h2 id="référencer-sans-posséder">Référencer sans posséder</h2>
<p><code>WeakReference</code> permet de tenir une référence à un objet sans empêcher le ramasse-miettes de le détruire. Le cas d&rsquo;usage : les caches et registres — on veut savoir qu&rsquo;un objet est vivant, mais on ne veut pas être la raison qu&rsquo;il reste vivant :</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>$object <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">HeavyObject</span>();
</span></span><span style="display:flex;"><span>$ref <span style="color:#f92672">=</span> <span style="color:#a6e22e">WeakReference</span><span style="color:#f92672">::</span><span style="color:#a6e22e">create</span>($object);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">var_dump</span>($ref<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">get</span>()); <span style="color:#75715e">// object(HeavyObject)
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">unset</span>($object);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">var_dump</span>($ref<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">get</span>()); <span style="color:#75715e">// NULL — le GC l&#39;a collecté
</span></span></span></code></pre></div><p>Avant 7.4, il y avait <code>WeakRef</code> via une extension, et certains frameworks faisaient des tours de passe-passe avec <code>SplObjectStorage</code> qui ne se comportaient pas tout à fait pareil. La classe native est juste directe.</p>
<h2 id="la-sérialisation-sans-surprise">La sérialisation sans surprise</h2>
<p>La sérialisation personnalisée d&rsquo;objets avant 7.4 passait par l&rsquo;interface <code>Serializable</code> : implémenter <code>serialize()</code> et <code>unserialize()</code>, retourner une string, reconstruire depuis elle. Le problème est que <code>serialize()</code> déclenchait <code>__sleep()</code>, <code>unserialize()</code> déclenchait <code>__wakeup()</code>, et l&rsquo;interaction entre ces hooks était fragile, surtout dans les hiérarchies d&rsquo;héritage.</p>
<p>7.4 introduit <code>__serialize()</code> et <code>__unserialize()</code>, qui travaillent avec des tableaux plutôt que des strings et n&rsquo;interagissent pas avec les anciens hooks :</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">Session</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">private</span> <span style="color:#a6e22e">string</span> $token;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">private</span> <span style="color:#a6e22e">\DateTime</span> $createdAt;
</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">__serialize</span>()<span style="color:#f92672">:</span> <span style="color:#66d9ef">array</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> $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">token</span>, <span style="color:#e6db74">&#39;created&#39;</span> <span style="color:#f92672">=&gt;</span> $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">createdAt</span><span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">getTimestamp</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">__unserialize</span>(<span style="color:#66d9ef">array</span> $data)<span style="color:#f92672">:</span> <span style="color:#a6e22e">void</span> {
</span></span><span style="display:flex;"><span>        $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">token</span> <span style="color:#f92672">=</span> $data[<span style="color:#e6db74">&#39;token&#39;</span>];
</span></span><span style="display:flex;"><span>        $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">createdAt</span> <span style="color:#f92672">=</span> (<span style="color:#66d9ef">new</span> <span style="color:#a6e22e">\DateTime</span>())<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">setTimestamp</span>($data[<span style="color:#e6db74">&#39;created&#39;</span>]);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Quand les nouvelles et anciennes méthodes coexistent sur la même classe, <code>__serialize()</code> gagne. L&rsquo;ancienne interface <code>Serializable</code> est dépréciée dans 8.1.</p>
<h2 id="ce-que-la-bibliothèque-standard-a-discrètement-reçu">Ce que la bibliothèque standard a discrètement reçu</h2>
<p><code>mb_str_split()</code> fait ce que <code>str_split()</code> fait mais correctement pour les strings multibyte. Le manque était franchement embarrassant pour un langage utilisé dans autant de locales que 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:#a6e22e">mb_str_split</span>(<span style="color:#e6db74">&#39;héllo&#39;</span>, <span style="color:#ae81ff">1</span>); <span style="color:#75715e">// [&#39;h&#39;, &#39;é&#39;, &#39;l&#39;, &#39;l&#39;, &#39;o&#39;]
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">str_split</span>(<span style="color:#e6db74">&#39;héllo&#39;</span>, <span style="color:#ae81ff">1</span>);    <span style="color:#75715e">// [&#39;h&#39;, &#39;Ã&#39;, &#39;©&#39;, &#39;l&#39;, &#39;l&#39;, &#39;o&#39;] — cassé
</span></span></span></code></pre></div><p><code>strip_tags()</code> accepte maintenant un tableau de tags autorisés, ce qui est plus propre que le format string qu&rsquo;il fallait passer auparavant :</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">strip_tags</span>($html, [<span style="color:#e6db74">&#39;p&#39;</span>, <span style="color:#e6db74">&#39;br&#39;</span>, <span style="color:#e6db74">&#39;strong&#39;</span>]); <span style="color:#75715e">// était : &#39;&lt;p&gt;&lt;br&gt;&lt;strong&gt;&#39;
</span></span></span></code></pre></div><p><code>proc_open()</code> accepte maintenant un tableau de commandes, contournant complètement l&rsquo;interprétation par le shell. Même idée que <code>subprocess</code> de Python avec <code>shell=False</code>. À retenir quand on passe des arguments fournis par l&rsquo;utilisateur à un processus externe.</p>
<h2 id="le-chapitre-ffi">Le chapitre FFI</h2>
<p>L&rsquo;extension Foreign Function Interface a atterri dans 7.4 après avoir passé du temps dans une branche feature. Elle permet à PHP d&rsquo;appeler des fonctions C natives en chargeant une bibliothèque partagée et en déclarant les signatures :</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>$ffi <span style="color:#f92672">=</span> <span style="color:#a6e22e">FFI</span><span style="color:#f92672">::</span><span style="color:#a6e22e">cdef</span>(<span style="color:#e6db74">&#34;int strlen(const char *s);&#34;</span>, <span style="color:#e6db74">&#34;libc.so.6&#34;</span>);
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">var_dump</span>($ffi<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">strlen</span>(<span style="color:#e6db74">&#34;hello&#34;</span>)); <span style="color:#75715e">// int(5)
</span></span></span></code></pre></div><p>Les applications pratiques sont étroites mais réelles : appeler des API de plateforme sans binding PHP, wrapper du code C critique pour les performances sans écrire une extension complète, ou juste jouer avec des bibliothèques natives directement. Ce n&rsquo;est pas un remplacement des extensions propres en production, mais ça supprime la barrière &ldquo;écrire une extension C&rdquo; pour l&rsquo;exploration.</p>
<h2 id="ce-qui-a-été-déprécié">Ce qui a été déprécié</h2>
<p>Quelques choses qui auraient dû être nettoyées depuis longtemps ont finalement reçu le traitement dépréciation dans 7.4.</p>
<p>Les ternaires imbriqués sans parenthèses ont toujours été ambigus. PHP les évaluait de gauche à droite alors que pratiquement tous les autres langages avec un ternaire évaluent de droite à gauche :</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">// Était ambigu, maintenant déprécié :
</span></span></span><span style="display:flex;"><span>$a <span style="color:#f92672">?</span> $b <span style="color:#f92672">:</span> $c <span style="color:#f92672">?</span> $d <span style="color:#f92672">:</span> $e;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Rendre explicite :
</span></span></span><span style="display:flex;"><span>($a <span style="color:#f92672">?</span> $b <span style="color:#f92672">:</span> $c) <span style="color:#f92672">?</span> $d <span style="color:#f92672">:</span> $e;
</span></span></code></pre></div><p>L&rsquo;accès par offset avec accolades pour les strings et tableaux — <code>$str{0}</code> au lieu de <code>$str[0]</code> — est déprécié et supprimé dans 8.0. C&rsquo;était toujours un alias, jamais une fonctionnalité distincte.</p>
<p><code>implode()</code> avec l&rsquo;ordre d&rsquo;arguments inversé (tableau en premier, colle en second) est déprécié. La fonction a accepté les deux ordres depuis le début, ce qui était une erreur. L&rsquo;ordre correct est <code>implode(string $separator, array $array)</code>.</p>
<h2 id="ce-qui-arrive-ensuite">Ce qui arrive ensuite</h2>
<p>7.4 est la dernière version 7.x. Les dépréciations sont principalement du déblayage pour les suppressions dans 8.0. Le backlog de RFCs pour 8.0 est substantiel : JIT, attributs, arguments nommés, expressions match. 7.4 est un bon endroit où atterrir en attendant que tout ça arrive.</p>
]]></content:encoded></item><item><title>PHP 7.3 : des petites victoires qui s'accumulent</title><link>https://guillaumedelre.github.io/fr/2019/01/20/php-7.3-des-petites-victoires-qui-saccumulent/</link><pubDate>Sun, 20 Jan 2019 00:00:00 +0000</pubDate><guid>https://guillaumedelre.github.io/fr/2019/01/20/php-7.3-des-petites-victoires-qui-saccumulent/</guid><description>Part 4 of 11 in &amp;quot;Sorties PHP&amp;quot;: PHP 7.3 apporte une syntaxe heredoc flexible, des virgules finales dans les appels de fonctions, et des améliorations du quotidien qui s&amp;#39;accumulent vite.</description><category>php-releases</category><content:encoded><![CDATA[<p>PHP 7.3 est sorti le 6 décembre. Pas de fonctionnalité phare. C&rsquo;est une collection d&rsquo;améliorations du quotidien qui, individuellement, semblent mineures, mais qui ensemble rendent le travail de tous les jours nettement moins agaçant.</p>
<h2 id="heredoc-et-nowdoc-flexibles">Heredoc et nowdoc flexibles</h2>
<p>Jusqu&rsquo;à 7.3, le marqueur de fermeture d&rsquo;un heredoc devait être en colonne zéro. Ce qui forçait une désindentation maladroite dans du code par ailleurs bien formaté :</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>$html <span style="color:#f92672">=</span> <span style="color:#e6db74">&lt;&lt;&lt;</span><span style="color:#e6db74">HTML</span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    &lt;div&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        &lt;p&gt;Hello&lt;/p&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    &lt;/div&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">HTML; // devait être en colonne 0, moche
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">// après
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">$html = &lt;&lt;&lt;HTML
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    &lt;div&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        &lt;p&gt;Hello&lt;/p&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    &lt;/div&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    </span><span style="color:#e6db74">HTML</span>;
</span></span></code></pre></div><p>Le marqueur de fermeture peut désormais être indenté pour correspondre au code environnant, et cette indentation est retirée du contenu. Ça paraît cosmétique. Ce n&rsquo;est pas le cas. Les heredocs dans des contextes imbriqués (méthodes de classe, conditions) étaient visuellement dissonants avant. Maintenant ils s&rsquo;intègrent.</p>
<h2 id="array_key_first-et-array_key_last"><code>array_key_first()</code> et <code>array_key_last()</code></h2>
<p>Ce contournement existait depuis toujours :</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>$first <span style="color:#f92672">=</span> <span style="color:#a6e22e">array_keys</span>($array)[<span style="color:#ae81ff">0</span>];
</span></span></code></pre></div><p>7.3 ajoute les helpers évidents :</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>$first <span style="color:#f92672">=</span> <span style="color:#a6e22e">array_key_first</span>($array);
</span></span><span style="display:flex;"><span>$last  <span style="color:#f92672">=</span> <span style="color:#a6e22e">array_key_last</span>($array);
</span></span></code></pre></div><p>Et <code>is_countable()</code> pour vérifier proprement avant d&rsquo;appeler <code>count()</code> sur quelque chose qui n&rsquo;implémente peut-être pas <code>Countable</code>. Des fonctions qui auraient dû exister depuis des années.</p>
<h2 id="pcre2">PCRE2</h2>
<p>Le moteur d&rsquo;expressions régulières a migré de PCRE vers PCRE2. Largement invisible pour les patterns existants, mais PCRE2 est activement maintenu et gère mieux les cas limites. L&rsquo;impact pratique principal : certains patterns qui produisaient auparavant un comportement indéfini lancent maintenant des erreurs. C&rsquo;est le bon comportement, même si ça surprend lors du premier upgrade.</p>
<h2 id="virgules-finales-dans-les-appels-de-fonctions">Virgules finales dans les appels de fonctions</h2>
<p>7.2 autorisait les virgules finales dans les imports de namespaces groupés. 7.3 étend ça aux appels de fonctions et de méthodes :</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>$result <span style="color:#f92672">=</span> <span style="color:#a6e22e">array_merge</span>(
</span></span><span style="display:flex;"><span>    $defaults,
</span></span><span style="display:flex;"><span>    $overrides,
</span></span><span style="display:flex;"><span>    $extras, <span style="color:#75715e">// plus besoin de retirer cette virgule avant la parenthèse fermante
</span></span></span><span style="display:flex;"><span>);
</span></span></code></pre></div><p>Ça compte surtout pour les appels multiligne. Ajouter ou retirer un argument ne nécessite plus de toucher à la ligne adjacente. Les diffs restent honnêtes, les rebases deviennent un peu moins douloureux.</p>
<h2 id="assignments-par-référence-dans-la-déstructuration-de-tableaux">Assignments par référence dans la déstructuration de tableaux</h2>
<p>La déstructuration de tableaux a gagné la capacité de capturer des références plutôt que des copies :</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>$data <span style="color:#f92672">=</span> [<span style="color:#e6db74">&#39;Alice&#39;</span>, <span style="color:#ae81ff">42</span>];
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>[<span style="color:#f92672">&amp;</span>$name, $age] <span style="color:#f92672">=</span> $data;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>$name <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;Bob&#39;</span>;
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">var_dump</span>($data[<span style="color:#ae81ff">0</span>]); <span style="color:#75715e">// string(3) &#34;Bob&#34;
</span></span></span></code></pre></div><p>Les références imbriquées fonctionnent aussi :</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>[$a, [<span style="color:#f92672">&amp;</span>$b]] <span style="color:#f92672">=</span> [<span style="color:#ae81ff">1</span>, [<span style="color:#ae81ff">2</span>]];
</span></span></code></pre></div><p>Plus de niche que les virgules finales, mais le bon outil quand on a besoin d&rsquo;aliaser profondément dans une structure sans un tas d&rsquo;assignations intermédiaires.</p>
<h2 id="instanceof-avec-des-littéraux-est-maintenant-légal"><code>instanceof</code> avec des littéraux est maintenant légal</h2>
<p>Avant, utiliser <code>instanceof</code> avec un littéral à gauche était une erreur de parsing. 7.3 le rend valide :</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">var_dump</span>(<span style="color:#66d9ef">null</span> <span style="color:#a6e22e">instanceof</span> <span style="color:#66d9ef">stdClass</span>); <span style="color:#75715e">// bool(false)
</span></span></span></code></pre></div><p>Ça retourne toujours <code>false</code>, ce qui est exactement correct. L&rsquo;avantage, c&rsquo;est que du code qui construit conditionnellement une valeur puis vérifie son type n&rsquo;a plus besoin d&rsquo;extraire la valeur dans une variable au préalable. Utile dans le code généré et les helpers de test.</p>
<h2 id="json_decode-et-json_encode-peuvent-maintenant-lever-des-exceptions"><code>json_decode()</code> et <code>json_encode()</code> peuvent maintenant lever des exceptions</h2>
<p>Avant 7.3, les erreurs JSON étaient silencieuses à moins de penser à vérifier <code>json_last_error()</code>. Facile à oublier, facile à rater :</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>$data <span style="color:#f92672">=</span> <span style="color:#a6e22e">json_decode</span>($response);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">json_last_error</span>() <span style="color:#f92672">!==</span> <span style="color:#a6e22e">JSON_ERROR_NONE</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// la plupart des gens oubliaient cette partie
</span></span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>7.3 ajoute <code>JSON_THROW_ON_ERROR</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>$data <span style="color:#f92672">=</span> <span style="color:#a6e22e">json_decode</span>($response, <span style="color:#66d9ef">true</span>, <span style="color:#ae81ff">512</span>, <span style="color:#a6e22e">JSON_THROW_ON_ERROR</span>);
</span></span><span style="display:flex;"><span><span style="color:#75715e">// lève JsonException sur une entrée malformée
</span></span></span></code></pre></div><p><code>JsonException</code> étend <code>RuntimeException</code>. Attrapez-la spécifiquement ou laissez-la se propager. Ça aurait dû fonctionner comme ça dès le début.</p>
<h2 id="setcookie-avec-un-tableau-doptions"><code>setcookie()</code> avec un tableau d&rsquo;options</h2>
<p>L&rsquo;ancienne signature de <code>setcookie()</code> est un vestige : sept arguments positionnels, dont la plupart qu&rsquo;on laisse à leurs valeurs par défaut juste pour atteindre celui qu&rsquo;on veut vraiment. 7.3 ajoute une forme alternative qui prend un tableau associatif :</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">setcookie</span>(<span style="color:#e6db74">&#39;session&#39;</span>, $token, [
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#39;expires&#39;</span>  <span style="color:#f92672">=&gt;</span> <span style="color:#a6e22e">time</span>() <span style="color:#f92672">+</span> <span style="color:#ae81ff">3600</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#39;path&#39;</span>     <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#39;/&#39;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#39;secure&#39;</span>   <span style="color:#f92672">=&gt;</span> <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#39;httponly&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#39;samesite&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#39;Lax&#39;</span>,
</span></span><span style="display:flex;"><span>]);
</span></span></code></pre></div><p>L&rsquo;option <code>samesite</code> est la vraie raison pour laquelle ça a été ajouté — l&rsquo;ancienne signature positionnelle n&rsquo;avait pas de slot pour elle. <code>session_set_cookie_params()</code> a reçu le même traitement, et une nouvelle directive ini <code>session.cookie_samesite</code> couvre la valeur par défaut.</p>
<h2 id="hrtime-pour-un-benchmarking-qui-mesure-vraiment-le-temps"><code>hrtime()</code> pour un benchmarking qui mesure vraiment le temps</h2>
<p><code>microtime()</code> lit l&rsquo;horloge murale. Très bien pour la plupart des cas. Mais elle est affectée par les ajustements NTP, et sa résolution dépend de l&rsquo;implémentation. <code>hrtime()</code> lit l&rsquo;horloge monotone haute résolution :</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>$start <span style="color:#f92672">=</span> <span style="color:#a6e22e">hrtime</span>(<span style="color:#66d9ef">true</span>);  <span style="color:#75715e">// nanosecondes sous forme d&#39;entier
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">doWork</span>();
</span></span><span style="display:flex;"><span>$elapsed <span style="color:#f92672">=</span> <span style="color:#a6e22e">hrtime</span>(<span style="color:#66d9ef">true</span>) <span style="color:#f92672">-</span> $start;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> $elapsed <span style="color:#f92672">/</span> <span style="color:#ae81ff">1e6</span> <span style="color:#f92672">.</span> <span style="color:#e6db74">&#34; ms</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>;
</span></span></code></pre></div><p>Sans l&rsquo;argument <code>true</code>, elle retourne <code>[secondes, nanosecondes]</code> sous forme d&rsquo;un tableau à deux éléments. Utilisez ça pour les microbenchmarks, ou partout où la dérive d&rsquo;horloge corromprait silencieusement vos mesures.</p>
<h2 id="gc_status--regarder-à-lintérieur-du-ramasse-miettes"><code>gc_status()</code> — regarder à l&rsquo;intérieur du ramasse-miettes</h2>
<p>Le ramasse-miettes cyclique de PHP se déclenche quand un buffer de cycles potentiels se remplit. Jusqu&rsquo;à 7.3 il n&rsquo;y avait pas de moyen simple de voir ce qu&rsquo;il faisait réellement. <code>gc_status()</code> expose l&rsquo;état interne :</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>$status <span style="color:#f92672">=</span> <span style="color:#a6e22e">gc_status</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;runs&#39;       =&gt; 3,
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">//   &#39;collected&#39;  =&gt; 127,
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">//   &#39;threshold&#39;  =&gt; 10001,
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">//   &#39;roots&#39;      =&gt; 42,
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">// ]
</span></span></span></code></pre></div><p>Pas quelque chose que la plupart du code applicatif a besoin. Utile quand on essaie de comprendre pourquoi la mémoire continue de grimper sous des charges de travail spécifiques.</p>
<h2 id="compileerror-rejoint-la-hiérarchie-des-exceptions"><code>CompileError</code> rejoint la hiérarchie des exceptions</h2>
<p>Les erreurs de parsing sont catchables en tant que <code>ParseError</code> depuis PHP 7.0. 7.3 introduit <code>CompileError</code> comme classe parente pour les échecs à la compilation, avec <code>ParseError</code> qui devient une sous-classe :</p>
<pre tabindex="0"><code>Error
└── CompileError
    └── ParseError
</code></pre><p>En pratique, le code qui catch <code>ParseError</code> continue de fonctionner. La nouvelle classe donne juste aux futures erreurs de compilation (qui ne sont pas des erreurs de parsing) une place correcte dans la hiérarchie.</p>
<h2 id="bcscale-comme-getter"><code>bcscale()</code> comme getter</h2>
<p>L&rsquo;échelle BC Math était toujours settable via <code>bcscale($n)</code>. Obtenir l&rsquo;échelle actuelle nécessitait de la suivre soi-même. 7.3 fait fonctionner <code>bcscale()</code> sans arguments :</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">bcscale</span>(<span style="color:#ae81ff">4</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> <span style="color:#a6e22e">bcscale</span>(); <span style="color:#75715e">// 4
</span></span></span></code></pre></div><p>Mineur. Utile à savoir si vous écrivez du code de bibliothèque qui doit respecter ou restaurer le paramètre d&rsquo;échelle de l&rsquo;appelant.</p>
<h2 id="lavertissement-pour-continue-dans-un-switch">L&rsquo;avertissement pour <code>continue</code> dans un <code>switch</code></h2>
<p>Celui-là est un correctif d&rsquo;exactitude qui ressemble à une dépréciation. En PHP, <code>continue</code> dans un <code>switch</code> s&rsquo;est toujours comporté comme <code>break</code> — il sort du switch, pas de la boucle englobante. Les développeurs venant d&rsquo;autres langages écrivent souvent ça en espérant passer à l&rsquo;itération suivante de la boucle :</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">foreach</span> ($items <span style="color:#66d9ef">as</span> $item) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">switch</span> ($item<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">type</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">case</span> <span style="color:#e6db74">&#39;skip&#39;</span><span style="color:#f92672">:</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">continue</span>; <span style="color:#75715e">// FAUX : sort du switch, pas du foreach
</span></span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>7.3 ajoute un warning pour ce pattern. Le correctif, c&rsquo;est <code>continue 2</code> pour cibler explicitement la boucle englobante. Le comportement n&rsquo;a pas changé. Le silence, si.</p>
<h2 id="dépréciations">Dépréciations</h2>
<p>Les constantes insensibles à la casse déclarées via <code>define()</code> sont dépréciées :</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">define</span>(<span style="color:#e6db74">&#39;MY_CONST&#39;</span>, <span style="color:#ae81ff">42</span>, <span style="color:#66d9ef">true</span>); <span style="color:#75715e">// troisième argument déprécié
</span></span></span></code></pre></div><p>Passer une needle non-chaîne à <code>strpos()</code>, <code>strstr()</code>, et fonctions similaires est déprécié. En PHP 8, ces fonctions interpréteront la needle comme une chaîne, pas comme un codepoint ASCII. Si vous passez intentionnellement des entiers à ces fonctions, <code>chr($n)</code> est la forme explicite.</p>
<p><code>fgetss()</code> est déprécié — c&rsquo;était <code>fgets()</code> avec les balises HTML/PHP retirées. Utilisez <code>fgets()</code> et retirez les balises explicitement si nécessaire. Le filtre de stream <code>string.strip_tags</code> disparaît avec lui.</p>
<p>7.3 est le genre de version qu&rsquo;on apprécie avec du recul. Rien d&rsquo;individuellement dramatique, mais après six mois avec elle, la correction des heredocs seule a amorti le coût de la migration en lisibilité. Parfois le banal est exactement ce qu&rsquo;il faut.</p>
]]></content:encoded></item><item><title>PHP 7.2 : adieu mcrypt, bonjour sodium</title><link>https://guillaumedelre.github.io/fr/2018/01/14/php-7.2-adieu-mcrypt-bonjour-sodium/</link><pubDate>Sun, 14 Jan 2018 00:00:00 +0000</pubDate><guid>https://guillaumedelre.github.io/fr/2018/01/14/php-7.2-adieu-mcrypt-bonjour-sodium/</guid><description>Part 3 of 11 in &amp;quot;Sorties PHP&amp;quot;: PHP 7.2 supprime l&amp;#39;extension mcrypt, abandonnée depuis des années, et intègre libsodium — la cryptographie PHP entre enfin dans le XXIe siècle.</description><category>php-releases</category><content:encoded><![CDATA[<p>PHP 7.2 est sorti le 30 novembre. La grande nouvelle n&rsquo;est pas une nouvelle fonctionnalité, c&rsquo;est une suppression. <code>mcrypt</code> disparaît.</p>
<p>C&rsquo;est une bonne chose, même si ça ne le semble pas quand c&rsquo;est vous qui devez faire la migration.</p>
<h2 id="le-problème-mcrypt">Le problème mcrypt</h2>
<p><code>mcrypt</code> n&rsquo;est plus maintenu depuis 2007. Plus d&rsquo;une décennie de stagnation dans une bibliothèque cryptographique. Dépréciée en 7.1, elle est retirée définitivement en 7.2. Son remplaçant : <code>sodium</code>, désormais intégré comme extension core.</p>
<p>Sodium est le binding PHP pour <a href="https://libsodium.org" target="_blank" rel="noopener noreferrer">libsodium</a>, une bibliothèque cryptographique moderne conçue autour de valeurs par défaut sûres. Là où mcrypt vous demandait de choisir le bon algorithme, le bon mode et le bon padding (et vous laissait vous planter silencieusement), l&rsquo;API de sodium rend les choix dangereux structurellement difficiles. <code>sodium_crypto_secretbox()</code> pour le chiffrement symétrique, <code>sodium_crypto_box()</code> pour l&rsquo;asymétrique, <code>sodium_crypto_sign()</code> pour les signatures. Les noms disent ce qu&rsquo;on fait.</p>
<p>Si vous avez du code mcrypt en production, la migration est inévitable. Et ça vaut le coup de le faire soigneusement : du code cryptographique qui &ldquo;fonctionne encore&rdquo; peut être silencieusement cassé d&rsquo;une façon que vous ne remarquerez qu&rsquo;il sera trop tard.</p>
<h2 id="le-type-hint-object">Le type hint <code>object</code></h2>
<p>7.2 ajoute <code>object</code> comme type de paramètre et de retour :</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">function</span> <span style="color:#a6e22e">serialize</span>(<span style="color:#a6e22e">object</span> $data)<span style="color:#f92672">:</span> <span style="color:#a6e22e">string</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// accepte n&#39;importe quel objet
</span></span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>C&rsquo;est large — n&rsquo;importe quel objet le satisfait — mais c&rsquo;est mieux qu&rsquo;aucun type du tout quand vous ne vous souciez vraiment pas de la classe spécifique. Complète les types existants <code>array</code>, <code>callable</code> et les hints par classe concrète.</p>
<h2 id="argon2-dans-password_hash">Argon2 dans password_hash</h2>
<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>$hash <span style="color:#f92672">=</span> <span style="color:#a6e22e">password_hash</span>($password, <span style="color:#a6e22e">PASSWORD_ARGON2I</span>);
</span></span></code></pre></div><p><code>PASSWORD_BCRYPT</code> était la valeur par défaut et reste raisonnable, mais Argon2 a remporté le Password Hashing Competition pour une bonne raison : il est memory-hard, ce qui rend le craquage par GPU significativement plus coûteux. Vaut la peine de basculer pour les nouvelles apps.</p>
<p>7.2 est davantage une version sécurité qu&rsquo;une version langage. Supprimez mcrypt, ajoutez sodium, et vous placez la plateforme dans un état où on peut lui faire confiance avec des données sensibles. Les fonctionnalités du langage sont incrémentales. Le changement d&rsquo;infrastructure, lui, ne l&rsquo;est pas.</p>
<h2 id="des-types-de-paramètres-quon-peut-désormais-omettre-intentionnellement">Des types de paramètres qu&rsquo;on peut désormais omettre intentionnellement</h2>
<p>7.2 formalise quelque chose qui était jusqu&rsquo;ici juste une odeur de code : quand vous implémentez une interface ou surchargez une méthode, vous pouvez maintenant omettre complètement le type du paramètre. C&rsquo;est de la contravariance valide au sens du principe de substitution de Liskov.</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">interface</span> <span style="color:#a6e22e">Serializable</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">serialize</span>(<span style="color:#66d9ef">array</span> $data)<span style="color:#f92672">:</span> <span style="color:#a6e22e">string</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">class</span> <span style="color:#a6e22e">JsonSerializer</span> <span style="color:#66d9ef">implements</span> <span style="color:#a6e22e">Serializable</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">serialize</span>($data)<span style="color:#f92672">:</span> <span style="color:#a6e22e">string</span> { <span style="color:#75715e">// pas de type — accepte plus, toujours valide
</span></span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">json_encode</span>($data);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Ça paraît bizarre au premier abord. Mais c&rsquo;est logiquement correct : une méthode qui accepte tout est strictement plus permissive qu&rsquo;une qui n&rsquo;accepte que des tableaux. Le système de types est d&rsquo;accord, même si votre relecteur de code lève un sourcil.</p>
<h2 id="des-méthodes-abstraites-qui-évoluent">Des méthodes abstraites qui évoluent</h2>
<p>Quand une classe abstraite étend une autre classe abstraite, elle peut désormais surcharger la méthode abstraite avec une signature différente. La contrainte est directionnelle : les paramètres peuvent être élargis (contravariants), les types de retour peuvent être resserrés (covariants).</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">abstract</span> <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">BaseProcessor</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">abstract</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">process</span>(<span style="color:#a6e22e">string</span> $input);
</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">abstract</span> <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">TypedProcessor</span> <span style="color:#66d9ef">extends</span> <span style="color:#a6e22e">BaseProcessor</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">abstract</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">process</span>($input)<span style="color:#f92672">:</span> <span style="color:#a6e22e">int</span>; <span style="color:#75715e">// paramètre élargi, type de retour ajouté
</span></span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>C&rsquo;était refusé avant 7.2. Ça débloque des abstractions intermédiaires sans forcer chaque classe feuille à répéter la même signature.</p>
<h2 id="virgule-finale-dans-les-imports-groupés">Virgule finale dans les imports groupés</h2>
<p>Petit, mais je remarque son absence quand elle manque. Les imports de namespaces groupés peuvent désormais avoir une virgule finale après le dernier élé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-php" data-lang="php"><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">App\Services\</span>{
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">UserService</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">OrderService</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">NotificationService</span>, <span style="color:#75715e">// virgule ici — enfin
</span></span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p>Ça signifie qu&rsquo;on peut réordonner ou ajouter des lignes sans toucher à la ligne précédente. Les diffs git deviennent plus propres, les conflits de merge plus rares.</p>
<h2 id="count-a-développé-une-conscience"><code>count()</code> a développé une conscience</h2>
<p>Avant 7.2, <code>count(null)</code> retournait 0. Silencieusement. Sans warning. C&rsquo;est exactement le genre de chose qui enterre un bug pendant des mois. Maintenant, ça émet un <code>E_WARNING</code> quand vous passez quelque chose qui n&rsquo;est ni un tableau ni un objet <code>Countable</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:#a6e22e">count</span>(<span style="color:#66d9ef">null</span>);  <span style="color:#75715e">// Warning: count(): Parameter must be an array or an object that implements Countable
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">count</span>(<span style="color:#ae81ff">42</span>);    <span style="color:#75715e">// pareil
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">count</span>(<span style="color:#e6db74">&#34;hi&#34;</span>);  <span style="color:#75715e">// pareil
</span></span></span></code></pre></div><p>Le comportement n&rsquo;a pas changé pour les entrées valides. Seul le silence a été brisé. C&rsquo;est la bonne direction.</p>
<h2 id="spl_object_id--ce-que-vous-émuliez-avec-splobjectstorage"><code>spl_object_id()</code> — ce que vous émuliez avec SplObjectStorage</h2>
<p>Si vous avez déjà construit une map indexée par identité d&rsquo;objet, vous avez écrit quelque chose comme ça :</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>$storage <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">SplObjectStorage</span>();
</span></span><span style="display:flex;"><span>$storage[$obj] <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
</span></span></code></pre></div><p>7.2 ajoute <code>spl_object_id()</code>, qui retourne un entier unique pour la durée de vie d&rsquo;un objet. C&rsquo;est le même handle interne qu&rsquo;utilise <code>SplObjectStorage</code>, rendu directement accessible :</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>$id <span style="color:#f92672">=</span> <span style="color:#a6e22e">spl_object_id</span>($obj); <span style="color:#75715e">// ex. 42
</span></span></span><span style="display:flex;"><span>$map[$id] <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;something&#39;</span>;
</span></span></code></pre></div><p>L&rsquo;entier est réutilisé après la destruction de l&rsquo;objet, donc ne le conservez pas au-delà de la durée de vie de l&rsquo;objet. Dans un contexte bien délimité cependant, c&rsquo;est une clé d&rsquo;identité peu coûteuse.</p>
<h2 id="pdo--les-chaînes-de-caractères-nationales">PDO : les chaînes de caractères nationales</h2>
<p>Quand on travaille avec des bases de données qui distinguent les types de chaînes régulières et nationales (Oracle, SQL Server), 7.2 ajoute les flags nécessaires :</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>$stmt <span style="color:#f92672">=</span> $pdo<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">prepare</span>(<span style="color:#e6db74">&#34;SELECT * FROM users WHERE name = ?&#34;</span>);
</span></span><span style="display:flex;"><span>$stmt<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">bindValue</span>(<span style="color:#ae81ff">1</span>, <span style="color:#e6db74">&#39;Ångström&#39;</span>, <span style="color:#a6e22e">PDO</span><span style="color:#f92672">::</span><span style="color:#a6e22e">PARAM_STR</span> <span style="color:#f92672">|</span> <span style="color:#a6e22e">PDO</span><span style="color:#f92672">::</span><span style="color:#a6e22e">PARAM_STR_NATL</span>);
</span></span></code></pre></div><p>Ou définir une valeur par défaut au niveau de la connexion :</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>$pdo<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">setAttribute</span>(<span style="color:#a6e22e">PDO</span><span style="color:#f92672">::</span><span style="color:#a6e22e">ATTR_DEFAULT_STR_PARAM</span>, <span style="color:#a6e22e">PDO</span><span style="color:#f92672">::</span><span style="color:#a6e22e">PARAM_STR_NATL</span>);
</span></span></code></pre></div><p><code>PDO::PARAM_STR_NATL</code> indique que la chaîne est un type caractère national (NCHAR/NVARCHAR). Obscur, certes. Indispensable si vous avez déjà vu vos données Unicode ressortir déformées parce que le driver ne faisait pas la différence.</p>
<h2 id="gd-supporte-les-bmp-et-les-rectangles-de-clipping">GD supporte les BMP et les rectangles de clipping</h2>
<p>Deux choses à connaître. D&rsquo;abord, les fichiers BMP sont désormais des citoyens de première classe dans l&rsquo;extension GD :</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>$image <span style="color:#f92672">=</span> <span style="color:#a6e22e">imagecreatefrombmp</span>(<span style="color:#e6db74">&#39;photo.bmp&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">imagebmp</span>($image, <span style="color:#e6db74">&#39;output.bmp&#39;</span>);
</span></span></code></pre></div><p>Ensuite, on peut maintenant définir un rectangle de clipping pour que les opérations de dessin n&rsquo;affectent qu&rsquo;une portion de l&rsquo;image :</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">imagesetclip</span>($image, <span style="color:#ae81ff">10</span>, <span style="color:#ae81ff">10</span>, <span style="color:#ae81ff">200</span>, <span style="color:#ae81ff">150</span>); <span style="color:#75715e">// x1, y1, x2, y2
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">// tout ce qui est dessiné en dehors de ce rectangle est silencieusement ignoré
</span></span></span></code></pre></div><p>Aucune de ces fonctionnalités ne transforme la façon dont la plupart des apps fonctionnent, mais les deux remplacent &ldquo;installer une bibliothèque supplémentaire&rdquo; par &ldquo;c&rsquo;est juste dans le core maintenant.&rdquo;</p>
<h2 id="mb_chr-et-mb_ord--le-chr-et-ord-dunicode"><code>mb_chr()</code> et <code>mb_ord()</code> — le <code>chr()</code> et <code>ord()</code> d&rsquo;Unicode</h2>
<p>PHP a <code>chr()</code> et <code>ord()</code> depuis toujours. Ils travaillent sur des octets. Pour les points de code Unicode, vous étiez livré à vous-même. 7.2 ajoute les équivalents multibyte :</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>$char <span style="color:#f92672">=</span> <span style="color:#a6e22e">mb_chr</span>(<span style="color:#ae81ff">0x1F600</span>); <span style="color:#75715e">// retourne l&#39;emoji 😀
</span></span></span><span style="display:flex;"><span>$code <span style="color:#f92672">=</span> <span style="color:#a6e22e">mb_ord</span>(<span style="color:#e6db74">&#39;é&#39;</span>);     <span style="color:#75715e">// retourne 233
</span></span></span></code></pre></div><p>Et <code>mb_scrub()</code>, qui supprime les séquences d&rsquo;octets invalides d&rsquo;une chaîne plutôt que d&rsquo;échouer silencieusement ou de lancer une exception :</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>$clean <span style="color:#f92672">=</span> <span style="color:#a6e22e">mb_scrub</span>($untrustedInput, <span style="color:#e6db74">&#39;UTF-8&#39;</span>);
</span></span></code></pre></div><p>Pratique à toute frontière externe : réponses API, uploads de fichiers, lectures en base depuis des systèmes legacy.</p>
<h2 id="dépréciations-à-connaître-avant-larrivée-de-74">Dépréciations à connaître avant l&rsquo;arrivée de 7.4</h2>
<p>Plusieurs choses ont été mollement dépréciées en 7.2 et deviendront des erreurs dans les versions ultérieures. Celles qui risquent le plus de piquer :</p>
<p><code>__autoload()</code> est déprécié. Si vous enregistrez encore une fonction d&rsquo;autoload globale au lieu d&rsquo;utiliser <code>spl_autoload_register()</code>, corrigez ça avant que ça devienne fatal.</p>
<p><code>create_function()</code> est déprécié. C&rsquo;est un wrapper autour de <code>eval()</code> et ça a toujours été dangereux. Utilisez une closure :</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>$fn <span style="color:#f92672">=</span> <span style="color:#a6e22e">create_function</span>(<span style="color:#e6db74">&#39;$x&#39;</span>, <span style="color:#e6db74">&#39;return $x * 2;&#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>$fn <span style="color:#f92672">=</span> <span style="color:#a6e22e">fn</span>($x) <span style="color:#f92672">=&gt;</span> $x <span style="color:#f92672">*</span> <span style="color:#ae81ff">2</span>;
</span></span></code></pre></div><p><code>each()</code> est déprécié. Le pattern de boucle qu&rsquo;il permettait s&rsquo;écrit mieux avec <code>foreach</code>. Aucune perte ici.</p>
<p><code>parse_str()</code> sans second argument déverse les valeurs parsées dans la table des symboles locale — un problème de sécurité qui n&rsquo;aurait jamais dû être autorisé. Passez toujours la variable de sortie :</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">parse_str</span>($queryString, $params); <span style="color:#75715e">// correct
</span></span></span></code></pre></div><p>Le cast <code>(unset)</code> est déprécié parce qu&rsquo;il retourne toujours <code>null</code>, que vous pouvez simplement écrire <code>null</code>. Une syntaxe complètement inutile qui n&rsquo;aurait jamais dû exister.</p>
]]></content:encoded></item><item><title>Forcer l'UTC dans Doctrine sans toucher aux entités</title><link>https://guillaumedelre.github.io/fr/2017/02/19/forcer-lutc-dans-doctrine-sans-toucher-aux-entit%C3%A9s/</link><pubDate>Sun, 19 Feb 2017 00:00:00 +0000</pubDate><guid>https://guillaumedelre.github.io/fr/2017/02/19/forcer-lutc-dans-doctrine-sans-toucher-aux-entit%C3%A9s/</guid><description>Comment surcharger les types natifs de Doctrine pour imposer l&amp;#39;UTC partout, sans modifier une seule entité.</description><content:encoded><![CDATA[<p>Un timestamp qui revient de la base de données avec une heure de décalage. Pas à chaque fois. Uniquement quand le serveur de dev tourne en <code>Europe/Paris</code> et que la CI tourne en UTC. Le genre de bug qui disparaît quand on le cherche et qui revient en production un vendredi soir.</p>
<p>Le problème n&rsquo;est pas dans la logique métier. Il est dans ce que Doctrine fait discrètement avec les dates.</p>
<h2 id="ce-que-doctrine-fait-par-défaut">Ce que Doctrine fait par défaut</h2>
<p>Quand on déclare un champ <code>datetime</code> dans une entité Doctrine, la conversion entre PHP et la base de données passe par <code>DateTimeType</code>. Cette classe appelle <code>format()</code> sur l&rsquo;objet <code>DateTime</code> pour écrire en base, et <code>DateTime::createFromFormat()</code> pour le relire. Aucune mention de timezone nulle part.</p>
<p>Si l&rsquo;objet PHP est en <code>Europe/Paris</code>, Doctrine formate <code>2017-01-15 11:30:00</code> et l&rsquo;écrit tel quel. Si le serveur qui lit ce champ est en UTC, il obtient <code>2017-01-15 11:30:00</code> et l&rsquo;interprète comme UTC. Une heure s&rsquo;est évaporée dans l&rsquo;aller-retour, sans le moindre message d&rsquo;erreur.</p>
<p><a href="https://www.doctrine-project.org/projects/doctrine-orm/en/latest/cookbook/working-with-datetime.html" target="_blank" rel="noopener noreferrer">La doc Doctrine couvre ce sujet</a> et suggère des types personnalisés comme solution. Ce qu&rsquo;elle mentionne en passant, c&rsquo;est qu&rsquo;on peut donner à ces types personnalisés le même nom que les types natifs. Ce détail change tout.</p>
<h2 id="remplacer-pas-ajouter">Remplacer, pas ajouter</h2>
<p>La plupart des exemples de types Doctrine personnalisés introduisent un nouveau nom : <code>utc_datetime</code>, <code>app_date</code>, et ainsi de suite. On annote ensuite chaque champ avec <code>type: 'utc_datetime'</code> dans les entités. Ça fonctionne, mais c&rsquo;est fastidieux et ça ne protège pas contre un <code>type: 'datetime'</code> oublié.</p>
<p>L&rsquo;autre option : enregistrer le type personnalisé sous le nom <code>datetime</code>. Doctrine remplace sa propre implémentation par la nôtre, partout, sans exception. Chaque champ <code>datetime</code> de toutes les entités passe par notre logique, sans changer une seule annotation.</p>
<p>C&rsquo;est ce qu&rsquo;on vient de déployer sur notre plateforme de microservices PHP. Voici à quoi ça ressemble.</p>
<h2 id="le-trait-partagé">Le trait partagé</h2>
<p>Les deux types (<code>date</code> et <code>datetime</code>) partagent la même logique de conversion via un trait :</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">trait</span> <span style="color:#a6e22e">UTCDate</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">private</span> <span style="color:#a6e22e">\DateTimeZone</span> $utc;
</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">convertToPHPValue</span>($value, <span style="color:#a6e22e">AbstractPlatform</span> $platform)<span style="color:#f92672">:</span> <span style="color:#f92672">?</span><span style="color:#a6e22e">\DateTime</span>
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (<span style="color:#66d9ef">null</span> <span style="color:#f92672">===</span> $value <span style="color:#f92672">||</span> $value <span style="color:#a6e22e">instanceof</span> <span style="color:#a6e22e">\DateTime</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> $value;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        $format <span style="color:#f92672">=</span> $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">getFormat</span>($platform);
</span></span><span style="display:flex;"><span>        $converted <span style="color:#f92672">=</span> <span style="color:#a6e22e">\DateTime</span><span style="color:#f92672">::</span><span style="color:#a6e22e">createFromFormat</span>($format, $value, $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">getUtc</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>$converted) {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">throw</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">\RuntimeException</span>(
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">sprintf</span>(<span style="color:#e6db74">&#39;Could not convert database value &#34;%s&#34; to DateTime using format &#34;%s&#34;.&#39;</span>, $value, $format)
</span></span><span style="display:flex;"><span>            );
</span></span><span style="display:flex;"><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">postConvert</span>($converted);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> $converted;
</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">abstract</span> <span style="color:#66d9ef">protected</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">getFormat</span>(<span style="color:#a6e22e">AbstractPlatform</span> $platform)<span style="color:#f92672">:</span> <span style="color:#a6e22e">string</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">private</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">getUtc</span>()<span style="color:#f92672">:</span> <span style="color:#a6e22e">\DateTimeZone</span>
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (<span style="color:#66d9ef">empty</span>($this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">utc</span>)) {
</span></span><span style="display:flex;"><span>            $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">utc</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">\DateTimeZone</span>(<span style="color:#e6db74">&#39;UTC&#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">return</span> $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">utc</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Le point clé : <code>\DateTime::createFromFormat()</code> reçoit une timezone UTC explicite. La valeur brute issue de la base est interprétée comme UTC, peu importe la timezone configurée sur le serveur PHP.</p>
<h2 id="utcdatetimetype">UTCDateTimeType</h2>
<p>Pour les champs <code>datetime</code>, le chemin d&rsquo;écriture doit aussi imposer l&rsquo;UTC :</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">UTCDateTimeType</span> <span style="color:#66d9ef">extends</span> <span style="color:#a6e22e">DateTimeType</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">use</span> <span style="color:#a6e22e">UTCDate</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">#[\Override]
</span></span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">convertToPHPValue</span>($value, <span style="color:#a6e22e">AbstractPlatform</span> $platform)<span style="color:#f92672">:</span> <span style="color:#f92672">?</span><span style="color:#a6e22e">\DateTime</span>
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (<span style="color:#66d9ef">null</span> <span style="color:#f92672">===</span> $value <span style="color:#f92672">||</span> $value <span style="color:#a6e22e">instanceof</span> <span style="color:#a6e22e">\DateTimeInterface</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">parent</span><span style="color:#f92672">::</span><span style="color:#a6e22e">convertToPHPValue</span>($value, $platform);
</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">return</span> <span style="color:#66d9ef">parent</span><span style="color:#f92672">::</span><span style="color:#a6e22e">convertToPHPValue</span>(<span style="color:#e6db74">&#34;</span><span style="color:#e6db74">$value</span><span style="color:#e6db74">+0000&#34;</span>, $platform);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">#[\Override]
</span></span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">convertToDatabaseValue</span>($value, <span style="color:#a6e22e">AbstractPlatform</span> $platform)<span style="color:#f92672">:</span> <span style="color:#f92672">?</span><span style="color:#a6e22e">string</span>
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> ($value <span style="color:#a6e22e">instanceof</span> <span style="color:#a6e22e">\DateTime</span>) {
</span></span><span style="display:flex;"><span>            $value<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">setTimezone</span>($this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">getUtc</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">return</span> <span style="color:#66d9ef">parent</span><span style="color:#f92672">::</span><span style="color:#a6e22e">convertToDatabaseValue</span>($value, $platform);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">#[\Override]
</span></span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">protected</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">getFormat</span>(<span style="color:#a6e22e">AbstractPlatform</span> $platform)<span style="color:#f92672">:</span> <span style="color:#a6e22e">string</span>
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> $platform<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">getDateTimeFormatString</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">protected</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">postConvert</span>(<span style="color:#a6e22e">\DateTime</span> $converted)<span style="color:#f92672">:</span> <span style="color:#a6e22e">void</span> {}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>En lecture (<code>convertToPHPValue</code>), si la valeur est une chaîne brute, on y ajoute <code>+0000</code> avant de déléguer au parent. Le parent utilise ensuite ce suffixe de timezone pour créer correctement l&rsquo;objet PHP.</p>
<p>En écriture (<code>convertToDatabaseValue</code>), on force le <code>DateTime</code> en UTC avant de le formater. Ce qui va en base est toujours en UTC.</p>
<h2 id="utcdatetype">UTCDateType</h2>
<p>Pour les colonnes <code>date</code> (sans composante horaire), même approche avec une étape supplémentaire :</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">UTCDateType</span> <span style="color:#66d9ef">extends</span> <span style="color:#a6e22e">DateType</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">use</span> <span style="color:#a6e22e">UTCDate</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">#[\Override]
</span></span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">protected</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">getFormat</span>(<span style="color:#a6e22e">AbstractPlatform</span> $platform)<span style="color:#f92672">:</span> <span style="color:#a6e22e">string</span>
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> $platform<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">getDateFormatString</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">protected</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">postConvert</span>(<span style="color:#a6e22e">\DateTime</span> $converted)<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>        $converted<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">setTime</span>(<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">0</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>La méthode <code>postConvert()</code> remet l&rsquo;heure à <code>00:00:00</code> après le parsing. Sans elle, un champ <code>date</code> pourrait revenir avec <code>23:59:59</code> ou <code>00:00:00+02:00</code> selon la timezone du serveur, ce qui casse les comparaisons et le tri.</p>
<h2 id="enregistrement-dans-symfony">Enregistrement dans Symfony</h2>
<p>La partie décisive : déclarer les types sous leurs noms natifs dans <code>config/packages/doctrine.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">doctrine</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">dbal</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">types</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">date</span>:
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">class</span>: <span style="color:#ae81ff">App\Doctrine\DBAL\Types\UTCDateType</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">datetime</span>:
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">class</span>: <span style="color:#ae81ff">App\Doctrine\DBAL\Types\UTCDateTimeType</span>
</span></span></code></pre></div><p>C&rsquo;est tout. Doctrine échange ses propres implémentations contre les nôtres. Les entités existantes ne changent pas, les migrations ne bougent pas, les annotations restent <code>type: Types::DATETIME_MUTABLE</code>. Le comportement change globalement, sans friction.</p>
<h2 id="12-microservices-89-colonnes-un-bloc-de-config">12 microservices, 89 colonnes, un bloc de config</h2>
<p>Ces deux types tournent maintenant sur 12 microservices indépendants, chacun avec sa propre config Doctrine, couvrant 89 colonnes de production. Les serveurs CI tournent en UTC, les machines de dev en <code>Europe/Paris</code>, les données voyagent entre eux sans dériver. Ce n&rsquo;est pas spectaculaire. C&rsquo;est juste fiable.</p>
<p>La vraie leçon n&rsquo;est pas technique : un problème de timezone non résolu est un problème d&rsquo;intégrité des données. Les décalages s&rsquo;accumulent silencieusement, les comparaisons se trompent, les exports deviennent inexacts. Deux lignes de config et trois classes peuvent prévenir ça définitivement.</p>
]]></content:encoded></item><item><title>PHP 7.1 : un système de types plus rigoureux et les petits gains autour</title><link>https://guillaumedelre.github.io/fr/2017/01/15/php-7.1-un-syst%C3%A8me-de-types-plus-rigoureux-et-les-petits-gains-autour/</link><pubDate>Sun, 15 Jan 2017 00:00:00 +0000</pubDate><guid>https://guillaumedelre.github.io/fr/2017/01/15/php-7.1-un-syst%C3%A8me-de-types-plus-rigoureux-et-les-petits-gains-autour/</guid><description>Part 2 of 11 in &amp;quot;Sorties PHP&amp;quot;: PHP 7.1 a comblé les lacunes laissées par la 7.0 : types nullables, retour void, et déstructuration — de petits ajouts qui ont rendu le système de types utilisable.</description><category>php-releases</category><content:encoded><![CDATA[<p>PHP 7.1 est sorti le 1er décembre. Pas de titre &ldquo;2x plus rapide&rdquo;, pas de réécriture du moteur. Il comble les lacunes que la 7.0 avait laissées dans le système de types, et ces lacunes étaient vraiment agaçantes.</p>
<h2 id="les-types-nullables">Les types nullables</h2>
<p>La 7.0 permettait de déclarer <code>string $name</code> comme type de paramètre. Ce qu&rsquo;elle ne permettait pas, c&rsquo;était de dire &ldquo;ça peut aussi être null&rdquo;. On devait soit abandonner le type hint complètement, soit bricoler autour. La 7.1 ajoute le préfixe <code>?</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">function</span> <span style="color:#a6e22e">findUser</span>(<span style="color:#f92672">?</span><span style="color:#a6e22e">int</span> $id)<span style="color:#f92672">:</span> <span style="color:#f92672">?</span><span style="color:#a6e22e">User</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> ($id <span style="color:#f92672">===</span> <span style="color:#66d9ef">null</span>) <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">null</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">repository</span><span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">find</span>($id);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Ça semble mineur. Ce n&rsquo;est pas le cas. Les types nullables font la différence entre une signature qui dit ce que fait une fonction et une qui ment par omission. Chaque codebase sur lequel j&rsquo;ai travaillé a des fonctions qui peuvent retourner null. Maintenant on peut vraiment le dire plutôt que de le cacher dans un docblock.</p>
<h2 id="le-type-de-retour-void">Le type de retour void</h2>
<p>Le complément du nullable : une fonction qui ne retourne intentionnellement 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-php" data-lang="php"><span style="display:flex;"><span><span style="color:#66d9ef">public</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">process</span>(<span style="color:#a6e22e">Order</span> $order)<span style="color:#f92672">:</span> <span style="color:#a6e22e">void</span> {
</span></span><span style="display:flex;"><span>    $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">dispatcher</span><span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">dispatch</span>(<span style="color:#66d9ef">new</span> <span style="color:#a6e22e">OrderProcessed</span>($order));
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><code>void</code> rend l&rsquo;intention explicite et empêche de retourner accidentellement une valeur depuis une fonction qui ne devrait pas. Combiné aux types nullables, le système de types de PHP en 7.1 est bien plus expressif qu&rsquo;en 7.0.</p>
<h2 id="la-visibilité-des-constantes-de-classe">La visibilité des constantes de classe</h2>
<p>Un petit correctif mais bienvenu. Les constantes dans les classes étaient toujours publiques avant la 7.1. Maintenant :</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">Config</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">private</span> <span style="color:#66d9ef">const</span> <span style="color:#66d9ef">DB_PASSWORD</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;secret&#39;</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">protected</span> <span style="color:#66d9ef">const</span> <span style="color:#66d9ef">VERSION</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;2.0&#39;</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">const</span> <span style="color:#66d9ef">MAX_RETRIES</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">3</span>;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Garder les détails d&rsquo;implémentation privés, ça compte. Ça aurait dû exister depuis le début.</p>
<h2 id="attraper-plusieurs-exceptions">Attraper plusieurs exceptions</h2>
<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">try</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// ...
</span></span></span><span style="display:flex;"><span>} <span style="color:#66d9ef">catch</span> (<span style="color:#a6e22e">InvalidArgumentException</span> <span style="color:#f92672">|</span> <span style="color:#a6e22e">RuntimeException</span> $e) {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// gérer les deux
</span></span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Évite un bloc catch dupliqué quand deux exceptions nécessitent un traitement identique. Simple, utile.</p>
<h2 id="déstructurer-des-tableaux-sans-list">Déstructurer des tableaux sans list()</h2>
<p><code>list()</code> est dans PHP depuis la 4.0 et a toujours semblé un peu à côté syntaxiquement. La 7.1 ajoute un raccourci avec <code>[]</code> qui se lit bien plus naturellement :</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>[$first, $second] <span style="color:#f92672">=</span> $coordinates;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">foreach</span> ($rows <span style="color:#66d9ef">as</span> [$id, $name, $email]) {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// ...
</span></span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Elle gagne aussi le support des clés, ce qui rend la déstructuration de tableaux associatifs enfin utilisable :</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:#e6db74">&#39;id&#39;</span> <span style="color:#f92672">=&gt;</span> $id, <span style="color:#e6db74">&#39;name&#39;</span> <span style="color:#f92672">=&gt;</span> $name] <span style="color:#f92672">=</span> $user;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">foreach</span> ($records <span style="color:#66d9ef">as</span> [<span style="color:#e6db74">&#39;id&#39;</span> <span style="color:#f92672">=&gt;</span> $id, <span style="color:#e6db74">&#39;status&#39;</span> <span style="color:#f92672">=&gt;</span> $status]) {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// ...
</span></span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Avant ça, extraire des clés nommées d&rsquo;un tableau signifiait soit <code>extract()</code> (qui déverse tout dans le scope et invite les collisions) soit un tas d&rsquo;assignations individuelles. C&rsquo;est juste plus propre.</p>
<h2 id="le-type-iterable">Le type iterable</h2>
<p>Si on écrit une fonction qui accepte soit un tableau soit un générateur, il n&rsquo;y avait pas de type hint propre pour ça en 7.0. On typait soit en <code>array</code> et on excluait silencieusement les générateurs, soit on abandonnait le hint complètement :</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">function</span> <span style="color:#a6e22e">processItems</span>(<span style="color:#a6e22e">iterable</span> $items)<span style="color:#f92672">:</span> <span style="color:#a6e22e">void</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">foreach</span> ($items <span style="color:#66d9ef">as</span> $item) {
</span></span><span style="display:flex;"><span>        $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">handle</span>($item);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><code>iterable</code> accepte tout ce sur quoi on peut faire un <code>foreach</code> : les tableaux et les implémentations de <code>Traversable</code>. Ça fonctionne aussi comme type de retour. Pas dramatique, mais ça comble un vrai manque.</p>
<h2 id="les-offsets-négatifs-sur-les-chaînes">Les offsets négatifs sur les chaînes</h2>
<p>L&rsquo;indexation de chaînes avec <code>[]</code> ou <code>{}</code> accepte maintenant les valeurs négatives, en comptant depuis la fin :</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>$str <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;hello&#39;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> $str[<span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>]; <span style="color:#75715e">// &#34;o&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> $str[<span style="color:#f92672">-</span><span style="color:#ae81ff">2</span>]; <span style="color:#75715e">// &#34;l&#34;
</span></span></span></code></pre></div><p>Plusieurs fonctions de chaînes ont reçu le même traitement : <code>strpos()</code>, <code>substr()</code>, <code>substr_count()</code> et d&rsquo;autres acceptent maintenant un offset négatif. Cohérent avec ce que Python fait depuis toujours. Mieux vaut tard que jamais.</p>
<h2 id="closurefromcallable">Closure::fromCallable()</h2>
<p>Avant ça, convertir un callable (comme <code>[$object, 'method']</code> ou une chaîne nom de fonction) en une vraie <code>Closure</code> nécessitait <code>Closure::bind()</code> ou <code>bindTo()</code> avec une gestion de portée délicate. La 7.1 ajoute une méthode de fabrique statique :</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">Processor</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">private</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">transform</span>(<span style="color:#a6e22e">string</span> $value)<span style="color:#f92672">:</span> <span style="color:#a6e22e">string</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">strtoupper</span>($value);
</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">getTransformer</span>()<span style="color:#f92672">:</span> <span style="color:#a6e22e">Closure</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">Closure</span><span style="color:#f92672">::</span><span style="color:#a6e22e">fromCallable</span>([$this, <span style="color:#e6db74">&#39;transform&#39;</span>]);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>La closure résultante capture le bon <code>$this</code> et la bonne portée. C&rsquo;est particulièrement utile quand on passe des méthodes comme callbacks à des fonctions qui attendent un <code>callable</code>, ou quand on construit des pipelines.</p>
<h2 id="argumentcounterror">ArgumentCountError</h2>
<p>En PHP 7.0, appeler une fonction définie par l&rsquo;utilisateur avec trop peu d&rsquo;arguments générait un warning et l&rsquo;exécution continuait avec des paramètres remplis à <code>null</code>. En 7.1, ça lève une <code>ArgumentCountError</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">function</span> <span style="color:#a6e22e">connect</span>(<span style="color:#a6e22e">string</span> $host, <span style="color:#a6e22e">int</span> $port)<span style="color:#f92672">:</span> <span style="color:#a6e22e">void</span> { <span style="color:#75715e">/* ... */</span> }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">try</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">connect</span>(<span style="color:#e6db74">&#39;localhost&#39;</span>); <span style="color:#75715e">// Lève ArgumentCountError
</span></span></span><span style="display:flex;"><span>} <span style="color:#66d9ef">catch</span> (<span style="color:#a6e22e">\ArgumentCountError</span> $e) {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// ...
</span></span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><code>ArgumentCountError</code> étend <code>TypeError</code>, qui étend <code>Error</code>. Les call sites qui se dégradaient silencieusement auparavant échouent maintenant bruyamment. C&rsquo;est un risque de migration si on a du code qui comptait sur le comportement permissif, mais honnêtement, c&rsquo;est la bonne décision.</p>
<p>La 7.1 est le genre de version qui fait davantage faire confiance à une plateforme. La core team regardait clairement les frictions, pas seulement les titres à faire valoir.</p>
]]></content:encoded></item><item><title>PHP 7.0 : performances, types, et les fonctionnalités qui ont marqué</title><link>https://guillaumedelre.github.io/fr/2016/01/17/php-7.0-performances-types-et-les-fonctionnalit%C3%A9s-qui-ont-marqu%C3%A9/</link><pubDate>Sun, 17 Jan 2016 00:00:00 +0000</pubDate><guid>https://guillaumedelre.github.io/fr/2016/01/17/php-7.0-performances-types-et-les-fonctionnalit%C3%A9s-qui-ont-marqu%C3%A9/</guid><description>Part 1 of 11 in &amp;quot;Sorties PHP&amp;quot;: PHP 7.0 a doublé les performances grâce à une réécriture du Zend Engine et apporté enfin les type hints scalaires au langage.</description><category>php-releases</category><content:encoded><![CDATA[<p>PHP 7.0 est sorti le 3 décembre. Un mois et demi plus tard, j&rsquo;ai migré deux projets et les résultats sont difficiles à ignorer.</p>
<p>Le chiffre phare : 2x plus rapide que PHP 5.6. Ce n&rsquo;est pas un benchmark cherry-pick — c&rsquo;est la médiane sur des applications réelles. Le Zend Engine a été réécrit pour utiliser une nouvelle représentation interne des valeurs, ce qui réduit significativement l&rsquo;utilisation mémoire et diminue les allocations. Sur un projet, le temps de réponse moyen a chuté de 40% sans aucune modification du code. On met à jour, et ça va plus vite.</p>
<p>Mais les performances ne sont pas la partie la plus intéressante.</p>
<h2 id="les-types-enfin">Les types, enfin</h2>
<p>PHP a eu les type hints pour les objets depuis la 5.0, pour les tableaux depuis la 5.1. En 7.0, on peut enfin déclarer des types scalaires pour les paramètres de fonctions et les valeurs de retour :</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">function</span> <span style="color:#a6e22e">add</span>(<span style="color:#a6e22e">int</span> $a, <span style="color:#a6e22e">int</span> $b)<span style="color:#f92672">:</span> <span style="color:#a6e22e">int</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> $a <span style="color:#f92672">+</span> $b;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>En mode strict (<code>declare(strict_types=1)</code>), passer un float à cette fonction lève une <code>TypeError</code>. En mode coercitif par défaut, PHP convertit la valeur. Cette distinction compte : le mode strict est par fichier, on peut donc l&rsquo;adopter progressivement sans tout casser d&rsquo;un coup.</p>
<p>Les déclarations de type de retour constituent l&rsquo;autre moitié. Placer l&rsquo;intention dans la signature plutôt que dans un docblock signifie que c&rsquo;est le moteur qui l&rsquo;applique, pas un code reviewer à moitié endormi.</p>
<h2 id="lopérateur-null-coalescent">L&rsquo;opérateur null coalescent</h2>
<p><code>??</code> est petit mais utilisé en permanence :</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>$username <span style="color:#f92672">=</span> $_GET[<span style="color:#e6db74">&#39;user&#39;</span>] <span style="color:#f92672">??</span> <span style="color:#e6db74">&#39;guest&#39;</span>;
</span></span></code></pre></div><p>Ça remplace <code>isset($_GET['user']) ? $_GET['user'] : 'guest'</code>. Il se chaîne aussi : <code>$a ?? $b ?? $c</code>. Après des années de bruit avec <code>isset()</code>, ça seul valait la mise à jour.</p>
<h2 id="la-partie-qui-casse">La partie qui casse</h2>
<p>La refonte de la gestion des erreurs est le vrai risque lors de la migration. Beaucoup d&rsquo;erreurs fatales sont maintenant des exceptions <code>Error</code>, attrapables mais différentes des <code>Exception</code>. Le code qui comptait sur les erreurs fatales pour stopper l&rsquo;exécution silencieusement a maintenant besoin d&rsquo;une gestion explicite. La suppression d&rsquo;erreurs avec <code>@</code> fonctionne aussi différemment par endroits.</p>
<p>Lire le guide de migration avant de toucher une appli en production. Le gain est réel, mais le fossé entre 5.6 et 7.0 est le plus large que PHP ait jamais eu.</p>
<h2 id="lopérateur-vaisseau-spatial">L&rsquo;opérateur vaisseau spatial</h2>
<p><code>&lt;=&gt;</code> est un opérateur de comparaison combiné qui retourne -1, 0 ou 1. Il est surtout là pour le tri :</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">usort</span>($users, <span style="color:#66d9ef">function</span> ($a, $b) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> $a<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">age</span> <span style="color:#f92672">&lt;=&gt;</span> $b<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">age</span>;
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>Avant ça, un comparateur de tri personnalisé était un petit exercice de mémoire arithmétique. <code>$a - $b</code> fonctionne pour les entiers mais plante silencieusement pour les flottants. <code>&lt;=&gt;</code> fait ce qu&rsquo;il faut pour chaque type comparable.</p>
<h2 id="les-classes-anonymes">Les classes anonymes</h2>
<p>On peut maintenant instancier une classe définie en ligne, sur le moment, sans lui donner de nom :</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>$logger <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">class</span>($config) <span style="color:#66d9ef">implements</span> <span style="color:#a6e22e">LoggerInterface</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:#66d9ef">array</span> $config) {}
</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">log</span>(<span style="color:#a6e22e">string</span> $message)<span style="color:#f92672">:</span> <span style="color:#a6e22e">void</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">file_put_contents</span>($this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">config</span>[<span style="color:#e6db74">&#39;path&#39;</span>], $message <span style="color:#f92672">.</span> <span style="color:#a6e22e">PHP_EOL</span>, <span style="color:#a6e22e">FILE_APPEND</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p>Le cas d&rsquo;usage canonique, ce sont les doublures de test et les implémentations d&rsquo;interface ponctuelles qui ne méritent pas un fichier. Ça supprime une vraie friction : le fossé entre &ldquo;j&rsquo;ai besoin d&rsquo;un objet&rdquo; et &ldquo;je dois créer un fichier de classe pour un truc de 10 lignes&rdquo;.</p>
<h2 id="aléatoire-cryptographiquement-sûr">Aléatoire cryptographiquement sûr</h2>
<p>Les <code>rand()</code> et <code>mt_rand()</code> de PHP 5 n&rsquo;ont jamais été conçus pour la sécurité. La 7.0 ajoute deux fonctions qui le sont :</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>$token <span style="color:#f92672">=</span> <span style="color:#a6e22e">bin2hex</span>(<span style="color:#a6e22e">random_bytes</span>(<span style="color:#ae81ff">32</span>)); <span style="color:#75715e">// token hexadécimal de 64 caractères
</span></span></span><span style="display:flex;"><span>$pin   <span style="color:#f92672">=</span> <span style="color:#a6e22e">random_int</span>(<span style="color:#ae81ff">100000</span>, <span style="color:#ae81ff">999999</span>);
</span></span></code></pre></div><p><code>random_bytes()</code> puise dans le CSPRNG du système d&rsquo;exploitation. <code>random_int()</code> enveloppe ça pour les entiers. Ces fonctions remplacent tous les schémas de génération de tokens maison qui faisaient ça mal en silence, ce qui représente la majorité d&rsquo;entre eux.</p>
<h2 id="les-déclarations-use-groupées">Les déclarations use groupées</h2>
<p>Avant 7.0, importer cinq éléments depuis le même namespace nécessitait cinq instructions <code>use</code>. Maintenant :</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">App\Model\</span>{<span style="color:#a6e22e">User</span>, <span style="color:#a6e22e">Order</span>, <span style="color:#a6e22e">Product</span>};
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">App\Helpers\</span>{<span style="color:#a6e22e">formatDate</span>, <span style="color:#a6e22e">slugify</span>};
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#66d9ef">const</span> <span style="color:#66d9ef">App\Config\</span>{<span style="color:#a6e22e">MAX_RETRIES</span>, <span style="color:#a6e22e">TIMEOUT</span>};
</span></span></code></pre></div><p>Petite amélioration ergonomique, mais qui réduit le bruit visuel en haut des fichiers avec des hiérarchies de namespaces profondes.</p>
<h2 id="les-générateurs-ont-grandi">Les générateurs ont grandi</h2>
<p>Les générateurs en 5.5 étaient intéressants mais incomplets. La 7.0 ajoute deux choses. Premièrement, un générateur peut maintenant avoir une valeur de retour, accessible après la fin de l&rsquo;itération :</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">function</span> <span style="color:#a6e22e">process</span>()<span style="color:#f92672">:</span> <span style="color:#a6e22e">Generator</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">yield</span> <span style="color:#e6db74">&#39;step 1&#39;</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">yield</span> <span style="color:#e6db74">&#39;step 2&#39;</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#39;done&#39;</span>;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>$gen <span style="color:#f92672">=</span> <span style="color:#a6e22e">process</span>();
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">foreach</span> ($gen <span style="color:#66d9ef">as</span> $step) { <span style="color:#75715e">/* ... */</span> }
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> $gen<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">getReturn</span>(); <span style="color:#75715e">// &#34;done&#34;
</span></span></span></code></pre></div><p>Deuxièmement, <code>yield from</code> délègue à un autre générateur ou itérable, en transmettant transparemment les valeurs et valeurs de retour :</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">function</span> <span style="color:#a6e22e">inner</span>()<span style="color:#f92672">:</span> <span style="color:#a6e22e">Generator</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">yield</span> <span style="color:#ae81ff">1</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">yield</span> <span style="color:#ae81ff">2</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#39;inner done&#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">function</span> <span style="color:#a6e22e">outer</span>()<span style="color:#f92672">:</span> <span style="color:#a6e22e">Generator</span> {
</span></span><span style="display:flex;"><span>    $result <span style="color:#f92672">=</span> <span style="color:#66d9ef">yield</span> <span style="color:#a6e22e">from</span> <span style="color:#a6e22e">inner</span>();
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">echo</span> $result; <span style="color:#75715e">// &#34;inner done&#34;
</span></span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">yield</span> <span style="color:#ae81ff">3</span>;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Ça rend la composition de générateurs pratique sans avoir à câbler manuellement les valeurs entre eux.</p>
<h2 id="closurecall">Closure::call()</h2>
<p>Une façon plus directe de lier une closure à un objet et de l&rsquo;appeler immédiatement :</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">Counter</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">private</span> <span style="color:#a6e22e">int</span> $count <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>$increment <span style="color:#f92672">=</span> <span style="color:#66d9ef">function</span> (<span style="color:#a6e22e">int</span> $by)<span style="color:#f92672">:</span> <span style="color:#a6e22e">void</span> {
</span></span><span style="display:flex;"><span>    $this<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">count</span> <span style="color:#f92672">+=</span> $by;
</span></span><span style="display:flex;"><span>};
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>$increment<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">call</span>(<span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Counter</span>(), <span style="color:#ae81ff">5</span>);
</span></span></code></pre></div><p><code>bindTo()</code> existait avant mais nécessitait deux étapes. <code>call()</code> les fusionne et est plus rapide à l&rsquo;exécution car il évite la création d&rsquo;une closure intermédiaire.</p>
<h2 id="syntaxe-déchappement-unicode-dans-les-chaînes">Syntaxe d&rsquo;échappement Unicode dans les chaînes</h2>
<p>On peut maintenant intégrer des caractères Unicode directement dans les chaînes entre guillemets doubles ou les heredocs via un point de 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">echo</span> <span style="color:#e6db74">&#34;\u{1F418}&#34;</span>; <span style="color:#75715e">// 🐘
</span></span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> <span style="color:#e6db74">&#34;\u{00E9}&#34;</span>;  <span style="color:#75715e">// é
</span></span></span></code></pre></div><p>C&rsquo;est mieux que de copier-coller des caractères depuis une table Unicode dans les fichiers sources, ce que les gens faisaient vraiment.</p>
<h2 id="un-unserialize-plus-sûr">Un unserialize() plus sûr</h2>
<p><code>unserialize()</code> a une longue histoire d&rsquo;être un vecteur d&rsquo;attaques par injection d&rsquo;objets. La 7.0 ajoute une option <code>allowed_classes</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>$data <span style="color:#f92672">=</span> <span style="color:#a6e22e">unserialize</span>($input, [<span style="color:#e6db74">&#39;allowed_classes&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#66d9ef">false</span>]);
</span></span><span style="display:flex;"><span>$data <span style="color:#f92672">=</span> <span style="color:#a6e22e">unserialize</span>($input, [<span style="color:#e6db74">&#39;allowed_classes&#39;</span> <span style="color:#f92672">=&gt;</span> [<span style="color:#a6e22e">User</span><span style="color:#f92672">::</span><span style="color:#a6e22e">class</span>, <span style="color:#a6e22e">Order</span><span style="color:#f92672">::</span><span style="color:#a6e22e">class</span>]]);
</span></span></code></pre></div><p>Passer <code>false</code> empêche toute instanciation d&rsquo;objet pendant la désérialisation. C&rsquo;est le comportement par défaut à adopter quand on désérialise des données non fiables.</p>
<h2 id="1234-division-entière">:1234: Division entière</h2>
<p><code>intdiv()</code> est une division entière explicite sans intermédiaire flottant :</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>$pages <span style="color:#f92672">=</span> <span style="color:#a6e22e">intdiv</span>(<span style="color:#a6e22e">count</span>($items), $perPage); <span style="color:#75715e">// int, pas besoin de cast
</span></span></span></code></pre></div><p>Oui, on pourrait caster le résultat d&rsquo;une division. <code>intdiv()</code> rend l&rsquo;intention claire et évite les cas limites de précision flottante que le cast introduit pour les grands nombres.</p>
<h2 id="les-constantes-en-tableaux">Les constantes en tableaux</h2>
<p>Avant 7.0, <code>define()</code> n&rsquo;acceptait que les valeurs scalaires. Les tableaux fonctionnaient avec <code>const</code> au niveau de la classe ou du namespace mais pas avec <code>define()</code>. Maintenant si :</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">define</span>(<span style="color:#e6db74">&#39;HTTP_METHODS&#39;</span>, [<span style="color:#e6db74">&#39;GET&#39;</span>, <span style="color:#e6db74">&#39;POST&#39;</span>, <span style="color:#e6db74">&#39;PUT&#39;</span>, <span style="color:#e6db74">&#39;DELETE&#39;</span>, <span style="color:#e6db74">&#39;PATCH&#39;</span>]);
</span></span></code></pre></div><p>Utile pour la configuration qui doit être une constante mais qui vit en dehors d&rsquo;une classe.</p>
<h2 id="des-assertions-avec-des-dents">Des assertions avec des dents</h2>
<p><code>assert()</code> a reçu une vraie refonte. En PHP 5, les assertions étaient un eval de chaînes à l&rsquo;exécution. Maintenant elles peuvent lever des exceptions et être complètement supprimées en production avec zéro overhead :</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">// Dans php.ini ou au bootstrap :
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">// assert.active = 1 (dev), 0 (prod)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">// assert.exception = 1
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">assert</span>($user<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">isVerified</span>(), <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">\LogicException</span>(<span style="color:#e6db74">&#39;Unverified user reached checkout&#39;</span>));
</span></span></code></pre></div><p>Quand <code>assert.active = 0</code>, l&rsquo;expression n&rsquo;est jamais évaluée. Quand c&rsquo;est activé, une assertion qui échoue lève directement l&rsquo;exception fournie. C&rsquo;est enfin un outil qu&rsquo;on peut utiliser sans honte.</p>
<h2 id="la-refonte-de-session_start">La refonte de session_start()</h2>
<p><code>session_start()</code> accepte maintenant un tableau d&rsquo;options qui surchargent les directives <code>php.ini</code> pour cet appel :</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">session_start</span>([
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#39;cookie_lifetime&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#ae81ff">86400</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#39;cookie_secure&#39;</span>   <span style="color:#f92672">=&gt;</span> <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#39;cookie_httponly&#39;</span>  <span style="color:#f92672">=&gt;</span> <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#39;cookie_samesite&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#39;Lax&#39;</span>,
</span></span><span style="display:flex;"><span>]);
</span></span></code></pre></div><p>Avant ça, on définissait soit les options globalement dans <code>php.ini</code>, soit on appelait <code>ini_set()</code> avant <code>session_start()</code>. Aucune des deux n&rsquo;était top quand on avait besoin de configurations de session différentes dans différentes parties d&rsquo;une appli.</p>
]]></content:encoded></item></channel></rss>