<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="https://ktherage.github.io/fr/xsl/atom.xsl" media="all"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="fr">
  <id>https://ktherage.github.io/fr/tags/symfony/</id>
  <title>Kévin THÉRAGE | Expert Symfony Developer - Symfony</title>
  <subtitle><![CDATA[Kévin THÉRAGE – Expert Symfony Developer. Technical blog on Symfony, PHP, web development with tutorials, best practices and expert advice for developers.]]></subtitle>
  <link href="https://ktherage.github.io/fr/tags/symfony/atom.xml" rel="self" type="application/atom+xml" />
  <link href="https://ktherage.github.io/fr/tags/symfony/" rel="alternate" type="text/html" />
  <updated>2026-06-08T09:33:13+00:00</updated>
  <author>
    <name>Kévin THÉRAGE</name>
    <uri>https://ktherage.github.io/</uri>
  </author>
  <entry xml:lang="fr">
    <id>https://ktherage.github.io/fr/blog/my-eventsubscriber-silenced-errors/</id>
    <title>Mon EventSubscriber masquait les erreurs, voici pourquoi</title>
    <published>2026-04-13T00:00:00+00:00</published>
    <link href="https://ktherage.github.io/fr/blog/my-eventsubscriber-silenced-errors/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<p>Un ticket Jira est apparu : <em>« Il y a un bug étrange qui empêche les utilisateurs d'accéder à une page à ce moment-là de la journée. »</em>
Les logs indiquaient plusieurs fois : <em>« [ce jour T cette heure] request.ERROR: Uncaught PHP Exception Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException: "Access denied to that resource." at WhitelistSubscriber.php line 99 »</em></p>
<p>Je n'avais aucune idée au début… 😅 Voici comment j'ai compris.</p>
<hr>
<h2 id="la-configuration">La configuration</h2>
<p>J'avais un EventSubscriber qui vérifiait l'accès aux pages basé sur une liste blanche de routes. C'était du code legacy — le refactoriser n'était pas à l'ordre du jour à ce moment-là.</p>
<pre><code class="language-php hljs php" translate="no"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">EventSubscriber</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">EventDispatcher</span>\<span class="hljs-title">EventSubscriberInterface</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">HttpKernel</span>\<span class="hljs-title">Event</span>\<span class="hljs-title">RequestEvent</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">HttpKernel</span>\<span class="hljs-title">KernelEvents</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">WhitelistRouteSubscriber</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">EventSubscriberInterface</span>
</span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> WHITELISTED_ROUTES = [
        <span class="hljs-string">'app_login'</span>,
        <span class="hljs-string">'app_homepage'</span>,
        <span class="hljs-string">'app_healthcheck'</span>,
    ];

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getSubscribedEvents</span><span class="hljs-params">()</span>: <span class="hljs-title">array</span>
    </span>{
        <span class="hljs-keyword">return</span> [
            KernelEvents::REQUEST =&gt; [<span class="hljs-string">'onKernelRequest'</span>, <span class="hljs-number">0</span>],
        ];
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">onKernelRequest</span><span class="hljs-params">(RequestEvent $event)</span>: <span class="hljs-title">void</span>
    </span>{
        $request = $event-&gt;getRequest();
        $route = $request-&gt;attributes-&gt;get(<span class="hljs-string">'_route'</span>);

        <span class="hljs-comment">// Allow whitelisted routes</span>
        <span class="hljs-keyword">if</span> (in_array($route, <span class="hljs-keyword">self</span>::WHITELISTED_ROUTES, <span class="hljs-keyword">true</span>)) {
            <span class="hljs-keyword">return</span>;
        }

        <span class="hljs-comment">// Deny access for non-whitelisted routes</span>
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> AccessDeniedHttpException(<span class="hljs-string">'Route not whitelisted'</span>);
    }
}</code></pre>
<p>Objectif : Bloquer toutes les routes sauf la liste blanche. Simple, non ?</p>
<hr>
<h2 id="le-probleme">Le problème</h2>
<p>Les logs montraient des <code translate="no">AccessDeniedHttpException</code> sur des routes que je savais être dans la liste blanche. Premier geste classique : mettre un <code translate="no">dump()</code> dans le subscriber pour voir ce qui arrivait.</p>
<pre><code class="language-php hljs php" translate="no"><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">onKernelRequest</span><span class="hljs-params">(RequestEvent $event)</span>: <span class="hljs-title">void</span>
</span>{
    $request = $event-&gt;getRequest();
    $route = $request-&gt;attributes-&gt;get(<span class="hljs-string">'_route'</span>);

    dump($route); <span class="hljs-comment">// 🔍 Voyons ce qui se passe</span>
    <span class="hljs-comment">// ...</span>
}</code></pre>
<p>Première découverte surprenante : <strong>le subscriber était appelé deux fois</strong> pour une seule requête. Le premier appel avait la route attendue, le second avait <code translate="no">$route = null</code>.</p>
<p>Question évidente : <em>pourquoi <code translate="no">_route</code> est-il null ?</em></p>
<p>J'ai creusé plus loin avec <code translate="no">dump($request-&gt;getPathInfo())</code> pour voir quelle URL était traitée lors du second appel :</p>
<pre><code translate="no">// 1er appel
dump($request-&gt;getPathInfo()); // "/foo"

// 2e appel
dump($request-&gt;getPathInfo()); // "/foo" ← identique. Attends, quoi ?</code></pre>
<p>Même URL, appelée deux fois. Cela n'avait aucun sens — si c'était la même requête, pourquoi <code translate="no">_route</code> était-il null la deuxième fois ? Je tournais en rond.</p>
<p>J'ai donc dumpé l'objet <code translate="no">$event</code> complet pour avoir plus de contexte, et j'ai réduit le champ à <code translate="no">_controller</code> dans les attributs de la requête :</p>
<pre><code class="language-php hljs php" translate="no">dump($request-&gt;attributes-&gt;get(<span class="hljs-string">'_controller'</span>));
<span class="hljs-comment">// "Symfony\Component\HttpKernel\Controller\ErrorController"</span></code></pre>
<p>Voilà. <code translate="no">_controller</code> ne pointait pas vers mon code du tout. Symfony avait forgé une toute nouvelle requête vers son propre <code translate="no">ErrorController</code>, réutilisant l'URL d'origine — ce qui explique pourquoi <code translate="no">getPathInfo()</code> était si trompeur — mais en contournant complètement le routeur. C'est pourquoi <code translate="no">_route</code> était null.</p>
<hr>
<h2 id="cause-racine">Cause racine</h2>
<p>Le flux réel était :</p>
<pre><code translate="no">Requête → /foo
  └── WhitelistSubscriber (1er appel) → _route = 'app_foo' ✅ Accès accordé
      └── Controller → lève RealException 💥
          └── Symfony l'attrape
              └── Sous-requête → ErrorController (contourne le routeur, pas de _route)
                  └── WhitelistSubscriber (2e appel) → _route = null ❌ AccessDenied levé
                      └── RealException est maintenant silencieuse 🔇</code></pre>
<p>Le piège : <strong>l'<code translate="no">AccessDeniedHttpException</code> du subscriber masquait complètement l'exception originale</strong> — celle qui contenait réellement les informations de débogage utiles.</p>
<p>Lorsqu'une exception est levée, le <code translate="no">HttpKernel</code> de Symfony distribue un événement <code translate="no">KernelEvents::EXCEPTION</code>, puis délègue le rendu de l'erreur à <code translate="no">ErrorController</code> via une sous-requête interne. Cette sous-requête réutilise l'URL d'origine — ce qui explique pourquoi <code translate="no">getPathInfo()</code> était trompeur — mais elle contourne complètement la couche de routage, laissant <code translate="no">_route</code> à <code translate="no">null</code>.</p>
<hr>
<h2 id="la-solution">La solution</h2>
<p>Vérifiez si la requête est la requête principale (pas une sous-requête) :</p>
<pre><code class="language-php hljs php" translate="no"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">EventSubscriber</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">EventDispatcher</span>\<span class="hljs-title">EventSubscriberInterface</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">HttpKernel</span>\<span class="hljs-title">Event</span>\<span class="hljs-title">RequestEvent</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">HttpKernel</span>\<span class="hljs-title">KernelEvents</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">WhitelistRouteSubscriber</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">EventSubscriberInterface</span>
</span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> WHITELISTED_ROUTES = [
        <span class="hljs-string">'app_login'</span>,
        <span class="hljs-string">'app_homepage'</span>,
        <span class="hljs-string">'app_healthcheck'</span>,
    ];

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getSubscribedEvents</span><span class="hljs-params">()</span>: <span class="hljs-title">array</span>
    </span>{
        <span class="hljs-keyword">return</span> [
            KernelEvents::REQUEST =&gt; [<span class="hljs-string">'onKernelRequest'</span>, <span class="hljs-number">0</span>],
        ];
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">onKernelRequest</span><span class="hljs-params">(RequestEvent $event)</span>: <span class="hljs-title">void</span>
    </span>{
        <span class="hljs-comment">// Skip sub-requests (like error handling)</span>
        <span class="hljs-keyword">if</span> (!$event-&gt;isMainRequest()) {
            <span class="hljs-keyword">return</span>;
        }

        $request = $event-&gt;getRequest();
        $route = $request-&gt;attributes-&gt;get(<span class="hljs-string">'_route'</span>);

        <span class="hljs-comment">// Allow whitelisted routes</span>
        <span class="hljs-keyword">if</span> (in_array($route, <span class="hljs-keyword">self</span>::WHITELISTED_ROUTES, <span class="hljs-keyword">true</span>)) {
            <span class="hljs-keyword">return</span>;
        }

        <span class="hljs-comment">// Deny access for non-whitelisted routes</span>
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> AccessDeniedHttpException(<span class="hljs-string">'Route not whitelisted'</span>);
    }
}</code></pre>
<p><code translate="no">isMainRequest()</code> retourne <code translate="no">false</code> pour toute sous-requête interne — gestion d'erreurs, fragments ESI, <code translate="no">hinclude</code> — donc votre logique ne s'exécute que sur les vraies requêtes distribuées par le routeur.</p>
<blockquote>
<p><strong>Note :</strong> <code translate="no">isMainRequest()</code> a remplacé l'ancien <code translate="no">isMasterRequest()</code> dans Symfony 5.3. Si vous êtes sur une version plus ancienne, utilisez <code translate="no">isMasterRequest()</code> à la place.</p>
</blockquote>
<hr>
<h2 id="conclusion">Conclusion</h2>
<p>Chaque fois que votre subscriber fait quelque chose de destructeur — lever une exception, rediriger, définir une réponse — demandez-vous : <em>que se passe-t-il quand Symfony appelle ceci sur une sous-requête ?</em></p>
<p>Les sous-requêtes sont omniprésentes dans Symfony : gestion d'erreurs, ESI, fragments. Elles n'ont pas le même contexte qu'une requête principale, et votre subscriber ne connaît pas la différence à moins que vous ne lui disiez.</p>
<p><code translate="no">isMainRequest()</code> est cette vérification. Faites-en un réflexe. 🎉</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://ktherage.github.io/fr/blog/api-platform-con-2025-day-1/</id>
    <title>API Platform con 2025 - JOUR 1</title>
    <published>2025-09-23T00:00:00+00:00</published>
    <link href="https://ktherage.github.io/fr/blog/api-platform-con-2025-day-1/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<p>J'ai eu l'opportunité d'assister à l'API Platform Con 2025 grâce à SensioLabs et voici ce que j'ai appris lors des conférences auxquelles j'ai assisté.</p>
<p>Table des matières :</p>
<div id="toc"><ul>
<li class="toc-level-2"><a href="#ameliorez-vos-api-platform-avec-go-grace-a-frankenphp-kevin-dunglas">Améliorez vos API Platform avec Go grâce à FrankenPHP (Kévin Dunglas)</a>
<ul>
<li class="toc-level-3"><a href="#un-modele-plusieurs-architectures-d-api">Un modèle, plusieurs architectures d&#039;API</a></li>
<li class="toc-level-3"><a href="#pourquoi-grpc-est-absent-d-api-platform">Pourquoi gRPC est absent d&#039;API Platform</a></li>
<li class="toc-level-3"><a href="#comment-fonctionne-grpc">Comment fonctionne gRPC</a></li>
<li class="toc-level-3"><a href="#grpc-avec-frankenphp">gRPC avec FrankenPHP</a></li>
</ul>
</li>
<li class="toc-level-2"><a href="#etendre-le-serveur-web-caddy-avec-votre-langage-prefere-sylvain-combraque">Étendre le serveur Web Caddy avec votre langage préféré (Sylvain Combraque)</a>
<ul>
<li class="toc-level-3"><a href="#etendre-caddy">Étendre Caddy</a></li>
<li class="toc-level-3"><a href="#etendre-caddy-avec-go">Étendre Caddy avec Go</a></li>
<li class="toc-level-3"><a href="#utilisation-d-un-interpreteur">Utilisation d&#039;un interpréteur</a></li>
<li class="toc-level-3"><a href="#wasm-x-wasi-x-darkweak-wazemmes-pour-une-extension-caddy-dans-n-importe-quel-langage">WASM x WASI x darkweak/wazemmes pour une extension Caddy dans n&#039;importe quel langage</a></li>
</ul>
</li>
<li class="toc-level-2"><a href="#mercure-sse-api-platform-et-un-llm-pour-ameliorer-un-chat-bot-mathieu-santostefano">Mercure, SSE, API Platform et un LLM pour améliorer un Chat(bot) (Mathieu Santostefano)</a>
<ul>
<li class="toc-level-3"><a href="#origine-du-sujet">Origine du sujet</a></li>
<li class="toc-level-3"><a href="#boite-a-outils">Boîte à outils</a></li>
<li class="toc-level-3"><a href="#implementation">Implémentation</a></li>
</ul>
</li>
<li class="toc-level-2"><a href="#comment-api-platform-4-2-redefinit-le-developpement-d-api-antoine-bluchet">Comment API Platform 4.2 redéfinit le développement d&#039;API (Antoine Bluchet)</a>
<ul>
<li class="toc-level-3"><a href="#quoi-de-neuf-dans-la-4-2">Quoi de neuf dans la 4.2</a></li>
<li class="toc-level-3"><a href="#ameliorations-des-metadonnees">Améliorations des métadonnées</a></li>
<li class="toc-level-3"><a href="#du-filtre-api-aux-parametres">Du filtre API aux paramètres</a></li>
<li class="toc-level-3"><a href="#ameliorations-du-json-schema">Améliorations du JSON Schema</a></li>
<li class="toc-level-3"><a href="#performances">Performances</a></li>
<li class="toc-level-3"><a href="#state-options">State Options</a></li>
<li class="toc-level-3"><a href="#data-mapping">Data Mapping</a></li>
<li class="toc-level-3"><a href="#debogage">Débogage</a></li>
<li class="toc-level-3"><a href="#retrocompatibilite">Rétrocompatibilité</a></li>
<li class="toc-level-3"><a href="#perspectives-pour-api-platform-5-0">Perspectives pour API Platform 5.0</a></li>
</ul>
</li>
<li class="toc-level-2"><a href="#design-pattern-le-tresor-est-dans-le-vendor-smaine-milianni">Design pattern, le trésor est dans le vendor (Smaïne Milianni)</a></li>
<li class="toc-level-2"><a href="#et-si-on-faisait-de-l-event-storming-dans-nos-projets-api-platform-gregory-planchat">Et si on faisait de l&#039;Event Storming dans nos projets API Platform ? (Gregory Planchat)</a>
<ul>
<li class="toc-level-3"><a href="#event-storming">Event Storming</a></li>
<li class="toc-level-3"><a href="#preparation">Préparation</a></li>
<li class="toc-level-3"><a href="#avantages">Avantages</a></li>
<li class="toc-level-3"><a href="#avec-api-platform">Avec API Platform</a></li>
<li class="toc-level-3"><a href="#resultats">Résultats</a></li>
</ul>
</li>
<li class="toc-level-2"><a href="#passage-a-l-echelle-des-bases-de-donnees-tobias-petry">Passage à l&#039;échelle des bases de données (Tobias Petry)</a>
<ul>
<li class="toc-level-3"><a href="#solutions">Solutions</a></li>
<li class="toc-level-3"><a href="#ca-semble-complique">Ça semble compliqué</a></li>
</ul>
</li>
<li class="toc-level-2"><a href="#api-platform-jsonstreamer-et-esa-pour-des-api-fulgurantes-mathias-arlaud">API Platform, JsonStreamer et ESA pour des API fulgurantes (Mathias Arlaud)</a>
<ul>
<li class="toc-level-3"><a href="#serialisation-normalisation-dans-symfony">Sérialisation / Normalisation dans Symfony</a></li>
<li class="toc-level-3"><a href="#le-streaming-comme-solution">Le streaming comme solution</a></li>
<li class="toc-level-3"><a href="#benchmarks-et-comparaisons">Benchmarks et comparaisons</a></li>
<li class="toc-level-3"><a href="#defis-avec-les-metadonnees-json-ld-et-comment-api-platform-s-adapte">Défis avec les métadonnées, JSON-LD et comment API Platform s&#039;adapte</a></li>
<li class="toc-level-3"><a href="#le-pattern-esa-edge-side-apis">Le pattern ESA (Edge Side APIs)</a></li>
<li class="toc-level-3"><a href="#points-a-retenir">Points à retenir</a></li>
</ul>
</li>
<li class="toc-level-2"><a href="#credits">Crédits</a></li>
</ul>
</div>
<hr>
<h2 id="ameliorez-vos-api-platform-avec-go-grace-a-frankenphp-kevin-dunglas">Améliorez vos API Platform avec Go grâce à FrankenPHP (Kévin Dunglas)</h2>
<p><strong>Les slides de cette conférence sont disponibles : <a href="https://dunglas.dev/2025/09/the-best-of-both-worlds-go-powered-grpc-for-your-php-and-api-platform-apps/" rel="noopener noreferrer">https://dunglas.dev/2025/09/the-best-of-both-worlds-go-powered-grpc-for-your-php-and-api-platform-apps/</a></strong></p>
<p>API Platform fête ses 10 ans cette année, ayant été créé le 20 janvier 2015. À l'origine un bundle Symfony, il est désormais utilisable avec Laravel ou même sans framework. Avec plus de 14 000 étoiles sur GitHub et 921 contributeurs de code et de documentation, API Platform est devenu un outil essentiel pour la création d'API.</p>
<p>Kevin a souligné qu'il a également été le point de départ de nombreux projets connexes tels que Mercure et FrankenPHP, et que de nombreux composants Symfony ont d'abord été développés pour API Platform.</p>
<p>Il a également rendu hommage à Ryan Weaver, un contributeur clé, et a encouragé les participants à soutenir sa famille via le <a href="https://gofund.me/31ec53011" rel="noopener noreferrer">GoFundMe « In memory of Ryan Weaver: For his son Beckett »</a>.</p>
<h3 id="un-modele-plusieurs-architectures-d-api">Un modèle, plusieurs architectures d'API</h3>
<p>Avec API Platform, vous pouvez utiliser le même DTO, le même code et la même classe PHP pour générer différents formats de sortie avec seulement quelques changements de configuration.</p>
<p>Voici quelques-uns des formats pris en charge :</p>
<ul>
<li>Hydra</li>
<li>OpenAPI</li>
<li>HAL</li>
<li>JSON:API</li>
<li>GraphQL</li>
<li>Mercure (support SSE)</li>
</ul>
<p>Cette approche élimine la duplication de code entre les différents formats d'API.</p>
<h3 id="pourquoi-grpc-est-absent-d-api-platform">Pourquoi gRPC est absent d'API Platform</h3>
<p>Actuellement, gRPC n'est pas pris en charge par API Platform. Voici pourquoi :</p>
<ul>
<li>gRPC ne suit pas les principes REST.</li>
<li>Il est différent de GraphQL.</li>
<li>Il utilise Protobuf (un format binaire) au lieu de JSON pour le format de sortie.</li>
</ul>
<p>La plupart du temps, dans l'architecture gRPC classique, PHP n'est pas un candidat.</p>
<figure>
<picture title="Schéma d&#039;une architecture gRPC typique">
<source type="image/webp" srcset="https://ktherage.github.io/thumbnails/480x/img/IMG_20250918_100400.abb2d9d9a62cfa0ca9981e2861fe6671.webp 480w, https://ktherage.github.io/thumbnails/640x/img/IMG_20250918_100400.abb2d9d9a62cfa0ca9981e2861fe6671.webp 640w, https://ktherage.github.io/thumbnails/768x/img/IMG_20250918_100400.abb2d9d9a62cfa0ca9981e2861fe6671.webp 768w, https://ktherage.github.io/thumbnails/800x/img/IMG_20250918_100400.abb2d9d9a62cfa0ca9981e2861fe6671.webp 800w" width="800" height="560" sizes="100vw">
<img src="https://ktherage.github.io/img/IMG_20250918_100400.abb2d9d9a62cfa0ca9981e2861fe6671.jpg" alt="Schéma d&#039;une architecture gRPC typique qui n&#039;inclut pas de serveur gRPC côté PHP mais un serveur C++, un client android/java et un client ruby" loading="lazy" decoding="async" class="img-fluid rounded mx-auto d-block" width="800" height="560" style=";max-width:100%;height:auto;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gNzUK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAMgBkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A8/pRwaSioKLkUgAqdZBWaGI704SMO9UmI1BKPWl80Vl+aacJT600yWjSEgqeNs1mI5JrYsYDJiqREpWFCkio5FIFdDDphZelVr3TzGp4qrEKaZzMvWowuasXEZVyKSNM0mi3KyI9lFWxHxRUWZHtDCpVUscUVYtk3OKmx0PRDo7Nn7VKdNfHSuj02xV1GRWq+moE6VpGBwzxDTscBLalO1V8YNdNqdsqE8Vz0q4ajlN6dTmWo6EZcV12iopK5rkIzhga6PS7kpirjEVRXO/t4kCDpWdq4QIcYqtFqLBOtUNQvS6nJrRQuYxjY5+8A801DHRPJucmo1fFU4Gtm0WwRiiq/mUVPKRyMycVatTtcVGUoUlTXMdrVzsNMu0RRk1qyahHs61w0V0yDg1Kb9yMZrSJzSopsv6pcK5OK55+WNWJJ2k6mosZrRK5cYcosMeWFdDp1oWxgVj2iZcV22i2wYLxWjVjKrdLQfFpzFOlUNRsmRTxXbRW4VOlYutRAIcCpUzGm3fU87nXbIRUVW75cTGqlJzudiQUUuKKXMPlITTKKK50bCilNFFaRJYlKKKK2iSXbL74rvND6LRRVS2MKp1C/crC1r7jUUVkc8PiPP7/AP1p+tUhRRUndEcKKKKRZ//Z);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="https://ktherage.github.io/thumbnails/480x/img/IMG_20250918_100400.abb2d9d9a62cfa0ca9981e2861fe6671.jpg 480w, https://ktherage.github.io/thumbnails/640x/img/IMG_20250918_100400.abb2d9d9a62cfa0ca9981e2861fe6671.jpg 640w, https://ktherage.github.io/thumbnails/768x/img/IMG_20250918_100400.abb2d9d9a62cfa0ca9981e2861fe6671.jpg 768w, https://ktherage.github.io/thumbnails/800x/img/IMG_20250918_100400.abb2d9d9a62cfa0ca9981e2861fe6671.jpg 800w" sizes="100vw">
</picture>
<figcaption>Schéma d'une architecture gRPC typique</figcaption>
</figure>
<p>Cependant, gRPC présente plusieurs avantages :</p>
<ul>
<li>Rapide et efficace</li>
<li>Fortement typé</li>
<li>Indépendant du langage : un générateur de code permet de générer des structures de données dans de nombreux langages.</li>
</ul>
<p>Les cas d'utilisation de gRPC incluent :</p>
<ul>
<li>Microservices</li>
<li>Internet des objets (IoT)</li>
<li>Composants critiques où la performance est essentielle</li>
</ul>
<h3 id="comment-fonctionne-grpc">Comment fonctionne gRPC</h3>
<p>gRPC fonctionne sur HTTP/2 et utilise des fichiers <code translate="no">.proto</code> de Protocol Buffer pour définir les contrats de service. Ces définitions permettent la génération automatique de code dans plusieurs langages de programmation. Le format de sérialisation binaire offre une transmission de données plus efficace que JSON, tandis que le multiplexage d'HTTP/2 prend en charge les communications haute performance.</p>
<p>De plus, la documentation officielle de gRPC déconseille l'utilisation de PHP pour les serveurs gRPC en raison des limitations du cycle de vie des requêtes PHP-FPM.</p>
<h3 id="grpc-avec-frankenphp">gRPC avec FrankenPHP</h3>
<p>Heureusement, FrankenPHP offre un moyen d'écrire des extensions en Go qui peuvent être exposées en PHP, rendant ainsi possible l'utilisation de gRPC avec API Platform. L'extension gRPC de FrankenPHP est disponible sur GitHub et est testable. Elle utilise le serveur gRPC Go et est conçue pour être utilisée avec ou sans API Platform.</p>
<p>Pour plus d'informations sur la configuration et l'utilisation de cette extension, veuillez vous référer à la <a href="https://github.com/dunglas/frankenphp-grpc" rel="noopener noreferrer">documentation du dépôt GitHub</a>.</p>
<p>Les tests peuvent être effectués avec gRPCui. Il est important de noter que cette solution est encore expérimentale.</p>
<hr>
<h2 id="etendre-le-serveur-web-caddy-avec-votre-langage-prefere-sylvain-combraque">Étendre le serveur Web Caddy avec votre langage préféré (Sylvain Combraque)</h2>
<p>Caddy est un serveur Web moderne, rapide et facile à utiliser qui simplifie le processus de服务 des sites Web et des applications Web. Il est connu pour sa configuration HTTPS automatique et sa syntaxe simple. Matt Holt, le créateur de Caddy, a apporté des contributions significatives au paysage des serveurs Web avec les fonctionnalités uniques et la facilité d'utilisation de Caddy.</p>
<h3 id="etendre-caddy">Étendre Caddy</h3>
<h4>Avec la construction xcaddy</h4>
<p>L'extension de Caddy peut être réalisée à l'aide de l'outil de construction <code translate="no">xcaddy</code>, qui permet de personnaliser et d'étendre Caddy avec des plugins écrits en Go. Cet outil offre un moyen simple d'ajouter de nouvelles fonctionnalités à Caddy.</p>
<h4>Avec l'interface Web du site Caddy</h4>
<p>Caddy propose également une interface Web accessible depuis le site Web de Caddy. Cette interface offre un moyen simple de gérer et de configurer votre serveur Web Caddy.</p>
<h3 id="etendre-caddy-avec-go">Étendre Caddy avec Go</h3>
<figure>
<picture title="Un exemple d&#039;extension Caddy réalisée avec GO">
<source type="image/webp" srcset="https://ktherage.github.io/thumbnails/480x/img/IMG_20250918_100400.abb2d9d9a62cfa0ca9981e2861fe6671.webp 480w, https://ktherage.github.io/thumbnails/640x/img/IMG_20250918_100400.abb2d9d9a62cfa0ca9981e2861fe6671.webp 640w, https://ktherage.github.io/thumbnails/768x/img/IMG_20250918_100400.abb2d9d9a62cfa0ca9981e2861fe6671.webp 768w, https://ktherage.github.io/thumbnails/800x/img/IMG_20250918_100400.abb2d9d9a62cfa0ca9981e2861fe6671.webp 800w" width="800" height="560" sizes="100vw">
<img src="https://ktherage.github.io/img/IMG_20250918_100400.abb2d9d9a62cfa0ca9981e2861fe6671.jpg" alt="Un exemple d&#039;extension Caddy réalisée avec GO" loading="lazy" decoding="async" class="img-fluid rounded mx-auto d-block" width="800" height="560" style=";max-width:100%;height:auto;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gNzUK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAMgBkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A8/pRwaSioKLkUgAqdZBWaGI704SMO9UmI1BKPWl80Vl+aacJT600yWjSEgqeNs1mI5JrYsYDJiqREpWFCkio5FIFdDDphZelVr3TzGp4qrEKaZzMvWowuasXEZVyKSNM0mi3KyI9lFWxHxRUWZHtDCpVUscUVYtk3OKmx0PRDo7Nn7VKdNfHSuj02xV1GRWq+moE6VpGBwzxDTscBLalO1V8YNdNqdsqE8Vz0q4ajlN6dTmWo6EZcV12iopK5rkIzhga6PS7kpirjEVRXO/t4kCDpWdq4QIcYqtFqLBOtUNQvS6nJrRQuYxjY5+8A801DHRPJucmo1fFU4Gtm0WwRiiq/mUVPKRyMycVatTtcVGUoUlTXMdrVzsNMu0RRk1qyahHs61w0V0yDg1Kb9yMZrSJzSopsv6pcK5OK55+WNWJJ2k6mosZrRK5cYcosMeWFdDp1oWxgVj2iZcV22i2wYLxWjVjKrdLQfFpzFOlUNRsmRTxXbRW4VOlYutRAIcCpUzGm3fU87nXbIRUVW75cTGqlJzudiQUUuKKXMPlITTKKK50bCilNFFaRJYlKKKK2iSXbL74rvND6LRRVS2MKp1C/crC1r7jUUVkc8PiPP7/AP1p+tUhRRUndEcKKKKRZ//Z);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="https://ktherage.github.io/thumbnails/480x/img/IMG_20250918_100400.abb2d9d9a62cfa0ca9981e2861fe6671.jpg 480w, https://ktherage.github.io/thumbnails/640x/img/IMG_20250918_100400.abb2d9d9a62cfa0ca9981e2861fe6671.jpg 640w, https://ktherage.github.io/thumbnails/768x/img/IMG_20250918_100400.abb2d9d9a62cfa0ca9981e2861fe6671.jpg 768w, https://ktherage.github.io/thumbnails/800x/img/IMG_20250918_100400.abb2d9d9a62cfa0ca9981e2861fe6671.jpg 800w" sizes="100vw">
</picture>
<figcaption>Un exemple d'extension Caddy réalisée avec GO</figcaption>
</figure>
<p>Pour des informations plus détaillées sur la façon d'étendre Caddy avec Go, vous pouvez consulter la <a href="https://caddyserver.com/docs/extending-caddy" rel="noopener noreferrer">documentation officielle</a>.</p>
<h3 id="utilisation-d-un-interpreteur">Utilisation d'un interpréteur</h3>
<p>L'utilisation d'interpréteurs pour étendre Caddy présente des avantages, mais aussi quelques inconvénients :</p>
<ul>
<li>Vous avez besoin d'un interpréteur par langage.</li>
<li>Les nouvelles versions du langage nécessitent de nouveaux interpréteurs.</li>
<li>Chaque interpréteur est maintenu séparément.</li>
<li>Vous devrez peut-être réimplémenter des types.</li>
</ul>
<h3 id="wasm-x-wasi-x-darkweak-wazemmes-pour-une-extension-caddy-dans-n-importe-quel-langage">WASM x WASI x darkweak/wazemmes pour une extension Caddy dans n'importe quel langage</h3>
<p>WebAssembly (WASM) est un format d'instruction binaire qui promet de permettre aux programmes de s'exécuter à une vitesse proche du natif sur le Web. La promesse de « construire une fois, exécuter partout » fait de WASM une option attrayante pour étendre Caddy. Cependant, la documentation actuelle n'est pas conviviale et il existe quelques bogues à connaître.</p>
<p>WebAssembly System Interface (WASI) est une interface système conçue pour permettre aux modules WebAssembly d'interagir avec le système d'exploitation de manière sécurisée et portable. Cette combinaison de WASM et WASI permet aux développeurs d'écrire du code dans leur langage préféré et de le compiler en WASM pour une exécution dans un navigateur ou un environnement serveur.</p>
<p>Pour plus d'informations sur l'utilisation de WASM et WASI dans Caddy, vous pouvez consulter le <a href="https://github.com/darkweak/wazemmes" rel="noopener noreferrer">dépôt darkweak/wazemmes sur GitHub</a>.</p>
<hr>
<h2 id="mercure-sse-api-platform-et-un-llm-pour-ameliorer-un-chat-bot-mathieu-santostefano">Mercure, SSE, API Platform et un LLM pour améliorer un Chat(bot) (Mathieu Santostefano)</h2>
<p><strong>Les slides de cette conférence sont disponibles : <a href="https://welcomattic.github.io/slides-real-time-ai-chatbot-with-mercure/1" rel="noopener noreferrer">https://welcomattic.github.io/slides-real-time-ai-chatbot-with-mercure/1</a></strong></p>
<h3 id="origine-du-sujet">Origine du sujet</h3>
<p>Le besoin initial du client était de créer des échanges de chat experts payants. La première version utilisait une API + React, mais manquait d'historique des messages. Mercure a été choisi pour la distribution sécurisée des messages aux clients via JWT.</p>
<p>Évolution des besoins :</p>
<ul>
<li>Assister les experts avec un assistant IA</li>
<li>Permettre à l'IA de gérer la première partie de la conversation</li>
<li>Permettre aux experts de prendre le relais si nécessaire</li>
</ul>
<h3 id="boite-a-outils">Boîte à outils</h3>
<h4>Mercure - Échanges en temps réel</h4>
<p>Architecture :</p>
<ul>
<li>Serveur =&gt; Hub =&gt; Client</li>
<li>Client =&gt; Hub =&gt; Client</li>
</ul>
<h4>SSE</h4>
<p>Les Server-Sent Events (SSE) sont une technologie qui permet à un serveur d'envoyer des mises à jour en temps réel à un client via une connexion HTTP persistante. Le client écoute un flux d'événements envoyés par le serveur.</p>
<h4>API Platform</h4>
<h5>LLM</h5>
<p>Génération de données et réponses intelligentes</p>
<h5>Symfony Messenger</h5>
<p>Gestion des processus asynchrones</p>
<h5>Symfony AI</h5>
<p>Équivalent de Mailer/Notifier mais pour les fournisseurs d'IA :</p>
<ul>
<li><strong>Platform</strong> : interface unifiée pour tous les fournisseurs d'IA</li>
<li><strong>Agent</strong> : création d'IA agentique</li>
<li><strong>Store</strong> : abstraction de stockage de données</li>
<li><strong>MCP SDK</strong> : désormais officiellement pris en charge par Anthropic</li>
<li><strong>AI Bundle</strong> : intégration complète avec Symfony</li>
<li><strong>MCP Bundle</strong> : composants supplémentaires</li>
</ul>
<h3 id="implementation">Implémentation</h3>
<p>Flux technique :
<code translate="no">Utilisateur =&gt; message =&gt; Mercure (Stockage) &lt;= Client SSE Symfony =&gt; Symfony Messenger =&gt; Mistral =&gt; Mercure =&gt; réponse IA =&gt; Utilisateur</code></p>
<p>Chat privé sécurisé via JWT (JSON Web Tokens) - une norme ouverte pour échanger des données en toute sécurité entre les parties.</p>
<h4>Envoyer un message à Mercure</h4>
<pre><code class="language-javascript hljs javascript" translate="no">fetch(<span class="hljs-keyword">this</span>.hubURL, {
    <span class="hljs-attr">method</span>: <span class="hljs-string">'POST'</span>,
    <span class="hljs-attr">credentials</span>: <span class="hljs-string">'include'</span>, <span class="hljs-comment">// Send JWT cookie</span>
    <span class="hljs-attr">body</span>: <span class="hljs-keyword">new</span> URLSearchParams({
        <span class="hljs-attr">topic</span>: topic,
        <span class="hljs-attr">data</span>: <span class="hljs-built_in">JSON</span>.stringify(
            <span class="hljs-keyword">new</span> MercureUpdateData(
                conversationId,
                msg,
            ),
        ),
        <span class="hljs-attr">private</span>: <span class="hljs-string">'on'</span> <span class="hljs-comment">// restrict message to subscribed clients</span>
    })
});</code></pre>
<h4>Se connecter à Mercure</h4>
<pre><code class="language-javascript hljs javascript" translate="no"><span class="hljs-keyword">const</span> eventSource = <span class="hljs-keyword">new</span> EventSource(<span class="hljs-string">'/sse-endpoint'</span>);
eventSource.onmessage = <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'New event received: '</span>, event.data);
};</code></pre>
<h4>Client SSE Symfony</h4>
<p>EventSourceHttpClient intégré :</p>
<pre><code class="language-php hljs php" translate="no"><span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">HttpClient</span>\<span class="hljs-title">Chunk</span>\<span class="hljs-title">ServerSentEvent</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">HttpClient</span>\<span class="hljs-title">EventSourceHttpClient</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">HttpClient</span>\<span class="hljs-title">HttpClient</span>;

$eventSourceClient = <span class="hljs-keyword">new</span> EventSourceHttpClient(HttpClient::create());

$connection = $eventSourceClient-&gt;connect(<span class="hljs-string">"YOUR-MERCURE-URL"</span>);

<span class="hljs-keyword">while</span> (<span class="hljs-keyword">true</span>) {
    <span class="hljs-keyword">foreach</span> ($eventSourceClient-&gt;stream($connection, <span class="hljs-number">2</span>) <span class="hljs-keyword">as</span> $r =&gt; $chunk) {
        <span class="hljs-keyword">if</span> ($chunk-&gt;isTimeout()) <span class="hljs-keyword">continue</span>; <span class="hljs-comment">// Keep the connection alive.</span>
        <span class="hljs-keyword">if</span> ($chunk-&gt;isLast()) <span class="hljs-keyword">return</span>; <span class="hljs-comment">// Connection closed by server.</span>
        <span class="hljs-keyword">if</span> ($chunk <span class="hljs-keyword">instanceof</span> ServerSentEvent) <span class="hljs-keyword">$this</span>-&gt;processSSE($chunk);
    }
}</code></pre>
<h4>Distribuer les messages avec Messenger</h4>
<pre><code class="language-php hljs php" translate="no"><span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">HttpClient</span>\<span class="hljs-title">Chunk</span>\<span class="hljs-title">ServerSentEvent</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">processSSE</span><span class="hljs-params">(ServerSentEvent $event)</span>: <span class="hljs-title">void</span>
</span>{
    $data = $event-&gt;getArrayData();

    <span class="hljs-comment">// do some checks before asking LLM</span>
    <span class="hljs-keyword">if</span> (!<span class="hljs-keyword">$this</span>-&gt;shouldProcessWithAi($data)) {
        <span class="hljs-keyword">return</span>;
    }

    <span class="hljs-comment">// Dispacth message to ask LLM</span>
    <span class="hljs-keyword">$this</span>-&gt;messageBus-&gt;dispatch(
        <span class="hljs-keyword">new</span> ProcessAiResponseMessage(
            conversationId: $data[<span class="hljs-string">'conversationId'</span>],
            userMessage: $data[<span class="hljs-string">'message'</span>],
            sseMessageId: $event-&gt;getId(),
            timestamp: $data[<span class="hljs-string">'timestamp'</span>]
        ),
    );
}</code></pre>
<h4>Configuration Symfony AI</h4>
<h5>Configuration YAML du bundle AI avec Mistral</h5>
<pre><code class="language-yaml hljs yaml" translate="no"><span class="hljs-attr">ai:</span>
    <span class="hljs-attr">platform:</span>
        <span class="hljs-attr">mistral:</span>
            <span class="hljs-attr">api_key:</span> <span class="hljs-string">'%env(MISTRAL_API_KEY)%'</span>

    <span class="hljs-attr">agent:</span>
        <span class="hljs-attr">default:</span>
            <span class="hljs-attr">platform:</span> <span class="hljs-string">'symfony_ai.platform.mistral'</span>
            <span class="hljs-attr">model:</span>
                <span class="hljs-attr">class:</span> <span class="hljs-string">'Symfony\AI\Platform\Bridge\Mistral\Mistral'</span>
                <span class="hljs-attr">name:</span> <span class="hljs-type">!php</span><span class="hljs-string">/const</span> <span class="hljs-string">Symfony\AI\Platform\Bridge\Mistral\Mistral::MISTRAL_LARGE</span></code></pre>
<h5>Exemple de Handler</h5>
<pre><code class="language-php hljs php" translate="no"><span class="hljs-comment">// Symfony AI Bundle code example</span>
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">AI</span>\<span class="hljs-title">Agent</span>\<span class="hljs-title">AgentInterface</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">AI</span>\<span class="hljs-title">Agent</span>\<span class="hljs-title">Chat</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">AI</span>\<span class="hljs-title">Platform</span>\<span class="hljs-title">Message</span>\<span class="hljs-title">Message</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">AI</span>\<span class="hljs-title">Platform</span>\<span class="hljs-title">Message</span>\<span class="hljs-title">MessageBag</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">AI</span>\<span class="hljs-title">Store</span>\<span class="hljs-title">StoreInterface</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MessageHandler</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span><span class="hljs-params">(
        private readonly AgentInterface $agent,
        private readonly StoreInterface $messageStore,
    )</span> </span>{
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__invoke</span><span class="hljs-params">(ProcessAiResponseMessage $message)</span>
    </span>{
        $chat = <span class="hljs-keyword">new</span> Chat(<span class="hljs-keyword">$this</span>-&gt;agent, <span class="hljs-keyword">$this</span>-&gt;messageStore, <span class="hljs-string">"UNIQUE_ID_TO_PROMPT"</span>);
        $messages = <span class="hljs-keyword">$this</span>-&gt;messageStore-&gt;load(<span class="hljs-string">"UNIQUE_ID_TO_PROMPT"</span>);

        <span class="hljs-keyword">if</span> ($messages-&gt;count() === <span class="hljs-number">0</span>) {
            <span class="hljs-comment">// retrieve system prompt from somewhere ...</span>

            <span class="hljs-comment">// Programmatic System prompt injection</span>
            $chat-&gt;initiate(<span class="hljs-keyword">new</span> MessageBag(
                Message::forSystem(<span class="hljs-string">"SYSTEM_PROMPT_INJECTION"</span>),
            ));
        }

        $llmAnswer = $chat-&gt;submit(Message::ofUser($message-&gt;userMessage));

        <span class="hljs-comment">// do something with the answer</span>
    }
}</code></pre>
<p>Dans ses mots de la fin, Mathieu a dédié sa conférence à la mémoire de Ryan Weaver, dont les contributions continuent d'inspirer la communauté Symfony. Il a également remercié Christopher Hertel pour l'initiative Symfony AI.</p>
<hr>
<h2 id="comment-api-platform-4-2-redefinit-le-developpement-d-api-antoine-bluchet">Comment API Platform 4.2 redéfinit le développement d'API (Antoine Bluchet)</h2>
<p><strong>Les slides de cette conférence sont disponibles : <a href="https://soyuka.me/api-platform-4-2-redefining-api-development/" rel="noopener noreferrer">https://soyuka.me/api-platform-4-2-redefining-api-development/</a></strong></p>
<p>Retour sur la version 4.0 :</p>
<ul>
<li>610 commits</li>
<li>~200 000 lignes de code</li>
<li>291 issues ouvertes</li>
<li>230 issues fermées</li>
</ul>
<h3 id="quoi-de-neuf-dans-la-4-2">Quoi de neuf dans la 4.2</h3>
<p>Fonctionnalités clés de cette version :</p>
<ul>
<li>Intégration FrankenPHP</li>
<li>State Options</li>
<li>Améliorations des paramètres de requête</li>
<li>Améliorations des performances</li>
<li>Compatibilité Laravel</li>
<li>Métadonnées par fichiers PHP</li>
</ul>
<h3 id="ameliorations-des-metadonnees">Améliorations des métadonnées</h3>
<h4>Métadonnées depuis les fichiers PHP</h4>
<figure>
<picture title="Un exemple de métadonnée de fichier PHP">
<source type="image/webp" srcset="https://ktherage.github.io/thumbnails/480x/img/IMG_20250918_134546.7364faf0d7b325c432fb8832ad102fcb.webp 480w, https://ktherage.github.io/thumbnails/640x/img/IMG_20250918_134546.7364faf0d7b325c432fb8832ad102fcb.webp 640w, https://ktherage.github.io/thumbnails/768x/img/IMG_20250918_134546.7364faf0d7b325c432fb8832ad102fcb.webp 768w, https://ktherage.github.io/thumbnails/800x/img/IMG_20250918_134546.7364faf0d7b325c432fb8832ad102fcb.webp 800w" width="800" height="453" sizes="100vw">
<img src="https://ktherage.github.io/img/IMG_20250918_134546.7364faf0d7b325c432fb8832ad102fcb.jpg" alt="Un exemple de métadonnée de fichier PHP" loading="lazy" decoding="async" class="img-fluid rounded mx-auto d-block" width="800" height="453" style=";max-width:100%;height:auto;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gNzUK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAMgBkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8Axbe0LHpWimnEjpU1jGCw4rehhUgcUkS2c8NLOelWY9PZe1dCtuvpUq26+lVcVznhp7elSrYsO1dCtsvpTxbL6UXC5gCzb0pfsbY6V0Ath6UG2HpRcOY5mWxZh0qm+lsT0NdgbZfSmG1X0ouFzjv7LYdqgmsSg6V2j2ygdKyNQjCqeKdyZS0OWMWDRVhyA5oqrHPzs1rKAqRxW5CvyioIrcKelXI1xWSOlskVamQU1RUqCqEPVaeBQKeBQK4YpCKfigimMjIphFSkVGaQiGQcViakhKnFbrDiqFzDvB4oWgmcXJA288UV0TWILHiitOYnlNNVqVVoVakVayRoKoqVRSKtSBaoQ5aeKQLTwKBCgUEUoGKCKBkZqM1MRTGWgCBqhkFWWWoXWkIqFeaKkK80UgHCpFoooRRKtSiiimSyQU8UUUxC0UUUFDTTTRRQBEaheiikIgPWiiikB//Z);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="https://ktherage.github.io/thumbnails/480x/img/IMG_20250918_134546.7364faf0d7b325c432fb8832ad102fcb.jpg 480w, https://ktherage.github.io/thumbnails/640x/img/IMG_20250918_134546.7364faf0d7b325c432fb8832ad102fcb.jpg 640w, https://ktherage.github.io/thumbnails/768x/img/IMG_20250918_134546.7364faf0d7b325c432fb8832ad102fcb.jpg 768w, https://ktherage.github.io/thumbnails/800x/img/IMG_20250918_134546.7364faf0d7b325c432fb8832ad102fcb.jpg 800w" sizes="100vw">
</picture>
<figcaption>Un exemple de métadonnée de fichier PHP</figcaption>
</figure>
<p>Nouveau système de métadonnées permettant d'extraire la configuration API directement depuis les fichiers PHP. Ce n'est pas encore documenté (à ma connaissance) mais vous pouvez voir la PR associée de Loïc Frémont <a href="https://github.com/api-platform/core/pull/7017" rel="noopener noreferrer">https://github.com/api-platform/core/pull/7017</a>.</p>
<h4>Metadata Mutator</h4>
<p>Une nouvelle façon de modifier les métadonnées par programmation :</p>
<ul>
<li>Configuration plus flexible</li>
<li>Ajustements à l'exécution</li>
<li>Architecture plus propre</li>
</ul>
<h3 id="du-filtre-api-aux-parametres">Du filtre API aux paramètres</h3>
<h4>Rétrospective sur ApiFilter</h4>
<p>L'attribut <code translate="no">#[ApiFilter]</code> faisait beaucoup de choses en arrière-plan, telles que :</p>
<ul>
<li>Déclarer des services avec des tags de filtre</li>
<li>Générer la documentation</li>
<li>Appliquer des opérations sur la base de données</li>
<li>Fonctionner avec plusieurs propriétés</li>
</ul>
<p>C'était déroutant et ne respectait pas le principe de responsabilité unique. C'est pourquoi les mainteneurs d'API Platform ont décidé de retravailler cela en paramètres.</p>
<h4>Améliorations de la documentation des filtres</h4>
<p>Désormais, les documentations sont générées séparément. Cela peut être fait avec deux nouvelles interfaces :</p>
<ul>
<li><code translate="no">JsonSchemaFilterInterface</code></li>
<li><code translate="no">OpenApiParameterFilter</code></li>
</ul>
<h4>Système de filtrage</h4>
<p>Maintenant, les filtres sont indépendants via une nouvelle <code translate="no">FilterInterface</code> avec :</p>
<ul>
<li>Méthode <code translate="no">apply()</code> simplifiée</li>
<li>Aucune exigence de constructeur</li>
<li>Conception sans dépendance</li>
</ul>
<h4>Système de paramètres</h4>
<p>L'attribut <code translate="no">#[ApiFilter]</code> laissera sa place à une nouvelle propriété des attributs d'opérations appelée <code translate="no">parameters</code>.</p>
<figure>
<picture title="Un exemple d&#039;utilisation des paramètres">
<source type="image/webp" srcset="https://ktherage.github.io/thumbnails/480x/img/IMG_20250918_135352.37a61667fe9cf52f8b7347629b3a055f.webp 480w, https://ktherage.github.io/thumbnails/640x/img/IMG_20250918_135352.37a61667fe9cf52f8b7347629b3a055f.webp 640w, https://ktherage.github.io/thumbnails/768x/img/IMG_20250918_135352.37a61667fe9cf52f8b7347629b3a055f.webp 768w, https://ktherage.github.io/thumbnails/800x/img/IMG_20250918_135352.37a61667fe9cf52f8b7347629b3a055f.webp 800w" width="800" height="442" sizes="100vw">
<img src="https://ktherage.github.io/img/IMG_20250918_135352.37a61667fe9cf52f8b7347629b3a055f.jpg" alt="Un exemple d&#039;utilisation des paramètres" loading="lazy" decoding="async" class="img-fluid rounded mx-auto d-block" width="800" height="442" style=";max-width:100%;height:auto;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gNzUK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAMgBkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A89QFjxVyK3cjpUlnZFnGa6mx0oMgOKqUWRGrF7HNpbSA9KvwwMO1dOmjr/dqddIH92kinO5ziwt6VIsLeldIulD0qQaWP7tXclyOYMT+lKIXPauoGlj+7S/2WP7tFxcxyE1uxHSs6SykLdDXfHSgf4ajbSFP8NPmHzHAmzcdRTGhI7V2tzpSop4rCurXYx4q0uY5q1dQMMxGitDyaKXs2cv1tFmwiUyCuw0+IbBXFaVPvkFd3p3MYp1JJl4enKO5fSEY6VMsI9KVBxUgFZHYIIR6U4RD0p4p4oAjEQ9KPKHpUwFGKAIDEPSmNEPSrJFRt0osIyb1BsNcpfgBzXW6gcIa4rU5trmuqiefiqUp7FU4zRVTz/eiunQ8/wCq1CLRQfNFei6bxGtee6Rw4Nd1Yz4jFeYfSyjY31IxUgxWctzx1qRbketFiLGgKeDWeLoetO+1e9OwWZoAilyKz/tY9aQ3Y9aLCsXyRUbEVSN2PWmtdigViHUfuGuD1cHea7S7n3Ia5HUl3MatS5SopN6mBg0VYMfNFV7ZmnIhdK+8K7Gz+4KKKyKkXxTxRRTRA4UoooqikJSGiigTGk0lFFSQyC4+6a52+6miipYR3Mw9aKKKg1P/2Q==);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="https://ktherage.github.io/thumbnails/480x/img/IMG_20250918_135352.37a61667fe9cf52f8b7347629b3a055f.jpg 480w, https://ktherage.github.io/thumbnails/640x/img/IMG_20250918_135352.37a61667fe9cf52f8b7347629b3a055f.jpg 640w, https://ktherage.github.io/thumbnails/768x/img/IMG_20250918_135352.37a61667fe9cf52f8b7347629b3a055f.jpg 768w, https://ktherage.github.io/thumbnails/800x/img/IMG_20250918_135352.37a61667fe9cf52f8b7347629b3a055f.jpg 800w" sizes="100vw">
</picture>
<figcaption>Un exemple d'utilisation des paramètres</figcaption>
</figure>
<h4>Nouveaux types de filtres</h4>
<ul>
<li>Capacités de recherche en texte libre</li>
<li>Fournisseur de variables URI</li>
</ul>
<h3 id="ameliorations-du-json-schema">Améliorations du JSON Schema</h3>
<p>Quelques améliorations ont été apportées à la génération du JSON Schema. Ces changements pourraient impliquer une rupture de compatibilité ascendante pour les outils utilisant l'ancien JSON Schema.</p>
<p>Améliorations :</p>
<ul>
<li>Mutualisation des schémas</li>
<li>Fichiers de spécification OpenAPI 30% plus petits</li>
<li>Opérations I/O réduites</li>
</ul>
<p>Un nouvel outil est désormais recommandé : <a href="https://pb33f.io" rel="noopener noreferrer">pb33f.io</a> car il est plus riche en fonctionnalités et mieux maintenu que Swagger UI.</p>
<h3 id="performances">Performances</h3>
<p>Benchmarks de performance comparant Nginx et FrankenPHP :</p>
<figure>
<picture title="Comparaison de performance entre Nginx et FrankenPHP 1">
<source type="image/webp" srcset="https://ktherage.github.io/thumbnails/480x/img/IMG_20250918_140008.c0143ed4065f1c319a49d3932b6470ea.webp 480w, https://ktherage.github.io/thumbnails/640x/img/IMG_20250918_140008.c0143ed4065f1c319a49d3932b6470ea.webp 640w, https://ktherage.github.io/thumbnails/768x/img/IMG_20250918_140008.c0143ed4065f1c319a49d3932b6470ea.webp 768w, https://ktherage.github.io/thumbnails/800x/img/IMG_20250918_140008.c0143ed4065f1c319a49d3932b6470ea.webp 800w" width="800" height="503" sizes="100vw">
<img src="https://ktherage.github.io/img/IMG_20250918_140008.c0143ed4065f1c319a49d3932b6470ea.jpg" alt="Comparaison de performance entre Nginx et FrankenPHP 1" loading="lazy" decoding="async" class="img-fluid rounded mx-auto d-block" width="800" height="503" style=";max-width:100%;height:auto;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gNzUK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAMgBkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A8/paKKSQx6dasrHkVXjHNaEERcdK1iS2V/LpRHWiLNz2pwsn9KqwuYzhHTtuK0fsL+lMaycdqpWJ5igRxUL9auy27KOlUnBBrWKuJsjxT1XNCoTVmOL2pyRlKViDYaKu+T7UVlymftCH+zH9KcNKc9jXcrpqf3alXTU/u1gkdTmcPDpD7hwa37DSTgZWuhj01M9Kvw2ioOlUQ5mSmkrjpTv7LUHpW6UVRVd2AapbOarVcTPXSlI+7UU2lqB0rciIxUM5BpXZEqr5bnK3WlhgcCseXRW3fdruxCr9qRrJPStYVLGlKbkjhU0Yj+GrA0sqOldh9jQdqbLaqF6Vo6lypRucj9hPpRXQtbrnpRRzEezLQnQdxTlukHcVxZ1o/wB6mHWyP4qhQN2md9HdJnqKtpMCOK89t9bJcfNXTadeGZRzVcliJXRrzS4FU/MLNVopvFItvzWc43Oea5kIshC1C8pJq2YeKiaDHNJx0M4x0sNjkCjmke7Re9U7yXyUNcxeasyOQDVwpm9KNjr/ALcnqKZJeIV61w/9stnqaeNWZu9OSsdCizqWulz1orlv7Rb1oqOYfIznyajNFFbQN2WLT/WCu50T7q0UVUjlqHURfdFSiiisWczHHpUUnSiimSjC1X7hrhL/AP1poorWB00SitTpRRWdQ7CWiiisBH//2Q==);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="https://ktherage.github.io/thumbnails/480x/img/IMG_20250918_140008.c0143ed4065f1c319a49d3932b6470ea.jpg 480w, https://ktherage.github.io/thumbnails/640x/img/IMG_20250918_140008.c0143ed4065f1c319a49d3932b6470ea.jpg 640w, https://ktherage.github.io/thumbnails/768x/img/IMG_20250918_140008.c0143ed4065f1c319a49d3932b6470ea.jpg 768w, https://ktherage.github.io/thumbnails/800x/img/IMG_20250918_140008.c0143ed4065f1c319a49d3932b6470ea.jpg 800w" sizes="100vw">
</picture>
<figcaption>Comparaison de performance entre Nginx et FrankenPHP 1</figcaption>
</figure>
<figure>
<picture title="Comparaison de performance entre Nginx et FrankenPHP 2">
<source type="image/webp" srcset="https://ktherage.github.io/thumbnails/480x/img/IMG_20250918_140037.f722bcd68b0b03577988e877110ceb80.webp 480w, https://ktherage.github.io/thumbnails/640x/img/IMG_20250918_140037.f722bcd68b0b03577988e877110ceb80.webp 640w, https://ktherage.github.io/thumbnails/768x/img/IMG_20250918_140037.f722bcd68b0b03577988e877110ceb80.webp 768w, https://ktherage.github.io/thumbnails/800x/img/IMG_20250918_140037.f722bcd68b0b03577988e877110ceb80.webp 800w" width="800" height="435" sizes="100vw">
<img src="https://ktherage.github.io/img/IMG_20250918_140037.f722bcd68b0b03577988e877110ceb80.jpg" alt="Comparaison de performance entre Nginx et FrankenPHP 2" loading="lazy" decoding="async" class="img-fluid rounded mx-auto d-block" width="800" height="435" style=";max-width:100%;height:auto;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gNzUK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAMgBkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A8qhXmtGEdKpxLg1ehHIoGaNshbFa0MDY6VFpduJCOK6y20wFAcVcdCGzn/IYdqURN6V1H9mD0ph05QelPmIc0jm/JfHSmmF/SuqTTA3anNpIx92mpCU09TkDA3pVS4hYA8V18unhT0qjdWA2E4oU7szVZXscU6kE5oQZq9ew7HNVo15rdQ0HOY4JxRU4HFFLkMvamJHZv6VaitXDDg12CaIP7tWI9FGR8tcqR2uRR0S3IK5FdxZxDYOKzLLTxFjitqLCLVJGUmSNGAOlUpcBqsyXAA61nyTBm61M9DixEmloX7YA1aeMbKpWsgAFWZLgBetCTsKjK8TNuVG41QuI90Zq5PKGahIvMWpi/eMI39ocRqdqxc4FZqWzg9K7+40sSH7tVf7GA/hrvjUVjv5bnJC3bHSiut/skelFHOifZmwkC+lTrAPShBU6iuNHQNWMCopm2jipz0qCRdwraBhNsy7ids1BG7M1XpLbcelLFaYPSoqxuzOa5oWHxEhagnnYVoCDC1BLa7u1Ul7tjGlFxZmK7O3NbFmvAzVdLTB6VoQR7RWChqW4+/dE3lAjpTGhX0qcHimMa01OlMgMa56UVJRU6lXK61MKKKaKENMNFFaxMJkZ609KKKTIexMOlMaiihbEoQdamSiipKJO1MNFFBaG0UUVJR//2Q==);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="https://ktherage.github.io/thumbnails/480x/img/IMG_20250918_140037.f722bcd68b0b03577988e877110ceb80.jpg 480w, https://ktherage.github.io/thumbnails/640x/img/IMG_20250918_140037.f722bcd68b0b03577988e877110ceb80.jpg 640w, https://ktherage.github.io/thumbnails/768x/img/IMG_20250918_140037.f722bcd68b0b03577988e877110ceb80.jpg 768w, https://ktherage.github.io/thumbnails/800x/img/IMG_20250918_140037.f722bcd68b0b03577988e877110ceb80.jpg 800w" sizes="100vw">
</picture>
<figcaption>Comparaison de performance entre Nginx et FrankenPHP 2</figcaption>
</figure>
<p>Plus de benchmarks disponibles sur <a href="https://soyuka.github.io/sylius-benchmarks/" rel="noopener noreferrer">soyuka.github.io/sylius-benchmarks/</a></p>
<p>Améliorations du JSON Streamer :</p>
<ul>
<li>~32,4% de meilleure performance en requêtes/seconde</li>
<li>Configurable via les paramètres</li>
</ul>
<h3 id="state-options">State Options</h3>
<p>Nouvelles fonctionnalités pour l'interrogation des sous-ressources :</p>
<ul>
<li>Chargement de données plus efficace</li>
<li>Entity class magic (RIP Ryan)</li>
</ul>
<h3 id="data-mapping">Data Mapping</h3>
<p>Nouvelles capacités de mapping :</p>
<ul>
<li>Mapping de la base de données vers la représentation API</li>
<li>Intégration Symfony ObjectMapper</li>
<li>Meilleure transformation des données</li>
</ul>
<h3 id="debogage">Débogage</h3>
<p>Les outils de profilage sont de retour !</p>
<h3 id="retrocompatibilite">Rétrocompatibilité</h3>
<ul>
<li>De nombreuses nouvelles fonctionnalités ajoutées</li>
<li>Aucune dépréciation dans cette version</li>
<li>Le système de paramètres n'est plus expérimental</li>
</ul>
<h3 id="perspectives-pour-api-platform-5-0">Perspectives pour API Platform 5.0</h3>
<p>Changements prévus :</p>
<ul>
<li>Dépréciation de <code translate="no">#[ApiFilter]</code> (script de migration à venir)</li>
<li>Utilisation accrue du JSON Streamer</li>
<li>Demandes de fonctionnalités pour Object Mapper</li>
<li>Améliorations pilotées par la communauté</li>
</ul>
<hr>
<h2 id="design-pattern-le-tresor-est-dans-le-vendor-smaine-milianni">Design pattern, le trésor est dans le vendor (Smaïne Milianni)</h2>
<p><strong>Les slides de cette conférence sont disponibles : <a href="https://ismail1432.github.io/conferences/2025/apip_con/index.html" rel="noopener noreferrer">https://ismail1432.github.io/conferences/2025/apip_con/index.html</a></strong></p>
<p>Dans cette conférence, Smaïne a fait un tour des design patterns qui sont couramment utilisés sans que l'on sache qu'ils se trouvent dans les vendors que nous utilisons quotidiennement. Il a également présenté des extraits de code PHP petits et compréhensibles expliquant certains d'entre eux.</p>
<p>Parmi eux, on trouvait :</p>
<ul>
<li>Le Pattern Strategy</li>
<li>Le Pattern Adapter</li>
<li>Le Pattern Factory</li>
<li>Le Pattern Builder</li>
<li>Le Pattern Proxy</li>
<li>Le Pattern Observer</li>
<li>Le Pattern Event Dispatcher</li>
<li>Le Pattern Decorator</li>
<li>Le Pattern Facade</li>
<li>Le Pattern Template</li>
<li>Le Pattern Chain of Responsibility</li>
</ul>
<p>Smaïne a également terminé par un clin d'œil à Ryan Weaver.</p>
<hr>
<h2 id="et-si-on-faisait-de-l-event-storming-dans-nos-projets-api-platform-gregory-planchat">Et si on faisait de l'Event Storming dans nos projets API Platform ? (Gregory Planchat)</h2>
<h3 id="event-storming">Event Storming</h3>
<p>L'Event Storming est une technique d'atelier collaboratif qui réunit à la fois les utilisateurs et les développeurs dans la même pièce. Cette méthodologie met en lumière les incompréhensions qui existent souvent entre les parties prenantes métier et les équipes techniques.</p>
<p>La beauté de l'Event Storming réside dans sa simplicité : il utilise des post-it physiques pour encourager les différents membres de l'équipe à échanger des idées, se voir, partager leurs connaissances, se rencontrer en personne et confronter leur compréhension du domaine métier.</p>
<h3 id="preparation">Préparation</h3>
<p>Le processus d'Event Storming suit une approche structurée avec plusieurs étapes clés :</p>
<ol>
<li><strong>Lister les événements</strong> - Commencez par identifier tous les événements significatifs qui se produisent dans votre domaine métier</li>
<li><strong>Organiser les événements</strong> - Disposez ces événements dans un ordre chronologique ou logique</li>
<li><strong>Établir les commandes</strong> - Identifiez les actions qui déclenchent chaque événement</li>
<li><strong>Identifier les acteurs</strong> - Déterminez qui ou quoi initie chaque commande</li>
<li><strong>Post-it verts</strong> - Ajoutez les données nécessaires aux utilisateurs pour prendre des décisions</li>
<li><strong>Ajouter les systèmes externes</strong> - Incluez les systèmes tiers qui interagissent avec votre domaine</li>
<li><strong>Agrégats</strong> - Regroupez les événements et commandes connexes en concepts métier cohérents</li>
</ol>
<p>L'équipe a mentionné avoir mené plusieurs sessions « jusqu'à ce que plus personne n'ait de questions, que ce soit de la part de l'équipe technique ou de l'équipe métier ». C'est un processus auto-documenté qui peut être répété à mesure que le métier évolue.</p>
<h3 id="avantages">Avantages</h3>
<p>L'Event Storming apporte plusieurs bénéfices concrets aux équipes de développement :</p>
<ul>
<li><strong>Documentation des processus</strong> - L'atelier crée naturellement une documentation vivante de vos processus métier</li>
<li><strong>Intégration facilitée</strong> - Les nouveaux membres de l'équipe peuvent rapidement comprendre le domaine en regardant les artefacts de l'Event Storming</li>
<li><strong>Révélation des incertitudes</strong> - Les hypothèses cachées et les exigences floues émergent pendant les sessions collaboratives</li>
</ul>
<h3 id="avec-api-platform">Avec API Platform</h3>
<h4>Le problème du modèle anémique</h4>
<p>La plupart des applications souffrent de ce qu'on appelle l'anti-patron du modèle anémique, où :</p>
<ul>
<li>La logique métier est dispersée dans de nombreux services</li>
<li>Perte du suivi de l'intention de l'utilisateur</li>
<li>Les entités deviennent de simples conteneurs de données avec des getters et setters</li>
</ul>
<p>Typiquement, lorsque vous souhaitez modifier des informations dans votre entité, vous appelez une méthode setter. Cela se produit souvent dans plusieurs services et classes, d'où le problème de « logique métier disséminée dans de nombreux services ».</p>
<p>L'intention est essentielle pour que les systèmes tiers comprennent ce qui s'est réellement passé dans votre application.</p>
<h4>Modèles riches</h4>
<p>L'approche alternative utilise des modèles de domaine riches qui :</p>
<ul>
<li><strong>Nécessitent un coût important</strong> - Plus complexes à implémenter initialement</li>
<li><strong>Nécessitent une compréhension détaillée de l'application</strong> - L'équipe a besoin d'une connaissance approfondie du domaine</li>
<li><strong>Garantissent la cohérence dans le temps</strong> - Les règles métier sont appliquées au niveau du modèle</li>
<li><strong>Appliquent les contraintes métier</strong> - La logique de validation vit là où elle doit être</li>
<li><strong>Centralisent la logique métier</strong> - Tout ce qui concerne un concept vit dans une ou deux classes</li>
<li><strong>Le modèle garantit l'intégrité</strong> - Les états invalides deviennent impossibles</li>
</ul>
<p>Avec un modèle riche, toutes les modifications se produisent dans l'entité elle-même, gardant la logique métier centralisée et cohérente.</p>
<h4>Le problème du CRUD</h4>
<p>Les opérations CRUD traditionnelles sont :</p>
<ul>
<li>Limitées à 4 opérations (Create, Read, Update, Delete)</li>
<li>Centrées sur la pensée SQL</li>
<li>Des outils comme PostgREST génèrent des API REST automatiquement mais apportent peu de valeur métier</li>
</ul>
<p>Pour faire mieux, nous pouvons tirer parti de la puissance des State Providers et State Processors d'API Platform.</p>
<p>Mais comment préserver l'intention dans notre application ?</p>
<p>La solution suit ce flux :
<strong>Repository → EventBus → Event → Handler</strong></p>
<h4>Modification du modèle</h4>
<p>L'équipe a implémenté un modèle avec trois méthodes clés dans leurs entités :</p>
<ul>
<li><strong>recordThat()</strong> - Enregistre qu'un événement s'est produit (par exemple, « un déploiement a été lancé »)</li>
<li><strong>apply()</strong> - Applique les modifications liées à l'événement (par exemple, met à jour la date de déploiement)</li>
<li><strong>releaseEvents()</strong> - Une étape de nettoyage qui se produit pendant le processus de sauvegarde, juste avant le persist/flush, puis distribue les événements dans l'application</li>
</ul>
<p>Cette approche garantit que chaque action métier est capturée comme un événement significatif, préservant l'intention de l'utilisateur et fournissant une piste d'audit claire de ce qui s'est passé dans le système.</p>
<h3 id="resultats">Résultats</h3>
<p>L'équipe a rapporté plusieurs améliorations concrètes après avoir implémenté cette approche :</p>
<ul>
<li><strong>Une API et une base de code qui ressemblent davantage au domaine métier de l'entreprise</strong></li>
<li><strong>L'intention de l'utilisateur a été préservée</strong> tout au long du cycle de vie de l'application</li>
<li><strong>Meilleure compréhension des actions effectuées</strong> dans l'application, à la fois pour les développeurs et les parties prenantes métier</li>
</ul>
<figure>
<picture title="Extrait de la documentation OpenAPI d&#039;une API conçue avec Event Storming">
<source type="image/webp" srcset="https://ktherage.github.io/thumbnails/480x/img/IMG_20250919_103045.8c36cb59c99c943fce44906124033d9f.webp 480w, https://ktherage.github.io/thumbnails/640x/img/IMG_20250919_103045.8c36cb59c99c943fce44906124033d9f.webp 640w, https://ktherage.github.io/thumbnails/768x/img/IMG_20250919_103045.8c36cb59c99c943fce44906124033d9f.webp 768w, https://ktherage.github.io/thumbnails/800x/img/IMG_20250919_103045.8c36cb59c99c943fce44906124033d9f.webp 800w" width="800" height="394" sizes="100vw">
<img src="https://ktherage.github.io/img/IMG_20250919_103045.8c36cb59c99c943fce44906124033d9f.jpg" alt="Extrait de la documentation OpenAPI d&#039;une API conçue avec Event Storming" loading="lazy" decoding="async" class="img-fluid rounded mx-auto d-block" width="800" height="394" style=";max-width:100%;height:auto;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gNzUK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAMgBkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A8Wity54FXE0p3HStLTrMO4GK6600pCgJFMlSucPHoz+hq3Ho7/3TXeJpSD+Gp00xB/CKVyrHDR6M/oav2+kOpHBrsk05P7tWEsUHai4WOT/st2XpUf8AYjk9DXcpZp6Cpls4/QU1KwHDR6G47Vci0Zx2NdktpGOwqQWyDtVe0YWOUTSWA6VBd6cyoeK7QwqB0rM1FF8s0KTbGcDJZneeKK1ZVHmGiuixHMc5pX3xXZWjfIK5TS4TuFddaQnYK5mY007ltWqVXpEgqwlv7VJ0DVepVc09bf2qdbf2oAiVjUqsamW29qlW29qAIAxp4Y1ZFtTxb+1AFJicVkaiTsNdI1vx0rI1K3+Q1cNxS2OLlz5hoq3LB+8PFFdljnuZmnQgMK6m2ACiuNtL4Kw5rai1QADmuJodJ3OlQrVhCK5tNUHrVmPUwe9Kx0HRIRU6YrAj1IetWY9QB70co0jdXFSrisdL4etTrej1o5WFjWAFOAFZi3o9alW7B70crC5ebG2sXU8bDV5rnI61i6pcfIaqEXcmT0MGVh5hoqjLP+8PNFdnKc5yduea0YycUUVxsqkWENW4yaKKEdCLsRq7EaKKpGiLcZqwpooqkTIlWrEdFFNmZN/DWRqf3DRRShuTLY5SX/WGiiiuswP/2Q==);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="https://ktherage.github.io/thumbnails/480x/img/IMG_20250919_103045.8c36cb59c99c943fce44906124033d9f.jpg 480w, https://ktherage.github.io/thumbnails/640x/img/IMG_20250919_103045.8c36cb59c99c943fce44906124033d9f.jpg 640w, https://ktherage.github.io/thumbnails/768x/img/IMG_20250919_103045.8c36cb59c99c943fce44906124033d9f.jpg 768w, https://ktherage.github.io/thumbnails/800x/img/IMG_20250919_103045.8c36cb59c99c943fce44906124033d9f.jpg 800w" sizes="100vw">
</picture>
<figcaption>Extrait de la documentation OpenAPI d'une API conçue avec Event Storming</figcaption>
</figure>
<hr>
<h2 id="passage-a-l-echelle-des-bases-de-donnees-tobias-petry">Passage à l'échelle des bases de données (Tobias Petry)</h2>
<p>Tobias Petry a partagé des informations sur les stratégies de passage à l'échelle des bases de données. Sa conférence a souligné pourquoi les problèmes de scalabilité proviennent généralement du niveau de la base de données et a passé en revue les solutions les plus courantes, leurs avantages et leurs pièges.</p>
<h3 id="solutions">Solutions</h3>
<p>Il n'y a pas de solution miracle : chaque application a ses propres contraintes. Néanmoins, plusieurs stratégies bien connues existent :</p>
<ul>
<li>
<p><strong>Trouver et corriger les requêtes lentes</strong>
Avant d'envisager l'infrastructure, vérifiez toujours les bases. Des outils comme <a href="https://mysqlexplain.com" rel="noopener noreferrer">mysqlexplain.com</a> peuvent aider à détecter les requêtes inefficaces et suggérer des améliorations.</p>
</li>
<li>
<p><strong>Mettre en cache les résultats</strong>
Servir des réponses mises en cache réduit considérablement la charge sur la base de données et évite de répéter des opérations coûteuses.</p>
</li>
<li>
<p><strong>Scaling vertical (machines plus puissantes)</strong>
Parfois, l'option la plus simple est d'augmenter la puissance : déplacer la base de données vers un serveur plus performant. Cependant, cette approche atteint rapidement des limites physiques et financières.</p>
</li>
<li>
<p><strong>Réplication multi-maître</strong>
Dans cette configuration, plusieurs serveurs acceptent à la fois les lectures et les écritures. Cela améliore la scalabilité des écritures mais crée un risque de conflits lors d'écritures parallèles. Des stratégies de résolution de conflits peuvent atténuer ce problème, mais la complexité augmente avec le nombre de nœuds.</p>
</li>
<li>
<p><strong>Réplication en lecture</strong>
Ici, un nœud principal unique gère les écritures, tandis que les réplicas servent les requêtes de lecture.</p>
<ul>
<li><strong>La réplication synchrone</strong> garantit que les modifications sont propagées à tous les réplicas avant d'accuser réception de l'écriture. Cela garantit la cohérence mais ajoute de la latence, car chaque réplica doit confirmer.</li>
<li><strong>La réplication asynchrone</strong> accuse réception de l'écriture immédiatement et met à jour les réplicas plus tard. Cela réduit la latence mais risque une incohérence temporaire entre les nœuds.
En pratique, la plupart des applications tolèrent la cohérence éventuelle. Une couche de cache devant le primaire masque souvent le retard de réplication. Néanmoins, des études montrent que 90 à 98 % des applications rencontrent des problèmes de latence si elles ne comptent que sur les réplicas pour les lectures.</li>
</ul>
</li>
<li>
<p><strong>Sharding</strong>
Le sharding distribue les données sur plusieurs bases de données. Cela permet une <em>scalabilité théoriquement infinie</em>. Par exemple, les utilisateurs peuvent être répartis entre différents shards en fonction de leur ID.
Le défi vient des requêtes cross-shard : si vous devez récupérer toutes les commandes d'un utilisateur dans plusieurs boutiques, et que les utilisateurs et les boutiques sont shardés différemment, vous devez interroger plusieurs shards et agréger les résultats manuellement. Certaines entreprises introduisent même des <em>shards de shards</em>, ajoutant une couche supplémentaire de complexité.
En raison de cette complexité, le sharding est généralement réservé aux systèmes à très grande échelle. Pour la plupart des cas d'utilisation, la réplication en lecture est suffisante.</p>
</li>
</ul>
<p>En général, ces stratégies sont conçues pour les workloads CRUD. Les requêtes analytiques (tableaux de bord, rapports) sont plus difficiles à scaler avec une base de données relationnelle standard. Les développeurs peuvent explorer des ressources comme <a href="https://sqlfordevs.com" rel="noopener noreferrer">sqlfordevs.com</a> (cours gratuit pour accélérer l'analytique) ou des systèmes spécialisés comme <a href="https://www.timescale.com" rel="noopener noreferrer">TimescaleDB</a>.</p>
<h3 id="ca-semble-complique">Ça semble compliqué</h3>
<p>Tobias a souligné un point crucial : les décisions de mise à l'échelle doivent être prises avant d'atteindre les goulots d'étranglement de la base de données. Une fois les données structurées et les stratégies de scaling en place, revenir en arrière devient presque impossible. L'architecture de base de données est l'un de ces domaines où il est bien plus facile de prendre la bonne décision tôt que de corriger les erreurs plus tard.</p>
<hr>
<h2 id="api-platform-jsonstreamer-et-esa-pour-des-api-fulgurantes-mathias-arlaud">API Platform, JsonStreamer et ESA pour des API fulgurantes (Mathias Arlaud)</h2>
<p><strong>Les slides de cette conférence sont disponibles : <a href="https://www.canva.com/design/DAGyYPxkygw/M1RzOiv8_cMp0Pa7Mh0u4g/view" rel="noopener noreferrer">https://www.canva.com/design/DAGyYPxkygw/M1RzOiv8_cMp0Pa7Mh0u4g/view</a></strong></p>
<p>Histoire : imaginez une librairie. Un client commande <strong>tous</strong> les livres liés à Symfony. Le libraire essaie de tous les rassembler, mais c'est lourd — ça prend du temps, beaucoup de livres. La deuxième fois, même demande, mais la pile est si grande que le libraire s'effondre sous le poids.</p>
<p>Dans le monde des API, <strong>JSON est roi</strong>.</p>
<p>Au cœur de notre stack se trouve <strong>API Platform</strong>, qui repose sur le Serializer de Symfony. Mais parfois, le Serializer est comme ce libraire : il fonctionne bien jusqu'à ce que la charge devienne trop lourde.</p>
<h3 id="serialisation-normalisation-dans-symfony">Sérialisation / Normalisation dans Symfony</h3>
<p>La sérialisation dans Symfony (et dans API Platform) consiste à transformer des objets PHP en tableaux ou valeurs scalaires, puis à les encoder dans des formats comme JSON ou XML. La <strong>normalisation</strong> transforme le graphe d'objets interne en une structure de données neutre (tableaux, scalaires), en appliquant des métadonnées comme les groupes ou les attributs. L'<strong>encodage</strong> convertit ensuite cette structure en la chaîne JSON finale. Le processus inverse (<strong>dénormalisation</strong>) gère l'entrée JSON → tableaux → objets.</p>
<p>Quand les objets ou les collections sont petits, cela fonctionne bien. Mais avec des milliers d'éléments, de grands graphes, des associations profondes et des tableaux imbriqués, l'utilisation mémoire et le temps jusqu'au premier octet se dégradent. La sérialisation devient un goulot d'étranglement.</p>
<h3 id="le-streaming-comme-solution">Le streaming comme solution</h3>
<p>Au lieu de construire une énorme structure en mémoire, le streaming émet les morceaux JSON <strong>progressivement</strong>. Vous ne gardez en mémoire que ce qui est nécessaire à chaque instant.</p>
<p>Symfony 7.3 introduit le composant <strong>JsonStreamer</strong> à cet effet.</p>
<p>Quelques fonctionnalités clés :</p>
<ul>
<li>Fonctionne mieux avec les <strong>POPOs</strong> (Plain Old PHP Objects) ayant des propriétés publiques, sans constructeurs complexes.</li>
<li>L'attribut <code translate="no">#[JsonStreamable]</code> peut être utilisé sur les classes pour les marquer comme streamables. Cela permet également la pré-génération de code pendant le warm-up du cache.</li>
<li>Utilisez le composant <strong>TypeInfo</strong> (<a href="https://symfony.com/blog/new-in-symfony-7-3-jsonstreamer-component" rel="noopener noreferrer">lien</a>) pour décrire les types des collections et des objets (par exemple, <code translate="no">Type::list(Type::object(MyDto::class))</code>). Cela aide JsonStreamer à deviner la forme du JSON de sortie sans tout charger en mémoire.</li>
</ul>
<p>Voici un extrait de code de la documentation Symfony montrant l'utilisation de base :</p>
<pre><code class="language-php hljs php" translate="no"><span class="hljs-comment">// Example class</span>
<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Dto</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">JsonStreamer</span>\<span class="hljs-title">Attribute</span>\<span class="hljs-title">JsonStreamable</span>;

<span class="hljs-comment">#[JsonStreamable]</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span>
</span>{
    <span class="hljs-keyword">public</span> string $name;
    <span class="hljs-keyword">public</span> int $age;
    <span class="hljs-keyword">public</span> string $email;
}

<span class="hljs-comment">// In controller</span>
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">JsonStreamer</span>\<span class="hljs-title">StreamWriterInterface</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">TypeInfo</span>\<span class="hljs-title">Type</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">HttpFoundation</span>\<span class="hljs-title">StreamedResponse</span>;

<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">retrieveUsers</span><span class="hljs-params">(StreamWriterInterface $jsonStreamWriter, UserRepository $userRepository)</span>: <span class="hljs-title">StreamedResponse</span>
</span>{
    $users = $userRepository-&gt;findAll();
    $type = Type::list(Type::object(User::class));
    $json = $jsonStreamWriter-&gt;write($users, $type);
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> StreamedResponse($json);
}</code></pre>
<p>Benchmarks et comparaisons</p>
<p>Pour un jeu de données de 10 000 objets :</p>
<table>
<thead>
<tr>
<th>Method</th>
<th>Time</th>
<th>Memory usage / footprint (rough / relative)</th>
</tr>
</thead>
<tbody>
<tr>
<td>Serializer (traditional)</td>
<td>~ 204 ms</td>
<td>~ 16 MB (grows with size)</td>
</tr>
<tr>
<td>JsonStreamer</td>
<td>~ 87 ms</td>
<td>~ 8 MB (much more constant)</td>
</tr>
</tbody>
</table>
<p>Défis avec les métadonnées, JSON-LD et comment API Platform s'adapte</p>
<p>API Platform ajoute des métadonnées, des contextes JSON-LD, des métadonnées de propriétés, etc. Cela ajoute de la surcharge à la sérialisation. Pour intégrer JsonStreamer tout en préservant les métadonnées riches :</p>
<p>Ils utilisent les points d'extension PropertyMetadataLoader pour fournir des métadonnées à JsonStreamer. Cela permet à JsonStreamer de connaître les noms des propriétés, si elles sont exposées, etc., sans parcourir l'arbre d'objets complet en mémoire.</p>
<p>API Platform</p>
<p>Utilisation de ValueTransformers qui peuvent transformer n'importe quelle valeur à l'exécution. Mais attention : une logique lourde dans les transformers peut dégrader les performances (ils s'exécutent par valeur).</p>
<p>Symfony
+1</p>
<p>Utilisation d'ObjectMapper pour convertir les entités (par exemple, les objets Doctrine) en POPOs (DTOs) adaptés au streaming. Cela aide car les entités ont souvent des propriétés paresseuses, des proxies, des relations, etc., qui compliquent le streaming.</p>
<p>ESA (Edge Side APIs)</p>
<p>Les Edge Side APIs consistent à diviser de grandes charges utiles JSON en appels ou morceaux progressifs plus petits, souvent délivrés depuis la périphérie / le CDN pour améliorer les performances perçues, en particulier dans les réseaux à latence élevée/lente. Dans le contexte de cette conférence :</p>
<p>Au lieu d'envoyer une seule énorme structure JSON, partitionnez ou paginez pour que le client puisse commencer à recevoir des données rapidement.</p>
<p>Combinez avec le streaming pour que des parties de la réponse commencent à être livrées tôt (le TTFB s'améliore).</p>
<p>Bonne expérience utilisateur : l'utilisateur voit quelque chose rapidement plutôt que d'attendre le chargement complet.</p>
<p>Points à retenir</p>
<p>Le Serializer fonctionne, mais pour les grands ensembles de données, il devient inefficace.</p>
<p>JsonStreamer apporte des améliorations significatives à la fois en utilisation mémoire et en temps jusqu'au premier octet.</p>
<p>Lorsque vous avez des couches de métadonnées (API Platform, JSON-LD), utilisez les points d'extension fournis pour brancher le streaming sans perdre de fonctionnalités.</p>
<p>Évitez les calculs/transformations lourds dans les chemins chauds à l'exécution (par exemple, ValueTransformers).</p>
<p>Concevez votre API en connaissant ces options tôt, car une fois que le chemin de sérialisation principal est profondément intégré, le changer est difficile.</p>
<h3 id="benchmarks-et-comparaisons">Benchmarks et comparaisons</h3>
<p>Pour un jeu de données de <strong>10 000 objets</strong> :</p>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Temps</th>
<th>Utilisation mémoire / empreinte</th>
</tr>
</thead>
<tbody>
<tr>
<td>Serializer (traditionnel)</td>
<td>~204 ms</td>
<td>~16 Mo (augmente avec la taille)</td>
</tr>
<tr>
<td>JsonStreamer</td>
<td>~87 ms</td>
<td>~8 Mo (beaucoup plus constante)</td>
</tr>
</tbody>
</table>
<h3 id="defis-avec-les-metadonnees-json-ld-et-comment-api-platform-s-adapte">Défis avec les métadonnées, JSON-LD et comment API Platform s'adapte</h3>
<p>API Platform ajoute des <strong>métadonnées</strong>, des contextes JSON-LD et des métadonnées de propriétés. Cette surcharge alourdit la sérialisation. Pour intégrer JsonStreamer tout en conservant ces fonctionnalités :</p>
<ul>
<li>Utilisez les points d'extension <strong>PropertyMetadataLoader</strong> pour fournir des métadonnées à JsonStreamer. Cela lui indique quelles propriétés exposer, sans parcourir l'arbre d'objets complet.</li>
<li>Utilisez les <strong>ValueTransformers</strong> pour ajuster les valeurs à l'exécution. Mais attention : une logique lourde ici dégradera les performances, car les transformers s'exécutent pour chaque valeur.</li>
<li>Utilisez <strong>ObjectMapper</strong> pour convertir les entités (par exemple, les objets Doctrine) en POPOs (DTOs) plus faciles à streamer.</li>
</ul>
<h3 id="le-pattern-esa-edge-side-apis">Le pattern ESA (Edge Side APIs)</h3>
<p>Les <strong>Edge Side APIs (ESA)</strong> consistent à diviser de grandes charges utiles JSON en morceaux progressifs plus petits, souvent délivrés depuis la périphérie ou un CDN pour améliorer les performances perçues, en particulier dans les réseaux à latence élevée ou lente.</p>
<p>En pratique :</p>
<ul>
<li>Au lieu d'envoyer une seule énorme structure JSON, partitionnez ou paginez pour que le client commence à recevoir les données plus tôt.</li>
<li>Combinez avec le streaming pour que des parties de la réponse arrivent progressivement, améliorant le temps jusqu'au premier octet.</li>
<li>L'expérience utilisateur est meilleure : les données apparaissent rapidement au lieu d'attendre que tout soit chargé.</li>
</ul>
<h3 id="points-a-retenir">Points à retenir</h3>
<ul>
<li>Le Serializer de Symfony est satisfaisant pour les ensembles de données petits à moyens.</li>
<li>JsonStreamer offre des <strong>améliorations significatives</strong> en utilisation mémoire et en TTFB.</li>
<li>API Platform l'intègre via des points d'extension (PropertyMetadataLoader, ValueTransformers, ObjectMapper).</li>
<li>Évitez les transformations lourdes à l'exécution pour de meilleures performances.</li>
<li>Concevez votre API avec ces options à l'esprit dès le début — les décisions de sérialisation sont très difficiles à modifier par la suite.</li>
</ul>
<h2 id="credits">Crédits</h2>
<p>Image de couverture par <a href="https://ncls.tv/" rel="noopener noreferrer">Nicolas Detrez</a></p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://ktherage.github.io/fr/blog/api-platform-con-2025-day-2/</id>
    <title>API Platform con 2025 - JOUR 2</title>
    <published>2025-09-23T00:00:00+00:00</published>
    <link href="https://ktherage.github.io/fr/blog/api-platform-con-2025-day-2/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<p>J'ai eu l'opportunité d'assister à l'API Platform Con 2025 grâce à SensioLabs et voici ce que j'ai appris lors des conférences auxquelles j'ai assisté.</p>
<p>Table des matières :</p>
<div id="toc"><ul>
<li class="toc-level-2"><a href="#comment-les-llm-changent-la-facon-de-construire-des-api-fabien-potencier">Comment les LLM changent la façon de construire des API (Fabien Potencier)</a>
<ul>
<li class="toc-level-3"><a href="#les-agents">Les agents ?</a></li>
<li class="toc-level-3"><a href="#qui-peut-consommer-votre-application">Qui peut consommer votre application ?</a></li>
<li class="toc-level-3"><a href="#le-defi-api-pour-les-humains-vs-machines-vs-agents-ia">Le défi : API pour les humains vs. machines vs. agents IA</a></li>
<li class="toc-level-3"><a href="#bonnes-pratiques-pour-des-api-adaptees-aux-llm">Bonnes pratiques pour des API adaptées aux LLM</a></li>
<li class="toc-level-3"><a href="#defis-des-tests">Défis des tests</a></li>
<li class="toc-level-3"><a href="#considerations-techniques">Considérations techniques</a></li>
<li class="toc-level-3"><a href="#tout-journaliser">Tout journaliser</a></li>
<li class="toc-level-3"><a href="#la-nouvelle-experience-ax-ai-experience">La nouvelle expérience : AX (AI Experience)</a></li>
</ul>
</li>
<li class="toc-level-2"><a href="#construire-une-application-decouplee-avec-api-platform-et-vue-js-nathan-de-pachtere">Construire une application découplée avec API Platform et Vue.js (Nathan de Pachtere)</a>
<ul>
<li class="toc-level-3"><a href="#headless">Headless</a></li>
<li class="toc-level-3"><a href="#decouple">Découplé</a></li>
<li class="toc-level-3"><a href="#pourquoi-choisir-cette-approche">Pourquoi choisir cette approche ?</a></li>
<li class="toc-level-3"><a href="#implementation-headless">Implémentation Headless</a></li>
<li class="toc-level-3"><a href="#implementation-decouplee">Implémentation Découplée</a></li>
<li class="toc-level-3"><a href="#gestion-des-versions">Gestion des versions</a></li>
</ul>
</li>
<li class="toc-level-2"><a href="#jean-beru-presente-fun-with-flags-hubert-lenoir">Jean-Beru présente : Fun with flags (Hubert Lenoir)</a>
<ul>
<li class="toc-level-3"><a href="#que-sont-les-feature-flags">Que sont les Feature Flags ?</a></li>
<li class="toc-level-3"><a href="#types-de-feature-flags">Types de Feature Flags</a></li>
<li class="toc-level-3"><a href="#implementation">Implémentation</a></li>
<li class="toc-level-3"><a href="#avec-api-platform">Avec API Platform</a></li>
<li class="toc-level-3"><a href="#avantages">Avantages</a></li>
</ul>
</li>
<li class="toc-level-2"><a href="#pie-la-prochaine-grande-chose-alexandre-daubois">PIE : La prochaine grande chose (Alexandre Daubois)</a>
<ul>
<li class="toc-level-3"><a href="#les-extensions">Les extensions ?</a></li>
<li class="toc-level-3"><a href="#installation-d-une-bibliotheque-tierce">Installation d&#039;une bibliothèque tierce</a></li>
<li class="toc-level-3"><a href="#pecl">PECL</a></li>
<li class="toc-level-3"><a href="#docker-php-extension-installer">docker-php-extension-installer</a></li>
<li class="toc-level-3"><a href="#projet-pour-remplacer-pecl">Projet pour remplacer PECL</a></li>
<li class="toc-level-3"><a href="#l-avenir-des-extensions">L&#039;avenir des extensions</a></li>
</ul>
</li>
<li class="toc-level-2"><a href="#rendez-vos-developpeurs-heureux-en-normalisant-vos-erreurs-api-clement-herreman">Rendez vos développeurs heureux en normalisant vos erreurs API (Clément Herreman)</a>
<ul>
<li class="toc-level-3"><a href="#qu-est-ce-qu-une-erreur">Qu&#039;est-ce qu&#039;une erreur ?</a></li>
<li class="toc-level-3"><a href="#pourquoi-normaliser-les-erreurs">Pourquoi normaliser les erreurs ?</a></li>
<li class="toc-level-3"><a href="#comment">Comment ?</a></li>
</ul>
</li>
<li class="toc-level-2"><a href="#symfony-et-l-injection-de-dependances-du-passe-au-futur-imen-ezzine">Symfony et l&#039;injection de dépendances : Du passé au futur (Imen Ezzine)</a>
<ul>
<li class="toc-level-3"><a href="#les-debuts">Les débuts</a></li>
<li class="toc-level-3"><a href="#symfony-2-et-le-changement-de-paradigme">Symfony 2 et le changement de paradigme</a></li>
<li class="toc-level-3"><a href="#symfony-5-a-symfony-7">Symfony 5 à Symfony 7</a></li>
<li class="toc-level-3"><a href="#points-a-retenir">Points à retenir</a></li>
</ul>
</li>
<li class="toc-level-2"><a href="#credits">Crédits</a></li>
</ul>
</div>
<hr>
<h2 id="comment-les-llm-changent-la-facon-de-construire-des-api-fabien-potencier">Comment les LLM changent la façon de construire des API (Fabien Potencier)</h2>
<p><strong>Les slides de cette conférence sont disponibles : <a href="https://speakerdeck.com/fabpot/how-ai-agents-are-changing-the-way-we-should-build-apis" rel="noopener noreferrer">https://speakerdeck.com/fabpot/how-ai-agents-are-changing-the-way-we-should-build-apis</a></strong></p>
<p>Fabien Potencier a partagé des informations sur la façon dont les grands modèles de langage sont en train de changer fondamentalement la façon dont nous devons concevoir les API. Comme il l'a mentionné, c'est un monde qui change si vite que certaines affirmations pourraient déjà être obsolètes.</p>
<h3 id="les-agents">Les agents ?</h3>
<p>Les LLM évoluent au-delà de la simple génération de texte pour devenir des agents autonomes. Selon la définition d'Anthropic, un agent est un LLM qui utilise des outils dans une boucle. Ces LLM sont auto-dirigés — ils peuvent raisonner sur les choses, ils peuvent planifier et ont une mémoire.</p>
<p>Un agent IA est en quelque sorte un mélange entre une machine et un humain, combinant la puissance de calcul des machines avec des capacités de raisonnement de type humain.</p>
<h3 id="qui-peut-consommer-votre-application">Qui peut consommer votre application ?</h3>
<p>À l'époque, les consommateurs étaient clairement définis :</p>
<p><strong>Site Web :</strong></p>
<ul>
<li>Utilisateurs humains uniquement</li>
</ul>
<p><strong>Outils CLI :</strong></p>
<ul>
<li>Réservés aux développeurs</li>
</ul>
<p><strong>API :</strong></p>
<ul>
<li>Uniquement pour les machines</li>
<li>Semi-privée (pour découpler le frontend) ou publique</li>
</ul>
<p>De nos jours, les API sont principalement utilisées pour exposer des données, mais les agents IA ont complètement changé la donne. Ils sont capables d'interagir avec les trois interfaces :</p>
<ul>
<li><strong>Les sites Web peuvent être scrappés par l'IA</strong> - les agents peuvent naviguer et extraire des informations des interfaces web</li>
<li><strong>Les outils CLI peuvent être utilisés via des serveurs MCP</strong> - fournissant un accès structuré aux outils</li>
<li><strong>Les API</strong> - les LLM (par exemple dans les chatbots) sont souvent des wrappers autour d'API. De plus, les LLM peuvent aussi écrire des appels API directement.</li>
</ul>
<p>Mais ces trois types ont des attentes différentes, ce qui crée de nouveaux défis.</p>
<h3 id="le-defi-api-pour-les-humains-vs-machines-vs-agents-ia">Le défi : API pour les humains vs. machines vs. agents IA</h3>
<p>Les API sont optimisées pour les machines, mais quand quelque chose se casse, vous avez besoin d'un humain dans la boucle. Cependant, les agents IA sont autonomes mais, comme les humains, ils ont besoin d'aide et de conseils.</p>
<p>Prenons l'exemple des codes de statut HTTP. Ils fournissent des informations sur les problèmes, mais les agents IA ont besoin de plus de contexte.
Les réponses HTTP peuvent fournir un contexte sur les erreurs, mais les réponses fournies par les API peuvent ne pas être à jour ou précises, ce qui fait que les LLM restent bloqués.</p>
<p>Voici un schéma de workflow courant suivi par les LLM : Pensée → Action → Observation.
Sans conseils fournis via les prompts, ils peuvent boucler sur le même problème, rencontrant la même Observation après avoir effectué la même Action — potentiellement pour toujours.
Les LLM vont essayer de deviner et de s'auto-corriger, ce qui est probablement mauvais pour deux raisons :</p>
<ul>
<li><strong>Coûteux</strong> - plus d'appels API et de traitement</li>
<li><strong>Perte de temps</strong> - résolution inefficace du problème</li>
<li><strong>Gourmand en ressources</strong> - le temps GPU et l'électricité sont consommés sans résoudre le problème</li>
</ul>
<p><strong>Conseil :</strong> Moins vous faites d'allers-retours avec un LLM, plus il devient « déterministe », même si les LLM ne sont pas intrinsèquement déterministes.</p>
<h3 id="bonnes-pratiques-pour-des-api-adaptees-aux-llm">Bonnes pratiques pour des API adaptées aux LLM</h3>
<p>Tout ce qui est valable pour les LLM est également valable pour les humains.</p>
<h4>Messages d'erreur</h4>
<p>Soyez précis avec vos messages d'erreur : « Format de date incorrect. Utilisez 'YYYY-MM-DD'. »
Avantages :</p>
<ul>
<li>Moins de tokens consommés</li>
<li>Utilisation réduite de la fenêtre de contexte</li>
<li>Résolution plus rapide</li>
</ul>
<h4>Nommage cohérent</h4>
<p>Utilisez le même modèle de nommage partout. Par exemple, utilisez <code translate="no">user_id</code> de manière cohérente sur tous les points de terminaison.
Avantages :</p>
<ul>
<li>Modèles prévisibles</li>
<li>Les LLM aiment la cohérence</li>
<li>Plus facile à comprendre et à utiliser</li>
</ul>
<h4>Documentation</h4>
<ul>
<li>Corrigez les exemples et supprimez le contenu obsolète</li>
<li>Moins de problèmes et d'hallucinations</li>
<li>Envisagez d'utiliser des fichiers <code translate="no">llms.txt</code> - documentation spécifiquement formatée pour les LLM en Markdown</li>
</ul>
<h4>Considérations de performance</h4>
<p>Les agents IA sont lents, donc réduire le nombre de requêtes offre un gain de performance significatif.</p>
<h4>Conception d'API orientée intention</h4>
<p>Concevez vos API pour capturer et préserver l'intention utilisateur plutôt que de simplement exposer des opérations CRUD.</p>
<h3 id="defis-des-tests">Défis des tests</h3>
<p>Tester les agents IA est extrêmement difficile car :</p>
<ul>
<li>Les LLM ne sont pas déterministes</li>
<li>Vous devez régler la température à 0 pour des résultats plus cohérents</li>
<li>Utilisez des prompts concis</li>
<li>En fin de compte, vous avez besoin d'un humain pour juger la qualité des actions effectuées par le LLM, ce qui rend les tests automatisés complexes</li>
</ul>
<h3 id="considerations-techniques">Considérations techniques</h3>
<h4>Tokens vs. Texte</h4>
<p>Comprendre la tokenisation est crucial. Des outils comme <a href="https://tiktokenizer.vercel.app" rel="noopener noreferrer">tiktokenizer.vercel.app</a> aident à visualiser comment le texte est tokenisé :</p>
<ul>
<li><strong>La langue a son importance :</strong> l'anglais coûte moins de tokens que le français ou le japonais par exemple</li>
<li><strong>Les identifiants uniques sont problématiques :</strong> les UUID sont mauvais pour les tokeniseurs, les ULID sont meilleurs</li>
<li><strong>Plus court n'est pas toujours mieux</strong> en termes d'efficacité des tokens</li>
<li><strong>Les formats de date ont un impact</strong> sur la consommation de tokens</li>
<li><strong>JSON n'est pas le meilleur format</strong> pour les LLM - Markdown est meilleur et utilise moins de tokens</li>
</ul>
<p>Plus de tokens nécessitent plus d'argent et créent des fenêtres de contexte plus grandes, ce qui impacte négativement les temps de réponse et la pertinence des agents IA.</p>
<h4>Sécurité et identifiants</h4>
<p>Les agents IA sont mauvais pour gérer les identifiants. La solution est d'utiliser des serveurs MCP (Model Context Protocol) qui :</p>
<ul>
<li>Gèrent les identifiants de manière sécurisée</li>
<li>Fournissent des outils aux agents IA</li>
<li>Donnent des permissions à portée limitée aux actions MCP</li>
<li>Agissent comme un intermédiaire sécurisé entre le LLM et vos API</li>
</ul>
<h3 id="tout-journaliser">Tout journaliser</h3>
<p>Compte tenu de la complexité et de l'imprévisibilité des interactions des agents IA, une journalisation complète devient essentielle pour le débogage et l'amélioration du système.</p>
<h3 id="la-nouvelle-experience-ax-ai-experience">La nouvelle expérience : AX (AI Experience)</h3>
<p>Fabien a introduit le concept d'AX (AI Experience) aux côtés de l'UX (User Experience) et du DX (Developer Experience). Cela représente une nouvelle dimension de la conception d'API axée sur la façon dont votre API fonctionne avec les agents IA.</p>
<p>Les aspects clés d'une bonne AX incluent :</p>
<ul>
<li>Documentation et exemples à jour (éviter les exemples obsolètes qui pourraient induire le LLM en erreur)</li>
<li>Utilisation de fichiers <code translate="no">llms.txt</code> avec toute la documentation utile pour le LLM au format Markdown</li>
<li>Messages d'erreur clairs et cohérents</li>
<li>Conception d'API préservant l'intention</li>
<li>Utilisation efficace des tokens</li>
</ul>
<p>L'aspect fascinant est que de nombreuses améliorations pour l'AX profitent également au DX traditionnel, rendant les API meilleures à la fois pour les développeurs humains et les agents IA.</p>
<hr>
<h2 id="construire-une-application-decouplee-avec-api-platform-et-vue-js-nathan-de-pachtere">Construire une application découplée avec API Platform et Vue.js (Nathan de Pachtere)</h2>
<p>Nathan de Pachtere a partagé son expérience de construction d'applications découplées en utilisant API Platform pour le backend et Vue.js pour le frontend. Ses réflexions ont couvert les différences entre les approches headless et découplées, les stratégies d'implémentation pratiques et les avantages de l'architecture monorepo.</p>
<h3 id="headless">Headless</h3>
<p>L'architecture headless consiste à créer une API orientée métier que tout le monde peut utiliser indépendamment. Pensez à l'API GitHub - elle est conçue comme un service autonome qui fournit toutes les fonctionnalités nécessaires pour interagir avec les fonctionnalités de GitHub, complètement indépendante de toute implémentation frontend spécifique.</p>
<p>L'objectif est de créer une logique métier et de fournir une API que chacun peut utiliser à ses propres fins.</p>
<h3 id="decouple">Découplé</h3>
<p>L'architecture découplée est similaire mais plus ciblée. Vous fournissez un frontend qui repose spécifiquement sur votre API, créant ainsi ce qu'on appelle un modèle backend-for-frontend. L'API ne semble pas conçue pour une utilisation indépendante en dehors de l'application spécifique - elle est adaptée pour répondre exactement aux besoins du frontend.</p>
<h3 id="pourquoi-choisir-cette-approche">Pourquoi choisir cette approche ?</h3>
<h4>Avantages</h4>
<ul>
<li><strong>Séparation des responsabilités</strong> - Limites claires entre les préoccupations frontend et backend</li>
<li><strong>Gestion d'équipe</strong> - Permet aux équipes spécialisées de travailler indépendamment sur leurs domaines d'expertise</li>
<li><strong>Capitalisation</strong> - Composants et logique réutilisables entre différents projets</li>
<li><strong>Pérennisation</strong> - L'IA pourrait être l'interface utilisée à l'avenir, rendant une approche API-first précieuse</li>
</ul>
<h4>Inconvénients</h4>
<ul>
<li><strong>Complexité</strong> - Configuration plus complexe pour les projets existants qui doivent être refactorisés</li>
</ul>
<h3 id="implementation-headless">Implémentation Headless</h3>
<h4>avec API Platform</h4>
<p>Le processus suit une approche orientée métier :</p>
<ol>
<li><strong>Représenter l'API basée sur les besoins métier</strong> - Concentrez-vous sur ce que l'entreprise fait réellement</li>
<li><strong>Traduire en entités et workflows</strong> - Convertissez les processus métier en implémentations techniques</li>
<li><strong>Écrire uniquement le code nécessaire</strong> - Restez simple initialement</li>
<li><strong>Puis optimiser et refactoriser</strong> - Améliorez les performances et la qualité du code</li>
<li><strong>Itérer</strong> - Améliorez continuellement en fonction des retours</li>
<li><strong>Aller au-delà du CRUD</strong> - Implémentez des opérations métier significatives, pas seulement la manipulation de données de base</li>
</ol>
<h4>Fourniture de clés API</h4>
<p>Pour l'authentification machine-à-machine :</p>
<ul>
<li><strong>Créez une interface simple</strong> pour créer/supprimer des clés configurables avec des permissions spécifiques</li>
<li><strong>Envisagez des fournisseurs d'identité externes</strong> comme Keycloak ou Zitadel pour des cas d'utilisation plus avancés</li>
<li><strong>Principe important :</strong> Ne mélangez pas les utilisateurs humains avec les utilisateurs machines - ils ont des besoins et des exigences de sécurité différents</li>
</ul>
<p>Nathan a souligné l'importance de rendre les tests simples et faciles à implémenter, en les intégrant naturellement dans le workflow de développement plutôt que de les traiter comme une réflexion après coup.</p>
<h4>Stratégie de dépréciation</h4>
<p>Lors de l'évolution de votre API :</p>
<ul>
<li><strong>Dépréciez les points de terminaison, ressources et propriétés</strong> progressivement</li>
<li><strong>Donnez aux consommateurs le temps de s'adapter</strong> aux changements</li>
<li><strong>Communiquez les changements clairement</strong> avant de supprimer une fonctionnalité</li>
</ul>
<p>Cette approche maintient la rétrocompatibilité tout en permettant à l'API d'évoluer.</p>
<h3 id="implementation-decouplee">Implémentation Découplée</h3>
<h4>avec Vue.js</h4>
<p>Nathan a choisi Vue.js pour plusieurs raisons :</p>
<ul>
<li><strong>Indépendant et piloté par la communauté</strong> - Pas contrôlé par une seule entreprise</li>
<li><strong>Composition API (Vue 3)</strong> - Favorise la réutilisabilité du code et une meilleure organisation</li>
<li><strong>Excellente expérience développeur</strong> - Outillage et workflow de développement de qualité</li>
<li><strong>Meilleures performances</strong> - Rapide et efficace (jusqu'au prochain framework, comme il l'a plaisanté)</li>
</ul>
<h4>Connexion API</h4>
<p>Pour connecter le frontend Vue.js au backend API Platform :</p>
<h5>Génération de code</h5>
<p>Utilisez <strong>openapi-ts.dev</strong> pour générer des types TypeScript et des composables à partir de votre spécification OpenAPI. Cela garantit la sécurité des types et réduit le travail manuel.</p>
<p><strong>Principe important :</strong> N'utilisez pas directement les types générés comme objets de base dans votre frontend. Créez vos propres modèles pour maintenir un découplage approprié entre les représentations frontend et backend.</p>
<h5>Client HTTP et gestion d'état</h5>
<ul>
<li><strong>Tanstack Query</strong> - Pour une récupération et un mise en cache efficaces des données</li>
<li><strong>TypeScript partout</strong> - Garantit la sécurité des types dans toute l'application</li>
<li><strong>VS Code pour le développement Vue.js</strong> - Meilleure intégration que les IDE JetBrains pour le travail Vue.js</li>
</ul>
<h5>SDK de haut niveau</h5>
<p>Fournissez des SDK de haut niveau pour faciliter l'intégration API, rendant plus facile pour les développeurs de travailler avec votre API.</p>
<h3 id="gestion-des-versions">Gestion des versions</h3>
<h4>Polyrepo vs Monorepo</h4>
<p><strong>Monorepo ne signifie pas monolithe</strong> - c'est une distinction cruciale :</p>
<ul>
<li><strong>Monorepo</strong> = Plusieurs projets séparés dans un seul dépôt</li>
<li><strong>Monolithe</strong> = Application unique gérant tout</li>
</ul>
<h4>Avantages du Monorepo</h4>
<p>L'objectif est de simplifier le workflow :</p>
<ul>
<li><strong>Façon unifiée de penser le code</strong> - Modèles cohérents entre les projets</li>
<li><strong>Cohérence</strong> - Outillage et configurations partagés</li>
<li><strong>Facilite le partage</strong> - Réutilisation facile du code et des composants</li>
<li><strong>Travail d'équipe plus efficace</strong> - Collaboration et gestion des dépendances simplifiées</li>
</ul>
<h4>Outillage</h4>
<p>Nathan a recommandé <strong>moonrepo.dev</strong> comme outil open source pour gérer les monorepos. Vous pouvez trouver plus d'informations sur <strong>monorepo.tools</strong>.</p>
<h4>Exemple concret</h4>
<p>Nathan a partagé leur expérience avec des avantages considérables :</p>
<ul>
<li><strong>Généralisation du code</strong> - Modèles et composants réutilisables</li>
<li><strong>Partage de fonctionnalités</strong> - Bibliothèques communes entre les projets</li>
<li><strong>Organisation par technologie</strong> - Les projets utilisent des bibliothèques partagées organisées par stack technologique</li>
</ul>
<p>Vous pouvez voir un exemple pratique de cette approche dans le projet <a href="https://github.com/alpsify/lychen" rel="noopener noreferrer">Lychen</a> (<a href="https://lychen.fr/" rel="noopener noreferrer">lychen.fr</a>), qui démontre un monorepo bien structuré avec une séparation claire entre le backend, le frontend et les outils partagés.</p>
<p>Le projet Lychen montre comment organiser un monorepo avec :</p>
<ul>
<li><strong>Backend</strong> (API Platform/PHP)</li>
<li><strong>Frontend</strong> (Vue.js/TypeScript)</li>
<li><strong>Outillage partagé</strong> (Moonrepo, Docker, outils de test)</li>
<li><strong>Limites technologiques claires</strong> tout en maintenant un partage de code efficace</li>
</ul>
<hr>
<h2 id="jean-beru-presente-fun-with-flags-hubert-lenoir">Jean-Beru présente : Fun with flags (Hubert Lenoir)</h2>
<p><strong>Les slides de cette conférence sont disponibles : <a href="https://jean-beru.github.io/2025_09_apiplatformcon_fun_with_flags" rel="noopener noreferrer">https://jean-beru.github.io/2025_09_apiplatformcon_fun_with_flags</a></strong></p>
<p>Jean-Beru (Hubert Lenoir) a présenté le monde fascinant des feature flags et leur implémentation pratique. Comme l'a dit Oncle Ben dans Spider-Man : « Un grand pouvoir implique de grandes responsabilités » - et les feature flags sont effectivement un outil puissant qui nécessite une réflexion approfondie.</p>
<h3 id="que-sont-les-feature-flags">Que sont les Feature Flags ?</h3>
<p>Les feature flags (également appelés feature flipping ou feature toggles) sont une technique de développement logiciel qui permet d'activer ou de désactiver des fonctionnalités sans déployer de nouveau code. Ils agissent comme des instructions conditionnelles dans votre code qui déterminent si une fonctionnalité particulière doit être activée ou désactivée pour des utilisateurs, environnements ou conditions spécifiques.</p>
<h3 id="types-de-feature-flags">Types de Feature Flags</h3>
<h4>Flags de Release</h4>
<p>Principalement utilisés pour tester de nouvelles fonctionnalités en production en toute sécurité.</p>
<ul>
<li><strong>Développement continu</strong> - Même si une fonctionnalité n'est pas prête, vous pouvez continuer à développer et déployer (non prêt = désactivé)</li>
<li><strong>Déploiement sécurisé</strong> - Déployez le code avec les fonctionnalités désactivées, puis activez-les quand elles sont prêtes</li>
<li><strong>Déploiement progressif</strong> - Activez les fonctionnalités pour de petits groupes avant le déploiement complet</li>
</ul>
<h4>Flags d'Expérimentation</h4>
<p>Utilisés pour comparer différentes versions de votre application.</p>
<ul>
<li><strong>Tests A/B</strong> - Comparez différentes implémentations ou expériences utilisateur</li>
<li><strong>Doivent être accompagnés de métriques</strong> - Suivez les performances et le comportement des utilisateurs</li>
<li><strong>Activation partielle</strong> - Activez pour des pourcentages spécifiques (par exemple, 20 % des utilisateurs)</li>
</ul>
<p>Cette approche permet des décisions basées sur les données concernant les fonctionnalités ou implémentations qui fonctionnent le mieux pour vos utilisateurs.</p>
<h4>Flags de Permission</h4>
<p>Contrôlent l'accès aux fonctionnalités en fonction des permissions des utilisateurs ou des niveaux d'abonnement.</p>
<ul>
<li><strong>Blocage d'accès basé sur les permissions</strong> - Par exemple, fonctionnalités payantes disponibles uniquement pour les abonnés premium</li>
<li><strong>Accès aux fonctionnalités basé sur les rôles</strong> - Différentes fonctionnalités pour différents types d'utilisateurs</li>
<li><strong>Niveaux d'abonnement</strong> - Activez des fonctionnalités avancées pour les clients de niveaux supérieurs</li>
</ul>
<h4>Flags Opérationnels</h4>
<p>Ceinture de sécurité et fonction kill switch.</p>
<ul>
<li><strong>Permettent de désactiver les fonctionnalités lourdes</strong> - Désactivez rapidement les fonctionnalités gourmandes en ressources en période de forte charge</li>
<li><strong>Réponse d'urgence</strong> - Désactivez les fonctionnalités problématiques sans déploiement</li>
<li><strong>Gestion des performances</strong> - Contrôlez la charge système en activant/désactivant les opérations coûteuses</li>
</ul>
<p>Pour plus d'informations détaillées sur les modèles de feature flags, Martin Fowler a un excellent article sur <a href="https://martinfowler.com/articles/feature-toggles.html" rel="noopener noreferrer">https://martinfowler.com/articles/feature-toggles.html</a>.</p>
<h3 id="implementation">Implémentation</h3>
<p>Il existe de nombreux fournisseurs de feature flags sur le marché, mais l'implémentation n'a pas nécessairement besoin d'utiliser le composant Security de Symfony.</p>
<h4>Pourquoi pas le composant Security ?</h4>
<ul>
<li><strong>Restreint au contexte utilisateur actuel</strong> - Limitations lorsque les flags doivent fonctionner dans différents contextes utilisateur</li>
<li><strong>Problèmes de timing d'authentification</strong> - L'authentification a lieu après le routage, ce qui peut entraîner des codes d'erreur interdits indésirables</li>
<li><strong>Besoins de flexibilité</strong> - Les implémentations personnalisées peuvent mieux s'intégrer avec des fournisseurs existants comme Unleash</li>
</ul>
<h4>Exigences pour une bonne implémentation</h4>
<p>Un système de feature flags solide devrait fournir :</p>
<ul>
<li><strong>Simplicité</strong> - Facile à implémenter et à utiliser</li>
<li><strong>Débogage intégré</strong> - Visibilité claire sur les flags actifs</li>
<li><strong>Sources multiples</strong> - Capacité de basculer entre différents fournisseurs de flags</li>
<li><strong>Support de divers fournisseurs</strong> - Fonctionne avec différents services de feature flags</li>
<li><strong>Cacheable</strong> - Optimisation des performances via des mécanismes de cache</li>
</ul>
<h4>Intégration Symfony</h4>
<p>Il existe un composant FeatureFlag en cours de développement pour Symfony (PR #53213). Ce composant vise à fournir un support natif des feature flags dans l'écosystème Symfony.</p>
<h3 id="avec-api-platform">Avec API Platform</h3>
<p>Les feature flags peuvent être facilement testés via un bundle séparé : <a href="https://github.com/ajgarlag/feature-flag-bundle" rel="noopener noreferrer">ajgarlag/feature-flag-bundle</a>.</p>
<h4>Étapes d'implémentation</h4>
<ol>
<li><strong>Décoration du provider API Platform</strong> - Utilisez le pattern decorator pour envelopper les providers existants avec la logique de feature flag</li>
<li><strong>Utilisez l'interface du composant FeatureFlag WIP</strong> - Intégrez-vous avec le futur composant FeatureFlag de Symfony</li>
</ol>
<h4>Exemple avec le fournisseur GitLab</h4>
<p>GitLab fournit un service de feature flags qui utilise Unleash en arrière-plan. Cette intégration vous permet de :</p>
<ul>
<li><strong>Gérer les flags via l'interface GitLab</strong> - Interface familière pour les équipes utilisant déjà GitLab</li>
<li><strong>Tirer parti des capacités d'Unleash</strong> - Moteur de feature flags puissant sous le capot</li>
<li><strong>Intégrer avec les pipelines CI/CD</strong> - Gestion automatique des flags dans le cadre du processus de déploiement</li>
</ul>
<h4>Intégration du Profiler</h4>
<p>L'implémentation inclut l'intégration du Symfony Profiler, fournissant :</p>
<ul>
<li><strong>Informations de débogage</strong> - Voyez quels flags sont actifs pendant le développement</li>
<li><strong>Informations de performance</strong> - Surveillez l'impact des vérifications de feature flags</li>
<li><strong>Workflow de développement</strong> - Test et débogage faciles du comportement des flags</li>
</ul>
<h3 id="avantages">Avantages</h3>
<p>L'implémentation des feature flags apporte plusieurs avantages significatifs :</p>
<h4>Déploiement continu</h4>
<ul>
<li><strong>Découpler le déploiement de la release</strong> - Déployez le code en toute sécurité avec les fonctionnalités désactivées</li>
<li><strong>Réduire le risque de déploiement</strong> - Moins de chances de casser la production</li>
<li><strong>Cycles d'itération plus rapides</strong> - Déploiements plus fréquents et plus petits</li>
</ul>
<h4>Tests progressifs</h4>
<ul>
<li><strong>Capacités de tests A/B</strong> - Comparez différentes approches avec de vrais utilisateurs</li>
<li><strong>Déploiements progressifs</strong> - Commencez avec de petits groupes d'utilisateurs et étendez</li>
<li><strong>Décisions basées sur les données</strong> - Faites des choix basés sur des métriques d'utilisation réelles</li>
</ul>
<h4>Désactivation rapide</h4>
<ul>
<li><strong>Pas de redéploiement nécessaire</strong> - Désactivez instantanément les fonctionnalités problématiques</li>
<li><strong>Réponse d'urgence</strong> - Réaction rapide aux problèmes de production</li>
<li><strong>Continuité d'activité</strong> - Gardez les fonctionnalités principales opérationnelles pendant la résolution des problèmes</li>
</ul>
<h4>Séparer le code de la release de fonctionnalité</h4>
<ul>
<li><strong>Calendriers indépendants</strong> - Les plannings de développement et de release métier peuvent différer</li>
<li><strong>Coordination marketing</strong> - Alignez les releases de fonctionnalités avec les campagnes marketing</li>
<li><strong>Gestion des parties prenantes</strong> - Donnez aux équipes métier le contrôle sur le moment où les fonctionnalités sont mises en ligne</li>
</ul>
<p>Les feature flags représentent un changement de paradigme puissant dans notre façon de concevoir le déploiement logiciel et la gestion des releases, permettant des pratiques de développement plus flexibles, plus sûres et basées sur les données.</p>
<hr>
<h2 id="pie-la-prochaine-grande-chose-alexandre-daubois">PIE : La prochaine grande chose (Alexandre Daubois)</h2>
<h3 id="les-extensions">Les extensions ?</h3>
<p>Les extensions sont comme des paquets Composer, mais écrites en C, C++, Rust, et maintenant Go.
Elles vivent à un niveau plus bas, ce qui les rend beaucoup plus rapides que le code PHP pur.</p>
<p>Des frameworks comme <strong>Phalcon</strong> sont eux-mêmes distribués comme des extensions.</p>
<h3 id="installation-d-une-bibliotheque-tierce">Installation d'une bibliothèque tierce</h3>
<p>Traditionnellement, installer une extension est beaucoup plus pénible qu'un <code translate="no">composer install</code>.
Cela implique généralement :</p>
<ol>
<li>Télécharger le code source.</li>
<li>Le compiler avec <code translate="no">phpize</code> et <code translate="no">make</code>.</li>
<li>Ajouter une ligne dans <code translate="no">php.ini</code> pour l'activer.</li>
<li>Redémarrer PHP-FPM ou Apache pour la charger.</li>
</ol>
<p>Ce workflow rend les extensions plus difficiles à distribuer et à standardiser par rapport aux paquets Composer.</p>
<h3 id="pecl">PECL</h3>
<ul>
<li>Lourd et obsolète.</li>
<li>Lent à installer.</li>
<li>Manque de sécurité appropriée (pas de signature de paquets).</li>
<li>Pas officiellement soutenu par PHP, et certains dans la communauté veulent le supprimer progressivement.</li>
</ul>
<h3 id="docker-php-extension-installer">docker-php-extension-installer</h3>
<p>Un projet communautaire largement utilisé qui simplifie l'installation des extensions dans les images Docker.
Au lieu d'écrire des commandes complexes <code translate="no">apt-get</code> + <code translate="no">phpize</code> + <code translate="no">make</code>, vous ajoutez simplement :</p>
<pre><code class="language-dockerfile hljs dockerfile" translate="no"><span class="hljs-keyword">COPY</span><span class="bash"> --from=ghcr.io/mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/<span class="hljs-built_in">local</span>/bin/</span>

<span class="hljs-keyword">RUN</span><span class="bash"> install-php-extensions xdebug redis</span></code></pre>
<p>C'est excellent, mais pas encore parfait — cela reste spécifique à Docker et ne s'intègre pas avec Composer ou Packagist.</p>
<h3 id="projet-pour-remplacer-pecl">Projet pour remplacer PECL</h3>
<p>Le dépôt <strong>pie-design</strong> définit les bases de <strong>PIE</strong>, une nouvelle façon d'installer les extensions aussi facilement que les paquets PHP.</p>
<ul>
<li>Lancé en mars 2024.</li>
<li>Version 1 publiée en juin 2025.</li>
<li>PIE est distribué sous forme d'un seul fichier <code translate="no">phar</code> : il suffit de le télécharger et de l'utiliser.</li>
<li>Toutes les métadonnées des extensions sont stockées dans Packagist.</li>
</ul>
<h4>Options de commande</h4>
<ul>
<li><code translate="no">pie install ext-xdebug</code> → installe une extension et met à jour <code translate="no">php.ini</code>.</li>
<li><code translate="no">pie uninstall ext-redis</code> → supprime une extension.</li>
<li><code translate="no">pie update</code> → met à jour vers la dernière version disponible.</li>
<li><code translate="no">pie search redis</code> → recherche des extensions dans Packagist.</li>
<li>Exécuter <code translate="no">pie</code> sans arguments lit les extensions depuis <code translate="no">composer.json</code> et les installe.</li>
</ul>
<p>Autres fonctionnalités :</p>
<ul>
<li>Ajout de dépôts via Composer, VCS ou chemins locaux.</li>
<li>Mise à jour automatique de <code translate="no">php.ini</code>.</li>
<li>Support de <code translate="no">GH_TOKEN</code> pour installer depuis des dépôts privés.</li>
<li>Restrictions de compatibilité OS.</li>
<li>Intégration Symfony CLI : <code translate="no">symfony pie install</code>.</li>
</ul>
<h3 id="l-avenir-des-extensions">L'avenir des extensions</h3>
<p>PIE est le remplacement théorique de PECL.
Un vote RFC a eu lieu, clos le <strong>20 septembre 2025</strong>.
Presque tout le monde a voté <em>oui</em>, ce qui signifie que PIE est désormais le successeur officiel de PECL.</p>
<hr>
<h2 id="rendez-vos-developpeurs-heureux-en-normalisant-vos-erreurs-api-clement-herreman">Rendez vos développeurs heureux en normalisant vos erreurs API (Clément Herreman)</h2>
<p>Les erreurs ne sont pas seulement des bugs. Ce sont des opportunités de donner de l'autonomie aux utilisateurs grâce à des retours clairs.</p>
<h3 id="qu-est-ce-qu-une-erreur">Qu'est-ce qu'une erreur ?</h3>
<p>Une erreur est tout comportement — intentionnel ou non — qui empêche l'utilisateur de terminer sa tâche.</p>
<h3 id="pourquoi-normaliser-les-erreurs">Pourquoi normaliser les erreurs ?</h3>
<ol>
<li>
<p>Pour réagir correctement à un problème précis :</p>
<ul>
<li>Réessayer un token.</li>
<li>Gérer les défaillances de systèmes distribués.</li>
<li>Corriger des problèmes de configuration.</li>
</ul>
</li>
<li>
<p>Pour présenter les erreurs de manière cohérente :</p>
<ul>
<li>Messages clairs et compréhensibles pour les utilisateurs finaux.</li>
<li>Identification précise pour faciliter le support.</li>
<li>Garder certains détails vagues pour des raisons de sécurité.</li>
</ul>
</li>
</ol>
<h3 id="comment">Comment ?</h3>
<p>Les erreurs peuvent être classées en trois catégories :</p>
<ol>
<li>Les erreurs qui appartiennent à votre domaine : vous les possédez, donc enrichissez-les avec du contexte.</li>
<li>Les erreurs qui n'appartiennent pas à votre domaine mais qui se produisent quand même : enveloppez-les avec un code et enrichissez-les.</li>
<li>Erreurs rares/imprévues : conservez la sortie JSON par défaut.</li>
</ol>
<h4>RFC 7807 : Détails du problème pour les API HTTP</h4>
<p>Cette RFC définit une structure JSON standard pour les erreurs :</p>
<ul>
<li><code translate="no">type</code> : code unique lisible par machine.</li>
<li><code translate="no">title</code> : résumé court et lisible par un humain.</li>
<li><code translate="no">detail</code> : explication contextuelle de cette erreur particulière.</li>
<li><code translate="no">instance</code> : URL du catalogue d'erreurs.</li>
<li><code translate="no">...</code> : tous les champs personnalisés que vous souhaitez.</li>
</ul>
<h5>Exemple de réponse HTTP</h5>
<pre><code class="language-http hljs http" translate="no">HTTP/1.1 <span class="hljs-number">401</span> Unauthorized
<span class="hljs-attribute">Content-Type</span>: application/problem+json

{
  "type": "https://example.com/errors/authentication_failed",
  "title": "Authentication failed",
  "detail": "Your token has expired. Please request a new one.",
  "instance": "/login"
}</code></pre>
<h4>API Platform</h4>
<p>API Platform fournit une classe prête à l'emploi <code translate="no">ApiPlatform\Problem\Error</code> pour implémenter la RFC 7807.</p>
<h4>Organisation des erreurs</h4>
<ul>
<li>Gardez uniquement les exceptions métier dans la couche domaine.</li>
<li>Enveloppez les erreurs d'infrastructure avant de les envoyer au client.</li>
</ul>
<h4>Documentation des erreurs</h4>
<p>Les erreurs peuvent être déclarées comme des attributs sur les opérations, les rendant explicites dans la documentation API.</p>
<h4>Améliorations : RFC 9457</h4>
<p>La RFC 9457 est essentiellement la même que la RFC 7807, avec quelques ajouts :</p>
<ul>
<li>Un registre des erreurs via <code translate="no">schema.org</code>.</li>
<li>Un mécanisme pour retourner plusieurs erreurs à la fois (bien que fortement déconseillé).</li>
</ul>
<p>Comme l'a souligné Clément : la RFC 9457 n'apporte pas beaucoup de valeur pratique, et certaines de ses suggestions sont même déconseillées dans la spécification.</p>
<hr>
<h2 id="symfony-et-l-injection-de-dependances-du-passe-au-futur-imen-ezzine">Symfony et l'injection de dépendances : Du passé au futur (Imen Ezzine)</h2>
<p>L'injection de dépendances (DI) est le « D » de SOLID, et elle est une pierre angulaire de la conception de Symfony depuis près de deux décennies.
Cette conférence a exploré son histoire, son évolution et ce qui nous attend.</p>
<h3 id="les-debuts">Les débuts</h3>
<p><strong>2007 – Symfony 1</strong></p>
<ul>
<li>Les services étaient instanciés directement, souvent via <code translate="no">sfContext()</code> (un singleton).</li>
<li>Difficile à tester, rigide, fortement couplé.</li>
<li>Pas de véritable conteneur.</li>
</ul>
<h3 id="symfony-2-et-le-changement-de-paradigme">Symfony 2 et le changement de paradigme</h3>
<p><strong>2011 – Symfony 2</strong></p>
<ul>
<li>Introduction d'un conteneur central.</li>
<li>Services configurés via YAML et paramètres.</li>
<li>Dépendances injectées comme arguments du constructeur.</li>
<li>L'autowiring introduit dans <strong>Symfony 2.8</strong>.</li>
</ul>
<p><strong>2015 – API Platform v1</strong></p>
<ul>
<li>Forte dépendance à l'autowiring (alors expérimental).</li>
</ul>
<p><strong>2016 – API Platform v2</strong></p>
<ul>
<li>La magie des annotations <code translate="no">@ApiResource</code> propulsée par le composant DI.</li>
<li>Les data persisters et providers devaient être tagués manuellement.</li>
</ul>
<p><strong>2017 – Symfony 3.3 / API Platform 2.2</strong></p>
<ul>
<li>Autowiring + autoconfigure.</li>
<li>Le tagging manuel a été presque éliminé (providers/persisters automatiquement câblés).</li>
<li>Symfony 3.4 : services privés par défaut.</li>
</ul>
<h3 id="symfony-5-a-symfony-7">Symfony 5 à Symfony 7</h3>
<p><strong>2021 – Symfony 5.3</strong></p>
<ul>
<li>DI propulsée par les attributs → beaucoup moins de YAML.</li>
<li>Attribut <code translate="no">#[When]</code> pour les services conditionnels.</li>
</ul>
<p><strong>Symfony 6.0 – 6.3</strong></p>
<ul>
<li>Nouveaux attributs pour les cas particuliers.</li>
<li>Attribut <code translate="no">#[Autowire]</code> pour l'injection de service précise.</li>
<li>Support des variables d'environnement et paramètres via attributs.</li>
<li><code translate="no">#[AsAlias]</code> pour créer des alias de services.</li>
</ul>
<p><strong>2022 – API Platform 3.0</strong></p>
<ul>
<li>Nouveaux state processors et providers remplacent l'ancien modèle persister/provider.</li>
</ul>
<p><strong>2023 – Symfony 7</strong></p>
<ul>
<li><code translate="no">#[AutoconfigureTag]</code> → tagging automatique (utilisé dans les filtres API Platform).</li>
<li><code translate="no">TaggedIterator</code> → injection de plusieurs services tagués.</li>
<li><code translate="no">AutowireIterator</code> → autowire de toutes les classes implémentant une interface.</li>
</ul>
<p><strong>Symfony 7.1 – 7.3</strong></p>
<ul>
<li><code translate="no">#[AutowireMethodOf]</code> pour autowirer une seule méthode.</li>
<li><code translate="no">#[WhenNot]</code> pour les services conditionnels.</li>
<li>Paramètre <code translate="no">when</code> dans <code translate="no">#[AsAlias]</code>.</li>
</ul>
<h3 id="points-a-retenir">Points à retenir</h3>
<p>En 20 ans, la DI dans Symfony a évolué de :</p>
<ul>
<li>L'instanciation manuelle →</li>
<li>La configuration manuelle →</li>
<li>La configuration automatique via les <strong>attributs</strong>.</li>
</ul>
<p>Ce parcours a rendu les projets Symfony <strong>plus testables, maintenables et conviviaux pour les développeurs</strong> tout en réduisant le code répétitif.</p>
<h2 id="credits">Crédits</h2>
<p>Image de couverture par <a href="https://ncls.tv/" rel="noopener noreferrer">Nicolas Detrez</a></p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://ktherage.github.io/fr/blog/symfony-and-doctrine-migrations-validation-in-ci/</id>
    <title>Symfony &amp; Doctrine Migrations : Validation en CI</title>
    <published>2024-09-05T00:00:00+00:00</published>
    <link href="https://ktherage.github.io/fr/blog/symfony-and-doctrine-migrations-validation-in-ci/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<p>J'ai eu l'opportunité de travailler sur un projet avec une équipe relativement novice en matière de migrations Doctrine. Pour les aider à s'y habituer et écarter la possibilité d'avoir des pull (ou merge) requests avec des modifications d'entités Doctrine sans migration générée.</p>
<p>Voici comment j'ai procédé. J'espère que cela vous plaira !</p>
<h2 id="avertissement">Avertissement</h2>
<p>Nous sommes le 10 juillet 2025 et cet article est obsolète en raison du merge de la Pull Request <a href="https://github.com/doctrine/migrations/issues/1406" rel="noopener noreferrer">https://github.com/doctrine/migrations/issues/1406</a> ; désormais, l'exécution de <code translate="no">bin/console doctrine:migrations:up-to-date</code> prend en compte la configuration <code translate="no">schema_filter</code>.</p>
<h2 id="comment-fonctionnent-les-migrations-doctrine">Comment fonctionnent les migrations Doctrine</h2>
<p>Lors de la génération de la migration, Doctrine calcule un delta entre son mapping et le schéma actuel de la base de données. Avec ce delta en mémoire, il génère un <strong>fichier de migration</strong> avec deux méthodes principales :</p>
<ul>
<li><code translate="no">up</code> applique les commandes SQL pour combler l'écart entre le schéma actuel de la base de données et son mapping. Utilisé pour déployer les modifications du schéma de votre base de données.</li>
<li><code translate="no">down</code> permet d'annuler la migration avec les commandes SQL nécessaires pour « annuler » les modifications effectuées dans la méthode up. Utilisé pour revenir en arrière sur les modifications du schéma de votre base de données.</li>
</ul>
<h2 id="l-astuce-magique">L'astuce magique</h2>
<p>Il n'existe actuellement aucun moyen simple de vérifier si une migration n'a pas été générée. Si ce code était fusionné, le schéma de la base de données pourrait ne plus être synchronisé avec le mapping des entités, entraînant ainsi une erreur serveur.</p>
<p>Les mots-clés dans la description ci-dessus sont <strong>fichiers de migration</strong>. J'utilise le fait que l'exécution de la commande <code translate="no">bin/console doctrine:migration:diff</code> génère un nouveau fichier et échoue s'il n'y a aucun changement à appliquer.</p>
<p>Connaître la liste des fichiers existants avant l'exécution de cette commande, puis l'exécuter, permet de détecter qu'il y a des modifications qui n'ont pas été commitées dans un <strong>fichier de migration</strong> dans cette pull (ou merge) request.</p>
<h2 id="etapes-a-suivre">Étapes à suivre</h2>
<ol>
<li>Créez votre base de données</li>
<li>Exécutez vos migrations existantes</li>
<li>Puis exécutez l'étape de vérification des modifications manquantes (voir ci-dessous)</li>
</ol>
<h2 id="avantages">Avantages</h2>
<ol>
<li>Tester que vos migrations ne échouent pas</li>
<li>Assurer la cohérence du schéma de base de données avec le mapping de Doctrine</li>
</ol>
<h2 id="vous-voulez-l-extrait-de-code-n-est-ce-pas">Vous voulez l'extrait de code, n'est-ce pas ?</h2>
<p>Voici le code bash :</p>
<pre><code class="language-bash hljs bash" translate="no"><span class="hljs-meta">#!/bin/bash
</span>
<span class="hljs-built_in">set</span> -e
<span class="hljs-built_in">set</span> -o pipefail

<span class="hljs-comment"># run doctrine migration diff to check if there is a new migration file generated and check last exit code</span>
<span class="hljs-keyword">if</span> [[ -z $(bin/console doctrine:migrations:diff -n --quiet) ]]; <span class="hljs-keyword">then</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"Error ! bin/console doctrine:migration:diff found a new migration which must not be the case."</span>;
    <span class="hljs-comment"># cat last file (should be the newly generated one)</span>
    cat $(ls -Art migrations/*.php | tail -n 1);
    <span class="hljs-comment"># remove that file (just in case to comply with my paranoïac side)</span>
    rm -f $(ls -Art migrations/*.php | tail -n 1);
    <span class="hljs-built_in">exit</span> 1;
<span class="hljs-keyword">else</span>
    <span class="hljs-built_in">exit</span> 0;
<span class="hljs-keyword">fi</span>
</code></pre>
<p>Et voilà ! Vous pouvez désormais vous assurer que chaque pull (ou merge) request contient des migrations fonctionnelles, sans modification en attente laissée de côté !</p>
<h2 id="ok-mais-pourquoi-ne-pas-utiliser-bin-console-doctrine-schema-validate">Ok, mais pourquoi ne pas utiliser <code translate="no">bin/console doctrine:schema:validate</code> ?</h2>
<p>La raison est que le projet sur lequel nous travaillions utilisait la configuration <code translate="no">schema_filter</code> de Doctrine pour filtrer certaines tables dont nous ne voulions pas nous occuper (contrainte liée au projet).</p>
<p>Le problème avec <code translate="no">bin/console doctrine:schema:validate</code> est qu'il ne prenait pas en compte cette configuration, et signalait donc des modifications (tentant de supprimer toutes les tables normalement filtrées) sans rapport avec ce que nous voulions.</p>
<p>Un collègue m'a dit qu'il s'agit d'un problème connu qui pourrait être corrigé prochainement (<a href="https://github.com/doctrine/migrations/issues/1406" rel="noopener noreferrer">https://github.com/doctrine/migrations/issues/1406</a>).</p>
<p>Merci d'avoir lu cet article et n'hésitez pas à laisser vos commentaires si vous avez des questions !</p>]]>
    </content>
  </entry>
</feed>
