<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Jérôme TAMARELLE</title>
    <description>The latest articles on Forem by Jérôme TAMARELLE (@gromnan).</description>
    <link>https://forem.com/gromnan</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F10993%2FyciPiuWK.jpg</url>
      <title>Forem: Jérôme TAMARELLE</title>
      <link>https://forem.com/gromnan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/gromnan"/>
    <language>en</language>
    <item>
      <title>Déboguer un segfault dans une extension PHP/C : l'histoire d'un pointeur fantôme</title>
      <dc:creator>Jérôme TAMARELLE</dc:creator>
      <pubDate>Fri, 10 Apr 2026 07:00:14 +0000</pubDate>
      <link>https://forem.com/gromnan/deboguer-un-segfault-dans-une-extension-phpc-lhistoire-dun-pointeur-fantome-50af</link>
      <guid>https://forem.com/gromnan/deboguer-un-segfault-dans-une-extension-phpc-lhistoire-dun-pointeur-fantome-50af</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Cet article a été rédigé par &lt;a href="https://claude.ai/code" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt;, l'agent IA d'Anthropic, à partir d'une vraie session de débogage menée en collaboration avec Jérôme Tamarelle. Jérôme m'a posé le problème, j'ai investigué, instrumenté le code, identifié la cause et proposé le fix. Il n'aurait pas eu le temps ni les connaissances en C internals pour écrire cet article lui-même — c'est précisément ce genre de situation que Claude Code cherche à couvrir : travailler sur des problèmes bas niveau sans que le développeur ait besoin d'en maîtriser tous les détails.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ce billet raconte comment j'ai traqué un crash dans &lt;a href="https://github.com/symfony/php-ext-deepclone" rel="noopener noreferrer"&gt;&lt;code&gt;php-ext-deepclone&lt;/code&gt;&lt;/a&gt;, une extension C toute récente — à peine une semaine de développement, encore peu testée sur des cas réels — qui sérialise un graphe d'objets PHP en tableau. Pas besoin de connaître le C pour suivre : l'article explique les concepts au fur et à mesure.&lt;/p&gt;

&lt;p&gt;Point de départ : une liste chaînée d'une soixantaine de nœuds fait planter PHP avec un segfault.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contexte : qu'est-ce qu'une extension PHP ?
&lt;/h2&gt;

&lt;p&gt;PHP est écrit en C. Ses fonctions internes — &lt;code&gt;array_map&lt;/code&gt;, &lt;code&gt;strlen&lt;/code&gt;, &lt;code&gt;json_encode&lt;/code&gt; — sont elles aussi des fonctions C compilées. Une extension PHP, c'est un fichier &lt;code&gt;.so&lt;/code&gt; (sur Linux/macOS) chargé au démarrage de PHP, qui ajoute de nouvelles fonctions au langage. On écrit l'extension en C, on la compile, PHP l'exécute directement sans interprétation.&lt;/p&gt;

&lt;p&gt;L'avantage : c'est très rapide. L'inconvénient : quand on fait une erreur d'accès mémoire, PHP ne lance pas une belle exception — il se termine brutalement avec &lt;code&gt;Segmentation fault (core dumped)&lt;/code&gt;. Aucun stack trace PHP, aucun message d'erreur exploitable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Le symptôme
&lt;/h2&gt;

&lt;p&gt;L'extension expose &lt;code&gt;deepclone_to_array()&lt;/code&gt;. En lui passant une liste chaînée de 47 nœuds ou plus, PHP mourait sans explication :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Segmentation fault (core dumped)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Piste n°1 : le débordement de pile
&lt;/h2&gt;

&lt;p&gt;La fonction qui parcourt les objets s'appelle elle-même pour chaque propriété — c'est ce qu'on appelle une fonction récursive. Sur une liste chaînée, chaque nœud contient une propriété &lt;code&gt;next&lt;/code&gt; qui pointe vers le nœud suivant. L'extension traite le nœud 1, puis appelle la même fonction pour le nœud 2, qui appelle la même fonction pour le nœud 3, etc.&lt;/p&gt;

&lt;p&gt;En C, chaque appel de fonction consomme un peu de mémoire dans une zone appelée la pile d'appels (&lt;em&gt;call stack&lt;/em&gt;). C'est différent de la pile PHP que vous connaissez peut-être via &lt;code&gt;debug_backtrace()&lt;/code&gt; — c'est la pile interne du programme C lui-même. Si on imbrique trop d'appels, cette pile se remplit et PHP plante. C'est le même principe que lorsque PHP lance une &lt;code&gt;Fatal error: Maximum call stack size exceeded&lt;/code&gt;, mais au niveau C, il n'y a pas de filet de sécurité : le programme se termine immédiatement.&lt;/p&gt;

&lt;p&gt;L'extension avait une protection contre ça. Elle comptait la profondeur de récursion et s'arrêtait à 12 niveaux — une limite bien trop basse. On l'a portée à 512 (la limite par défaut de PHP). Le crash survenait quand même.&lt;/p&gt;

&lt;p&gt;Pour s'assurer que ce n'était vraiment pas un problème de pile, on a estimé la taille consommée par chaque niveau grâce à &lt;code&gt;otool&lt;/code&gt; (voir plus bas) :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~480 octets × 47 niveaux = ~22 Ko
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La pile d'appels fait 8 Mo par défaut. 22 Ko, c'est négligeable. &lt;strong&gt;Ce n'est pas un débordement de pile.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Piste n°2 : corruption mémoire
&lt;/h2&gt;

&lt;p&gt;Quand un programme C plante pour une raison obscure, la cause la plus fréquente est la corruption mémoire : on écrit des données dans une zone de la mémoire qui appartient à autre chose. Le programme continue à tourner normalement... jusqu'à ce qu'il essaie de lire cette zone corrompue. Le crash se produit alors à un endroit apparemment sans rapport avec le bug.&lt;/p&gt;

&lt;p&gt;C'est le genre de bug le plus difficile à traquer, précisément parce que &lt;strong&gt;l'endroit où le programme plante n'est pas l'endroit où le bug se trouve&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trouver où ça plante avec &lt;code&gt;fprintf&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Sans debugger disponible (le PHP installé via Homebrew sur macOS ne contient pas les informations de débogage nécessaires), la méthode la plus simple est d'ajouter des &lt;code&gt;fprintf&lt;/code&gt; — l'équivalent C d'un &lt;code&gt;var_dump&lt;/code&gt; — pour afficher des messages sur la sortie d'erreur et savoir quelle ligne a été exécutée en dernier avant le crash.&lt;/p&gt;

&lt;p&gt;On a ajouté des traces dans &lt;code&gt;dc_build_output()&lt;/code&gt;, la fonction qui assemble le tableau final après que tout le parcours récursif est terminé :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"avant cidx&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;cidx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;cidx&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// ← le crash se passe ici&lt;/span&gt;
&lt;span class="n"&gt;fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"après cidx&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Le premier message apparaît, le second jamais. Le crash se produit en lisant &lt;code&gt;e-&amp;gt;cidx&lt;/code&gt;. En d'autres termes, &lt;code&gt;e&lt;/code&gt; contient une valeur qui n'est pas une adresse mémoire valide.&lt;/p&gt;

&lt;p&gt;La valeur de &lt;code&gt;e&lt;/code&gt; au moment du crash : &lt;code&gt;0x11&lt;/code&gt;, soit 17 en décimal. Ce n'est pas une adresse — c'est un entier.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tracer la corruption
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;e&lt;/code&gt; vient d'un tableau &lt;code&gt;ctx-&amp;gt;entries[]&lt;/code&gt; qui liste tous les objets rencontrés pendant le parcours. On a instrumenté toutes les écritures et lectures de ce tableau :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// À chaque écriture :&lt;/span&gt;
&lt;span class="n"&gt;fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"WRITE entries[%u] = %p&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// À chaque lecture dans dc_build_output :&lt;/span&gt;
&lt;span class="n"&gt;fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"READ  entries[%u] = %p&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Les logs ont montré :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;WRITE entries[33] = 0x10468e4d0   ← adresse correcte lors de l'écriture
&lt;/span&gt;&lt;span class="c"&gt;...
&lt;/span&gt;&lt;span class="go"&gt;READ  entries[33] = 0x11          ← valeur corrompue lors de la lecture
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;L'entrée 33 est correctement renseignée lors du traitement du nœud 33. Puis sa valeur devient &lt;code&gt;0x11&lt;/code&gt; — et ce changement se produit &lt;strong&gt;pendant&lt;/strong&gt; le traitement du nœud 16, bien plus tard.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pourquoi 0x11 = 17 ?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;0x11&lt;/code&gt; en hexadécimal vaut 17. Or, le nœud 17 a justement l'identifiant interne 17. Ce n'est pas un hasard. Quelque chose écrit l'entier 17 dans la case mémoire qui contenait autrefois l'adresse de l'entrée 33.&lt;/p&gt;

&lt;p&gt;En cherchant dans le code où l'on écrit des entiers à des adresses reçues en paramètre, on arrive à une ligne qui note l'identifiant d'un objet dans le tableau résultat. Cette ligne fait exactement son travail — mais elle écrit au mauvais endroit. L'adresse qu'elle utilise est devenue invalide.&lt;/p&gt;

&lt;h2&gt;
  
  
  La cause : quand PHP réorganise ses tableaux en mémoire
&lt;/h2&gt;

&lt;p&gt;Pour comprendre la cause racine, il faut savoir comment PHP gère ses tableaux en interne.&lt;/p&gt;

&lt;p&gt;En PHP, quand vous écrivez &lt;code&gt;$arr[] = $value&lt;/code&gt;, le tableau grossit. PHP alloue un bloc de mémoire pour les éléments du tableau. Quand ce bloc est plein, PHP en alloue un plus grand (le double), copie tous les éléments dedans, et libère l'ancien. C'est le même principe qu'un &lt;code&gt;ArrayList&lt;/code&gt; en Java ou un &lt;code&gt;list&lt;/code&gt; en Python.&lt;/p&gt;

&lt;p&gt;En C, quand on travaille directement avec les tableaux internes de PHP, on peut obtenir un pointeur direct vers une case de ce tableau — l'équivalent d'écrire &lt;code&gt;$ref = &amp;amp;$arr[5]&lt;/code&gt; en PHP. Si on garde ce pointeur et que PHP réalloue le tableau entre-temps, le pointeur devient invalide : il pointe vers l'ancien bloc, qui a été libéré. En C, personne ne vous prévient. On appelle ça un &lt;em&gt;pointeur fantôme&lt;/em&gt; ou &lt;em&gt;dangling pointer&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;C'est exactement ce qui se passait. Le code buggé ressemblait à ceci, traduit en PHP pour rendre l'idée concrète :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Pseudo-code équivalent au code C buggé&lt;/span&gt;
&lt;span class="nv"&gt;$ref&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;$properties&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'stdClass'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'next'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nv"&gt;$nodeId&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;// on prend une référence dans un tableau&lt;/span&gt;
&lt;span class="nf"&gt;processRecursively&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$node&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$ref&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;             &lt;span class="c1"&gt;// pendant la récursion, le tableau grossit&lt;/span&gt;
&lt;span class="c1"&gt;// $ref pointe maintenant dans un ancien bloc mémoire libéré !&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Et le même code, corrigé :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Pseudo-code équivalent au code C corrigé&lt;/span&gt;
&lt;span class="nv"&gt;$value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nf"&gt;processRecursively&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$node&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;           &lt;span class="c1"&gt;// on travaille dans une variable locale&lt;/span&gt;
&lt;span class="nv"&gt;$properties&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'stdClass'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'next'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nv"&gt;$nodeId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// on insère seulement après la récursion&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;En C, la correction est identique dans l'esprit : on fait travailler la récursion dans une variable locale sur la pile (qui, elle, ne bouge jamais), puis on insère le résultat dans le tableau une fois la récursion terminée et le tableau stabilisé.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ce qui rendait le bug difficile à voir
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Le crash se produisait loin du bug.&lt;/strong&gt; La corruption avait lieu profondément dans la récursion, mais PHP ne plantait que bien plus tard, dans une fonction de finalisation, sur une ligne parfaitement correcte. Sans les traces &lt;code&gt;fprintf&lt;/code&gt;, il était impossible de faire le lien.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;La valeur corrompue était plausible.&lt;/strong&gt; &lt;code&gt;0x11 = 17&lt;/code&gt; ressemble à un ID d'objet ordinaire. Un bug qui produirait une valeur comme &lt;code&gt;0xDEADBEEF&lt;/code&gt; saute immédiatement aux yeux. Là, rien de suspect à première vue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;L'hypothèse initiale était raisonnable.&lt;/strong&gt; Une récursion profonde sur une liste chaînée, ça déborde la pile — c'est un classique. Il a fallu réfuter cette hypothèse par le calcul avant de chercher autre chose.&lt;/p&gt;

&lt;h2&gt;
  
  
  La boîte à outils
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;make&lt;/code&gt; et &lt;code&gt;make test&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Une extension PHP se compile comme n'importe quel programme C. &lt;code&gt;make&lt;/code&gt; lance la compilation. &lt;code&gt;make test&lt;/code&gt; utilise le runner de tests intégré à PHP lui-même (&lt;code&gt;run-tests.php&lt;/code&gt;), qui exécute les fichiers &lt;code&gt;.phpt&lt;/code&gt; — un format texte qui contient le code PHP à exécuter et la sortie attendue.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make &lt;span class="nt"&gt;-j&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;sysctl &lt;span class="nt"&gt;-n&lt;/span&gt; hw.ncpu&lt;span class="si"&gt;)&lt;/span&gt;                        &lt;span class="c"&gt;# compile en parallèle&lt;/span&gt;
make &lt;span class="nb"&gt;test &lt;/span&gt;&lt;span class="nv"&gt;NO_INTERACTION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1                         &lt;span class="c"&gt;# tous les tests&lt;/span&gt;
make &lt;span class="nb"&gt;test &lt;/span&gt;&lt;span class="nv"&gt;TESTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tests/deepclone_deep_nesting.phpt  &lt;span class="c"&gt;# un seul test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;php -d extension=...&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Pour tester l'extension sans l'installer globalement, on charge le fichier &lt;code&gt;.so&lt;/code&gt; directement au lancement de PHP :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nv"&gt;extension&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;modules/deepclone.so &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'var_dump(deepclone_to_array(new stdClass()));'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;L'option &lt;code&gt;-d&lt;/code&gt; permet de définir n'importe quel paramètre &lt;code&gt;php.ini&lt;/code&gt; à la volée. &lt;code&gt;-r&lt;/code&gt; exécute du code PHP directement en ligne de commande, sans créer de fichier.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;codesign&lt;/code&gt; (macOS uniquement)
&lt;/h3&gt;

&lt;p&gt;Sur macOS, tous les binaires doivent avoir une signature cryptographique. Quand on recompile une extension &lt;code&gt;.so&lt;/code&gt;, la signature précédente est effacée. Si on oublie de la refaire, macOS tue le processus avec un SIGKILL (exit code 137) au chargement — sans aucun message d'erreur. Cette erreur silencieuse a coûté du temps avant d'être identifiée.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;codesign &lt;span class="nt"&gt;--sign&lt;/span&gt; - &lt;span class="nt"&gt;--force&lt;/span&gt; modules/deepclone.so
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Le &lt;code&gt;-&lt;/code&gt; signifie « signe avec une identité ad-hoc » — pas un certificat Apple officiel, juste assez pour satisfaire macOS en développement local.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;otool&lt;/code&gt; et &lt;code&gt;nm&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Ces deux outils inspectent le contenu d'un binaire compilé, sans l'exécuter.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;nm&lt;/code&gt; liste tous les symboles (fonctions, variables globales) d'un fichier &lt;code&gt;.so&lt;/code&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nm modules/deepclone.so | &lt;span class="nb"&gt;grep &lt;/span&gt;dc_copy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;otool&lt;/code&gt; peut désassembler le code machine. On l'a utilisé pour mesurer précisément la taille de la frame de pile de chaque fonction :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;otool &lt;span class="nt"&gt;-tv&lt;/span&gt; modules/deepclone.so | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-A5&lt;/span&gt; &lt;span class="s2"&gt;"dc_copy_value"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sur ARM (Apple Silicon), l'instruction &lt;code&gt;sub sp, sp, #N&lt;/code&gt; au début d'une fonction indique exactement combien d'octets elle réserve sur la pile. C'est comme ça qu'on a obtenu les ~480 octets par niveau qui ont permis de réfuter l'hypothèse de débordement de pile.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;lldb&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;lldb&lt;/code&gt; est le debugger natif de macOS (l'équivalent de &lt;code&gt;gdb&lt;/code&gt; sur Linux). On l'a lancé pour obtenir une trace d'appels au moment du crash :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lldb &lt;span class="nt"&gt;--&lt;/span&gt; php &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nv"&gt;extension&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;modules/deepclone.so /tmp/test.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Résultat décevant : le PHP installé via Homebrew est compilé sans symboles de débogage. Les noms de fonctions C internes n'apparaissent pas dans les traces, les numéros de ligne sont absents. &lt;code&gt;lldb&lt;/code&gt; a confirmé que le crash se produisait dans &lt;code&gt;dc_build_output&lt;/code&gt;, mais guère plus. C'est ce qui a conduit à l'approche &lt;code&gt;fprintf&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Aurait-on été plus rapide avec un PHP debug ?
&lt;/h2&gt;

&lt;p&gt;Oui, clairement. PHP peut se compiler en mode debug (&lt;code&gt;--enable-debug&lt;/code&gt;), ce qui active plusieurs protections supplémentaires.&lt;/p&gt;

&lt;p&gt;Avec les &lt;strong&gt;symboles de débogage&lt;/strong&gt;, &lt;code&gt;lldb&lt;/code&gt; aurait affiché les noms de fonctions C, les numéros de ligne et la valeur des variables locales au moment du crash — directement, sans &lt;code&gt;fprintf&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Surtout, &lt;strong&gt;AddressSanitizer (ASan)&lt;/strong&gt; aurait résolu le problème en quelques minutes. C'est un outil qui s'intercale entre le programme et l'allocateur mémoire. Il maintient une carte de toutes les zones allouées et libérées, et lève une erreur dès qu'on lit ou écrit dans une zone libérée. Avec ASan, le crash aurait eu lieu &lt;em&gt;exactement&lt;/em&gt; à la première écriture via le pointeur fantôme, avec un message indiquant l'adresse corrompue, la pile d'appels complète, et l'endroit où la zone avait été initialement allouée puis libérée.&lt;/p&gt;

&lt;p&gt;La contrainte est pratique : compiler PHP depuis les sources prend du temps et produit un binaire incompatible avec les extensions précompilées. Personne ne garde un PHP debug sous la main en permanence. C'est un investissement utile quand on développe activement une extension, moins justifié pour un débogage ponctuel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Leçons
&lt;/h2&gt;

&lt;p&gt;La corruption mémoire est le genre de bug qui ne ressemble à rien depuis PHP : une exception est levée, on a un message, un numéro de ligne. En C, le programme se termine sans explication à un endroit qui n'a rien à voir avec la vraie cause. La seule méthode sans debugger est de réduire progressivement la zone suspecte avec des &lt;code&gt;fprintf&lt;/code&gt;, comme on réduit un bug PHP avec des &lt;code&gt;var_dump&lt;/code&gt; — en cherchant à identifier &lt;em&gt;quand&lt;/em&gt; une valeur bascule de correcte à corrompue.&lt;/p&gt;

&lt;p&gt;Et la leçon principale : en C, un pointeur dans un tableau ne survit pas à une insertion dans ce tableau. C'est vrai pour les tableaux PHP internes, et c'est vrai pour la plupart des structures de données dynamiques en C et C++. Si le tableau peut grossir, les pointeurs qu'on avait obtenus avant peuvent devenir invalides. Il faut alors travailler dans une variable locale et n'insérer dans le tableau qu'une fois la zone à risque passée.&lt;/p&gt;

</description>
      <category>php</category>
      <category>internals</category>
      <category>c</category>
      <category>claude</category>
    </item>
    <item>
      <title>Andreas turned massive analytical tasks into lightning-fast reads using the Bucket Pattern on a giant MongoDB dataset.</title>
      <dc:creator>Jérôme TAMARELLE</dc:creator>
      <pubDate>Wed, 14 Jan 2026 23:37:04 +0000</pubDate>
      <link>https://forem.com/gromnan/andreas-turned-massive-analytical-tasks-into-lightning-fast-reads-using-the-bucket-pattern-on-a-4o76</link>
      <guid>https://forem.com/gromnan/andreas-turned-massive-analytical-tasks-into-lightning-fast-reads-using-the-bucket-pattern-on-a-4o76</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/mongodb" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__org__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F140%2F9639a040-3c27-4b99-b65a-85e100016d3c.png" alt="MongoDB" width="217" height="300"&gt;
      &lt;div class="ltag__link__user__pic"&gt;
        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F900668%2Facf128b6-68be-4ee8-97a8-2e07f7b1e734.jpeg" alt="" width="800" height="800"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/mongodb/how-to-scale-to-a-billion-documents-in-symfony-16pf" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;How to Scale to a Billion Documents in Symfony&lt;/h2&gt;
      &lt;h3&gt;Rishabh Bisht for MongoDB ・ Jan 14&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#symfony&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#php&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#database&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>symfony</category>
      <category>php</category>
      <category>webdev</category>
      <category>database</category>
    </item>
    <item>
      <title>Searching in a database from image and text, made easy thanks to Symfony AI, MongoDB and Voyage AI.</title>
      <dc:creator>Jérôme TAMARELLE</dc:creator>
      <pubDate>Fri, 12 Dec 2025 14:21:49 +0000</pubDate>
      <link>https://forem.com/gromnan/searching-in-a-database-from-image-and-text-made-easy-thanks-to-symfony-ai-mongodb-and-voyage-ai-17gk</link>
      <guid>https://forem.com/gromnan/searching-in-a-database-from-image-and-text-made-easy-thanks-to-symfony-ai-mongodb-and-voyage-ai-17gk</guid>
      <description>&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/mongodb/building-an-archaeology-matcher-a-literal-deep-dive-into-multimodal-vector-search-2d5o" class="crayons-story__hidden-navigation-link"&gt;Building an Archaeology Matcher: A (Literal) Deep Dive Into Multimodal Vector Search&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;
          &lt;a class="crayons-logo crayons-logo--l" href="/mongodb"&gt;
            &lt;img alt="MongoDB logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F140%2F9639a040-3c27-4b99-b65a-85e100016d3c.png" class="crayons-logo__image"&gt;
          &lt;/a&gt;

          &lt;a href="/therish2050" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F900668%2Facf128b6-68be-4ee8-97a8-2e07f7b1e734.jpeg" alt="therish2050 profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/therish2050" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Rishabh Bisht
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Rishabh Bisht
                
              
              &lt;div id="story-author-preview-content-3102060" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/therish2050" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F900668%2Facf128b6-68be-4ee8-97a8-2e07f7b1e734.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Rishabh Bisht&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

            &lt;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/mongodb" class="crayons-story__secondary fw-medium"&gt;MongoDB&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/mongodb/building-an-archaeology-matcher-a-literal-deep-dive-into-multimodal-vector-search-2d5o" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Dec 12 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/mongodb/building-an-archaeology-matcher-a-literal-deep-dive-into-multimodal-vector-search-2d5o" id="article-link-3102060"&gt;
          Building an Archaeology Matcher: A (Literal) Deep Dive Into Multimodal Vector Search
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/symfony"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;symfony&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/php"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;php&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/vectordatabase"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;vectordatabase&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/mongodb/building-an-archaeology-matcher-a-literal-deep-dive-into-multimodal-vector-search-2d5o" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;8&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/mongodb/building-an-archaeology-matcher-a-literal-deep-dive-into-multimodal-vector-search-2d5o#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            8 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;




</description>
      <category>symfony</category>
      <category>php</category>
      <category>ai</category>
      <category>vectordatabase</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Jérôme TAMARELLE</dc:creator>
      <pubDate>Tue, 10 Jun 2025 09:29:20 +0000</pubDate>
      <link>https://forem.com/gromnan/-3o0l</link>
      <guid>https://forem.com/gromnan/-3o0l</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/mongodb/symfony-and-mongodb-our-commitment-to-a-stronger-integration-hf4" class="crayons-story__hidden-navigation-link"&gt;Symfony and MongoDB: Our Commitment to a Stronger Integration&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;
          &lt;a class="crayons-logo crayons-logo--l" href="/mongodb"&gt;
            &lt;img alt="MongoDB logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F140%2F9639a040-3c27-4b99-b65a-85e100016d3c.png" class="crayons-logo__image"&gt;
          &lt;/a&gt;

          &lt;a href="/mongodb_guests" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2904295%2F8ac50b13-2361-4db9-a4ed-940b7bb88f57.jpg" alt="mongodb_guests profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/mongodb_guests" class="crayons-story__secondary fw-medium m:hidden"&gt;
              MongoDB Guests
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                MongoDB Guests
                
              
              &lt;div id="story-author-preview-content-2556117" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/mongodb_guests" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2904295%2F8ac50b13-2361-4db9-a4ed-940b7bb88f57.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;MongoDB Guests&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

            &lt;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/mongodb" class="crayons-story__secondary fw-medium"&gt;MongoDB&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/mongodb/symfony-and-mongodb-our-commitment-to-a-stronger-integration-hf4" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jun 9 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/mongodb/symfony-and-mongodb-our-commitment-to-a-stronger-integration-hf4" id="article-link-2556117"&gt;
          Symfony and MongoDB: Our Commitment to a Stronger Integration
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/symfony"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;symfony&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/mongodb"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;mongodb&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/php"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;php&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/mongodb/symfony-and-mongodb-our-commitment-to-a-stronger-integration-hf4" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;14&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/mongodb/symfony-and-mongodb-our-commitment-to-a-stronger-integration-hf4#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              2&lt;span class="hidden s:inline"&gt; comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            3 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>symfony</category>
      <category>mongodb</category>
      <category>webdev</category>
      <category>php</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Jérôme TAMARELLE</dc:creator>
      <pubDate>Wed, 19 Mar 2025 15:58:57 +0000</pubDate>
      <link>https://forem.com/gromnan/-5c9g</link>
      <guid>https://forem.com/gromnan/-5c9g</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/mongodb" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__org__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F140%2F9639a040-3c27-4b99-b65a-85e100016d3c.png" alt="MongoDB" width="217" height="300"&gt;
      &lt;div class="ltag__link__user__pic"&gt;
        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2904295%2F8ac50b13-2361-4db9-a4ed-940b7bb88f57.jpg" alt="" width="400" height="400"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/mongodb/better-aggregation-pipeline-support-in-the-mongodb-php-driver-30g1" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Better Aggregation Pipeline Support in the MongoDB PHP Driver&lt;/h2&gt;
      &lt;h3&gt;MongoDB Guests for MongoDB ・ Mar 17&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#php&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#queries&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#programming&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>php</category>
      <category>mongodb</category>
      <category>queries</category>
      <category>webdev</category>
    </item>
    <item>
      <title>MongoDB optimistic update with versioned documents</title>
      <dc:creator>Jérôme TAMARELLE</dc:creator>
      <pubDate>Thu, 13 Mar 2025 19:06:25 +0000</pubDate>
      <link>https://forem.com/gromnan/mongodb-optimistic-update-with-versioned-documents-3e4b</link>
      <guid>https://forem.com/gromnan/mongodb-optimistic-update-with-versioned-documents-3e4b</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://dev.to/gromnan/mise-a-jour-optimiste-des-documents-versionnes-dans-mongodb-2hnn"&gt;Lire cet article en Français&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Suppose you need to store documents in MongoDB from an external source such as a RabbitMQ or Kafka queue. The order in which these messages are processed is not guaranteed, especially if there are several processes running in parallel.&lt;/p&gt;

&lt;p&gt;To ensure that the latest version of a document is always available, and that it is not overwritten by an older version from another message that was processed later, a version number can be used. The higher the version number, the more recent the document.&lt;/p&gt;

&lt;p&gt;The external data source must then include the document version number with each message. This version number can be an increment or simply the timestamp at which the data source generated the message.&lt;/p&gt;

&lt;p&gt;In MongoDB, we use the &lt;code&gt;updateOne&lt;/code&gt; method to update a document, but to ensure that we don't overwrite a more recent version, we add a condition that checks that the version number of the document in the database is lower than that of the document we wish to insert.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;MongoDB\Collection&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cd"&gt;/**
 * @param Collection $collection The MongoDB collection
 * @param int $id                The ID of the document to update
 * @param int $version           The version of the new document
 * @param array $document        The new document
 * @return bool                  True if the new version have been saved, false if it was outdated
 */&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;optimisticUpsert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Collection&lt;/span&gt; &lt;span class="nv"&gt;$collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$document&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// The document id and version are stored in the document itself&lt;/span&gt;
    &lt;span class="nv"&gt;$document&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'_id'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$document&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'_version'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$version&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$collection&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;updateOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'$replaceWith'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'$cond'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                    &lt;span class="s1"&gt;'if'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'$lt'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'$_version'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$version&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;
                    &lt;span class="s1"&gt;'then'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s1"&gt;'else'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'$$ROOT'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="p"&gt;]],&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'upsert'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getUpsertedCount&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getModifiedCount&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The MongoDB server is smart enough to know that there are no changes&lt;br&gt;
when the stored version was already more recent than the one you wanted to insert.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;$result-&amp;gt;getUpsertedCount()&lt;/code&gt; is greater than 0, this means that the document did not exist and has been inserted. If &lt;code&gt;$result-&amp;gt;getModifiedCount()&lt;/code&gt; is greater than 0, this means that the document already existed and has been updated. Otherwise, it means&lt;br&gt;
that the document version was already more recent than the one to be inserted.&lt;/p&gt;

&lt;p&gt;More information on update operations can be found in the documentation :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Expression &lt;a href="https://www.mongodb.com/docs/manual/reference/operator/aggregation/replaceWith/" rel="noopener noreferrer"&gt;$replaceWith&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Operator &lt;a href="https://www.mongodb.com/docs/manual/reference/operator/aggregation/cond/" rel="noopener noreferrer"&gt;$cond&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Method &lt;a href="https://www.mongodb.com/docs/php-library/current/reference/method/MongoDBCollection-updateOne/" rel="noopener noreferrer"&gt;updateOne&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>mongodb</category>
      <category>php</category>
    </item>
    <item>
      <title>Mise à jour optimiste des documents versionnés dans MongoDB</title>
      <dc:creator>Jérôme TAMARELLE</dc:creator>
      <pubDate>Thu, 13 Mar 2025 19:03:11 +0000</pubDate>
      <link>https://forem.com/gromnan/mise-a-jour-optimiste-des-documents-versionnes-dans-mongodb-2hnn</link>
      <guid>https://forem.com/gromnan/mise-a-jour-optimiste-des-documents-versionnes-dans-mongodb-2hnn</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://dev.to/gromnan/mongodb-optimistic-update-with-versioned-documents-3e4b"&gt;Read this article in English&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Supposons que vous ayez besoin de stocker dans MongoDB des documents provenant d'une source externe comme une file d'attente RabbitMQ ou Kafka. L'ordre dans lequel ces messages sont traités n'est pas garanti, surtout s'il y a plusieurs processus de traitement en parallèle.&lt;/p&gt;

&lt;p&gt;Afin de garantir de toujours disposer de la dernière version d'un document, et que celle-ci ne soit pas écrasée par une version plus ancienne qui proviendrait d'un autre message ayant été traité plus tard, il est possible d'utiliser un numéro de version. Plus le numéro de version est élevé, plus le document est récent.&lt;/p&gt;

&lt;p&gt;La source de données externe doit alors inclure le numéro de version du document à chaque message. Ce numéro de version peut être un incrément ou simplement le timestamp auquel la source de données a généré le message.&lt;/p&gt;

&lt;p&gt;Dans MongoDB, on utilise la méthode &lt;code&gt;updateOne&lt;/code&gt; pour mettre à jour un document, mais pour s'assurer qu'on n'écrase pas une version plus récente, on ajoute une condition qui vérifie que le numéro de version du document dans la base de données est inférieur à celui du document que l'on souhaite insérer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;MongoDB\Collection&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cd"&gt;/**
 * @param Collection $collection The MongoDB collection
 * @param int $id                The ID of the document to update
 * @param int $version           The version of the new document
 * @param array $document        The new document
 * @return bool                  True if the new version have been saved, false if it was outdated
 */&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;optimisticUpsert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Collection&lt;/span&gt; &lt;span class="nv"&gt;$collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$document&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// The document id and version are stored in the document itself&lt;/span&gt;
    &lt;span class="nv"&gt;$document&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'_id'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$document&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'_version'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$version&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$collection&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;updateOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'$replaceWith'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'$cond'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                    &lt;span class="s1"&gt;'if'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'$lt'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'$_version'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$version&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;
                    &lt;span class="s1"&gt;'then'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s1"&gt;'else'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'$$ROOT'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="p"&gt;]],&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'upsert'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getUpsertedCount&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getModifiedCount&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Le serveur MongoDB est assez intelligent pour savoir qu'il n'y a pas de changement lorsque la version stockée était déjà plus récente que celle que l'on souhaitait insérer.&lt;/p&gt;

&lt;p&gt;Si &lt;code&gt;$result-&amp;gt;getUpsertedCount()&lt;/code&gt; est supérieur à 0, cela signifie que le document n'existait pas et a été inséré. Si &lt;code&gt;$result-&amp;gt;getModifiedCount()&lt;/code&gt; est supérieur à 0, cela signifie que le document existait déjà et a été mis à jour. Sinon, cela signifie&lt;br&gt;
que la version du document était déjà plus récente que celle que l'on souhaitait insérer.&lt;/p&gt;

&lt;p&gt;Plus d'informations sur les opérations de mise à jour dans la documentation :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Expression &lt;a href="https://www.mongodb.com/docs/manual/reference/operator/aggregation/replaceWith/" rel="noopener noreferrer"&gt;$replaceWith&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Opérateur &lt;a href="https://www.mongodb.com/docs/manual/reference/operator/aggregation/cond/" rel="noopener noreferrer"&gt;$cond&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Méthode &lt;a href="https://www.mongodb.com/docs/php-library/current/reference/method/MongoDBCollection-updateOne/" rel="noopener noreferrer"&gt;updateOne&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>mongodb</category>
      <category>php</category>
    </item>
    <item>
      <title>PHP Closures and Generators can hold circular references</title>
      <dc:creator>Jérôme TAMARELLE</dc:creator>
      <pubDate>Fri, 17 Jan 2025 21:34:11 +0000</pubDate>
      <link>https://forem.com/gromnan/php-closures-and-generators-can-hold-circular-references-45ge</link>
      <guid>https://forem.com/gromnan/php-closures-and-generators-can-hold-circular-references-45ge</guid>
      <description>&lt;p&gt;Circular references are a common cause of memory leaks in PHP. They occur when objects reference each other, directly or indirectly. Thankfully, PHP has a garbage collector that can detect and clean up circular references. However, this consumes CPU cycles and can slow down your application.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The garbage collector is triggered whenever &lt;strong&gt;10,000&lt;/strong&gt; possible cyclic objects or arrays are currently in memory, and one of them falls out of scope.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The garbage collection is never triggered if you have a small number of objects using a lot of memory. You can reach the memory limit even if the memory is used by orphaned objects that should be collected by the garbage collector.&lt;/p&gt;

&lt;p&gt;That's why you should identify situations where you create circular references and avoid them.&lt;/p&gt;

&lt;p&gt;In this article, we explore how closures and generators can hold circular references, and how to prevent them.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
About circular references

&lt;ul&gt;
&lt;li&gt;Typical example of a circular reference&lt;/li&gt;
&lt;li&gt;Preventing circular references with weak references&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Closures and circular references&lt;/li&gt;

&lt;li&gt;Generators and circular references&lt;/li&gt;

&lt;li&gt;Conclusion&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  About circular references
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Typical example of a circular reference
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;A&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;B&lt;/span&gt; &lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;B&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;B&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;A&lt;/span&gt; &lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, &lt;code&gt;A&lt;/code&gt; and &lt;code&gt;B&lt;/code&gt; reference each other. When you create an instance of &lt;code&gt;A&lt;/code&gt;, it creates an instance of &lt;code&gt;B&lt;/code&gt; that references &lt;code&gt;A&lt;/code&gt;. This creates a circular reference.&lt;/p&gt;

&lt;p&gt;To detect circular references, we can trigger the garbage collector manually using &lt;a href="https://www.php.net/function.gc-collect-cycles" rel="noopener noreferrer"&gt;&lt;code&gt;gc_collect_cycles()&lt;/code&gt;&lt;/a&gt; and read the number of collected references using &lt;a href="https://www.php.net/function.gc-status" rel="noopener noreferrer"&gt;&lt;code&gt;gc_status()&lt;/code&gt;&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Object created but not assigned to a variable&lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;A&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nb"&gt;gc_collect_cycles&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nb"&gt;print_r&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;gc_status&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Array
(
    ...
    [collected] =&amp;gt; 2
    ...
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example shows that the garbage collector has detected and removed 2 objects with circular references.&lt;/p&gt;

&lt;p&gt;You can also see the number of references to an object using the &lt;a href="https://xdebug.org/docs/all_functions#xdebug_debug_zval" rel="noopener noreferrer"&gt;&lt;code&gt;xdebug_debug_zval()&lt;/code&gt; function&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Preventing circular references with weak references
&lt;/h3&gt;

&lt;p&gt;When confronted to a circular reference, an easy solution is to use a weak reference. A weak reference is an object that holds a reference that does not prevent the garbage collector from collecting the object it references. In PHP, you can create a weak reference using the &lt;a href="https://www.php.net/class.weakreference" rel="noopener noreferrer"&gt;&lt;code&gt;WeakReference&lt;/code&gt;&lt;/a&gt; class.&lt;/p&gt;

&lt;p&gt;This requires some changes to the code. The &lt;code&gt;B&lt;/code&gt; class now stores a &lt;code&gt;WeakReference&lt;/code&gt; object instead of an &lt;code&gt;A&lt;/code&gt; object. You must access the &lt;code&gt;A&lt;/code&gt; object using the &lt;code&gt;get()&lt;/code&gt; method of the &lt;code&gt;WeakReference&lt;/code&gt; object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;A&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;B&lt;/span&gt; &lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;B&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;B&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/** @var WeakReference&amp;lt;A&amp;gt; $a */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;WeakReference&lt;/span&gt; &lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;A&lt;/span&gt; &lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;WeakReference&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;    
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Object created but not assigned to a variable&lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;A&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nb"&gt;gc_collect_cycles&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nb"&gt;print_r&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;gc_status&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="c1"&gt;// [collected] =&amp;gt; 0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the output, you will see that the number of collected references is now &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip 1:&lt;/strong&gt; Use weak references to prevent circular references only when necessary.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Closures and circular references
&lt;/h2&gt;

&lt;p&gt;The concept of closures in PHP is to create a function that can access variables from the parent scope. This can lead to circular references if you are not careful.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;createCircularReference&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;stdClass&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, the closure &lt;code&gt;$a-&amp;gt;b&lt;/code&gt; references the variable &lt;code&gt;$a&lt;/code&gt; from the parent scope. The circular reference is easy to spot as the reference is explicit.&lt;/p&gt;

&lt;p&gt;But the same issue can occur in a more subtle way if you use the &lt;a href="https://www.php.net/functions.arrow" rel="noopener noreferrer"&gt;short syntax for closures&lt;/a&gt;. Using an arrow function, the variable &lt;code&gt;$a&lt;/code&gt; is not explicitly referenced in the closure, but it is still captured by reference.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;createCircularReference&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;stdClass&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;createCircularReference&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nb"&gt;gc_collect_cycles&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nb"&gt;print_r&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;gc_status&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="c1"&gt;// [collected] =&amp;gt; 2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, the number of collected references is &lt;code&gt;2&lt;/code&gt;, indicating a circular reference.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reference to &lt;code&gt;$this&lt;/code&gt; in closures
&lt;/h3&gt;

&lt;p&gt;Any non-static closure that is created within a class method will have a reference to the object instance (&lt;code&gt;$this&lt;/code&gt;), even if &lt;code&gt;$this&lt;/code&gt; is not accessed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;A&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;Closure&lt;/span&gt; &lt;span class="nv"&gt;$closure&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;closure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;A&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nb"&gt;gc_collect_cycles&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nb"&gt;print_r&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;gc_status&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="c1"&gt;// [collected] =&amp;gt; 2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is because the &lt;code&gt;$this&lt;/code&gt; reference is always captured by reference in the closure. It can be accessed using &lt;a href="https://www.php.net/reflectionfunctionabstract.getclosurethis" rel="noopener noreferrer"&gt;&lt;code&gt;Reflection::getClosureThis()&lt;/code&gt;&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;A&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;Closure&lt;/span&gt; &lt;span class="nv"&gt;$closure&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;closure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;A&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nb"&gt;gc_collect_cycles&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nb"&gt;print_r&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;gc_status&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="c1"&gt;// [collected] =&amp;gt; 0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the closure is created from the global scope or inside a static method, the &lt;code&gt;$this&lt;/code&gt; reference is null.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip 2:&lt;/strong&gt; Always use &lt;code&gt;static function () {}&lt;/code&gt; or &lt;code&gt;static fn () =&amp;gt;&lt;/code&gt; when creating a closure if you don't need &lt;code&gt;$this&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Generators and circular references
&lt;/h2&gt;

&lt;p&gt;We come to the reason for this article. Something I discovered recently:&lt;br&gt;
Generators keep references, as long as they are not exhausted.&lt;/p&gt;

&lt;p&gt;In this example, the class stores the generator in a property, but the generator has &lt;code&gt;$this&lt;/code&gt; reference to the object instance.&lt;br&gt;
The generator behaves like a closure and keeps a reference to the object instance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;A&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;Iterator&lt;/span&gt; &lt;span class="nv"&gt;$iterator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;iterator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;generator&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;generator&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;Generator&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;A&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nb"&gt;gc_collect_cycles&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nb"&gt;print_r&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;gc_status&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="c1"&gt;// [collected] =&amp;gt; 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The class instance was collected by the garbage collector because it has a reference to the generator, which has a reference to the object instance.&lt;/p&gt;

&lt;p&gt;As soon as the generator is exhausted, the reference is released, and the object instance is removed from the memory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nb"&gt;iterator_to_array&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;A&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;iterator&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nb"&gt;gc_collect_cycles&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nb"&gt;print_r&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;gc_status&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="c1"&gt;// [collected] =&amp;gt; 0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip 3:&lt;/strong&gt; Ensure you always exhaust the generator by iterating over it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip 4:&lt;/strong&gt; Use static methods or closure to create generators to not keep references to the object instance.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Circular references are a common cause of memory leaks in PHP. Even if the garbage collector can detect and clean up circular references, it consumes CPU cycles and can slow down your application. You must detect situations where you create such circular reference and adapt your code to prevent them. Using weak references can prevent circular references, but some simple tips can help you to prevent circular references in the first place:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;static function () {}&lt;/code&gt; or &lt;code&gt;static fn () =&amp;gt;&lt;/code&gt; when creating a closure if you don't need &lt;code&gt;$this&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Ensure you always exhaust the generator by iterating over it.&lt;/li&gt;
&lt;li&gt;Use static methods or closure to create generators to not keep references to the object instance.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Read more
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.php.net/features.gc.performance-considerations" rel="noopener noreferrer"&gt;PHP Garbage Collection — Performance Considerations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tideways.com/profiler/blog/what-is-garbage-collection-in-php-and-how-do-you-make-the-most-of-it" rel="noopener noreferrer"&gt;What Is Garbage Collection in PHP And How Do You Make The Most Of It?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://johann.pardanaud.com/blog/about-circular-references-in-php/" rel="noopener noreferrer"&gt;How you can end up with circular references&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/arnaud-lb/php-memory-profiler" rel="noopener noreferrer"&gt;&lt;em&gt;memprof&lt;/em&gt; — Memory profiler for PHP. Helps finding memory leaks in PHP scripts. &lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://xdebug.org/docs/profiler" rel="noopener noreferrer"&gt;Xdebug's built-in profiler&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>php</category>
      <category>performance</category>
    </item>
    <item>
      <title>Fix PHP 8.4 deprecation: Implicitly marking parameter as nullable is deprecated, the explicit nullable type must be used instead</title>
      <dc:creator>Jérôme TAMARELLE</dc:creator>
      <pubDate>Mon, 29 Apr 2024 23:18:55 +0000</pubDate>
      <link>https://forem.com/gromnan/fix-php-84-deprecation-implicitly-marking-parameter-as-nullable-is-deprecated-the-explicit-nullable-type-must-be-used-instead-5gp3</link>
      <guid>https://forem.com/gromnan/fix-php-84-deprecation-implicitly-marking-parameter-as-nullable-is-deprecated-the-explicit-nullable-type-must-be-used-instead-5gp3</guid>
      <description>&lt;p&gt;PHP 8.4 will be released in November 2024, but the list of new features and deprecated features is already very established. It is clearly detailed on &lt;a href="https://php.watch/versions/8.4" rel="noopener noreferrer"&gt;php.watch/versions/8.4&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The most prominent change is the deprecation of implicit nullable parameter declarations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'PHP'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Allowed&lt;/span&gt;
&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Allowed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which is easy to fix using the explicit nullable type introduced in PHP 7.1:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- function test(string $test = null) {}
&lt;/span&gt;&lt;span class="gi"&gt;+ function test(?string $test = null) {}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can detect this deprecation in your PHP files using PHP's built-in linter. This requires a local installation of PHP 8.4.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;php -l src/**/*.php
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that you know the affected files, you can fix them by yourself, or automatically using PHP-CS-Fixer or Rector.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix your codebase using PHP-CS-Fixer
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://cs.symfony.com/" rel="noopener noreferrer"&gt;PHP Coding Standards Fixer&lt;/a&gt; is a tool to automatically apply formatting rules on your code. In our case, we will use only the &lt;a href="https://cs.symfony.com/doc/rules/function_notation/nullable_type_declaration_for_default_null_value.html" rel="noopener noreferrer"&gt;&lt;code&gt;nullable_type_declaration_for_default_null_value&lt;/code&gt;&lt;/a&gt; rule.&lt;/p&gt;

&lt;p&gt;1 - Install php-cs-fixer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;composer require --dev friendsofphp/php-cs-fixer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2 - Copy the minimal configuration in &lt;code&gt;.php-cs-fixer.php&lt;/code&gt;, at the root of your project. We assume your source files are located in &lt;code&gt;src&lt;/code&gt; but you can change it to set the path of all your PHP files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="nv"&gt;$finder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PhpCsFixer\Finder&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;in&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="k"&gt;__DIR__&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/src'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'*.php'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PhpCsFixer\Config&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setFinder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$finder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setRules&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="s1"&gt;'nullable_type_declaration_for_default_null_value'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3 - Run php-cs-fixer&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;vendor/bin/php-cs-fixer fix
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note: For versions before &lt;a href="https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/releases/tag/v3.80.0" rel="noopener noreferrer"&gt;v3.80.0&lt;/a&gt;, seting the environment variable &lt;code&gt;PHP_CS_FIXER_IGNORE_ENV=1&lt;/code&gt; is necessary to run on PHP 8.4.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix your codebase using Rector
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://getrector.com/" rel="noopener noreferrer"&gt;Rector&lt;/a&gt; is an excellent tool for refactoring and modernizing code. In our case, we will only use the &lt;a href="https://github.com/rectorphp/rector/blob/aff937886fc1bd7605bdc6f8d750174a15d890cf/docs/rector_rules_overview.md#explicitnullableparamtyperector" rel="noopener noreferrer"&gt;&lt;code&gt;ExplicitNullableParamTypeRector&lt;/code&gt;&lt;/a&gt; rule.&lt;/p&gt;

&lt;p&gt;1 - Install rector:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;composer require --dev rector/rector
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2 - Copy this minimal configuration in &lt;code&gt;rector.php&lt;/code&gt;, at the root of your project. We assume your source files are located in &lt;code&gt;src&lt;/code&gt; but you can change it to set the path of all your PHP files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Rector\Config\RectorConfig&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withPaths&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="k"&gt;__DIR__&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/src'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withPhpVersion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Rector\ValueObject\PhpVersion&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PHP_84&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withRules&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
         &lt;span class="nc"&gt;Rector\Php84\Rector\Param\ExplicitNullableParamTypeRector&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3 - Run rector:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;vendor/bin/rector
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I hope this quick tutorial has saved you some time. There are hundreds of projects to patch on GitHub, have fun!&lt;/p&gt;

&lt;h2&gt;
  
  
  Spread the world
&lt;/h2&gt;


&lt;blockquote class="bluesky-embed"&gt;
&lt;p&gt;Deprecating the implicitly nullable parameter in #PHP 8.4 had a huge cost for the maintainers. Even if there are solutions to automate the fixes, that breaks almost all old code with very low value for developers. 
My article about fixing this is by far the most popular I have ever published.&lt;/p&gt;— &lt;a href="https://bsky.app/profile/did:plc:u3q2m7pblmqwh2ymsfn6nnoh?ref_src=embed" rel="noopener noreferrer"&gt;GromNaN (@jerome.tamarelle.net)&lt;/a&gt; &lt;a href="https://bsky.app/profile/did:plc:u3q2m7pblmqwh2ymsfn6nnoh/post/3llxx544uv22x?ref_src=embed" rel="noopener noreferrer"&gt;2025-04-04T08:00:03.746Z&lt;/a&gt;
&lt;/blockquote&gt;


</description>
      <category>php</category>
    </item>
    <item>
      <title>How to overcome PHP's naming constraints to model MongoDB operators</title>
      <dc:creator>Jérôme TAMARELLE</dc:creator>
      <pubDate>Mon, 16 Oct 2023 11:42:39 +0000</pubDate>
      <link>https://forem.com/gromnan/how-to-override-phps-naming-constraints-to-model-mongodb-operators-3p30</link>
      <guid>https://forem.com/gromnan/how-to-override-phps-naming-constraints-to-model-mongodb-operators-3p30</guid>
      <description>&lt;p&gt;TL;TR: I research how to modelize MongoDB operators in PHP. From namespaced function names, to static method on classes or enums, to closures in variables. I'm looking for the best developer experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  About MongoDB aggregation pipeline operators
&lt;/h3&gt;

&lt;p&gt;As developer in the MongoDB PHP driver team, my goal is to provide the best developer experience to PHP developers using the document database.&lt;/p&gt;

&lt;p&gt;MongoDB provides drivers for various languages including PHP. In order to ease create of aggregation pipelines in PHP, we need to modeling all the &lt;a href="https://www.mongodb.com/docs/manual/reference/operator/" rel="noopener noreferrer"&gt;stages and operators&lt;/a&gt; as functions that can be combined.&lt;/p&gt;

&lt;p&gt;An aggregation pipeline is a list of "stage" documents. We will take an example doing a query with &lt;a href="https://www.mongodb.com/docs/manual/reference/operator/aggregation/match/" rel="noopener noreferrer"&gt;&lt;code&gt;$match&lt;/code&gt;&lt;/a&gt; and a join  with &lt;a href="https://www.mongodb.com/docs/manual/reference/operator/aggregation/lookup/" rel="noopener noreferrer"&gt;&lt;code&gt;$lookup&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;aggregate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;$match&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;$or&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;shipped&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$gte&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ISODate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2023-01-01T00:00:00Z&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;$lookup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;inventory&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;localField&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;product_id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;foreignField&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;product_id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;as&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;inventory_docs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each key with a dollar prefix is an operator for which we want to provide a factory method.&lt;/p&gt;

&lt;h3&gt;
  
  
  Namespaced functions
&lt;/h3&gt;

&lt;p&gt;The most obvious solution is to create namespaced functions, like: &lt;code&gt;MongoDB\Operator\eq&lt;/code&gt; for the &lt;a href="https://www.mongodb.com/docs/v7.0/reference/operator/query/eq/" rel="noopener noreferrer"&gt;&lt;code&gt;$eq&lt;/code&gt;&lt;/a&gt; operator.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;MongoDB\Operator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;mixed&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'$eq'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$localField&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$foreignField&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$as&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'$lookup'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'from'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'localField'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$localField&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'foreignField'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$foreignField&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'as'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$as&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using this functions with named arguments, the pipeline would be written in PHP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'shipped'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
            &lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;gte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;UTCDateTime&lt;/span&gt;&lt;span class="p"&gt;())),&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'inventory'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;localField&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'product_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;foreignField&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'product_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'inventory_docs'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But, some operator names conflict with the &lt;a href="https://www.php.net/manual/en/reserved.keywords.php" rel="noopener noreferrer"&gt;reserved keywords in PHP&lt;/a&gt;. We cannot create functions (global or namespaced) with the following operator names: &lt;a href="https://www.mongodb.com/docs/v7.0/reference/operator/query/and/" rel="noopener noreferrer"&gt;&lt;code&gt;$and&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://www.mongodb.com/docs/v7.0/reference/operator/query/or/" rel="noopener noreferrer"&gt;&lt;code&gt;$or&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://www.mongodb.com/docs/v7.0/reference/operator/aggregation/match/" rel="noopener noreferrer"&gt;&lt;code&gt;$match&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://www.mongodb.com/docs/v7.0/reference/operator/update/unset/" rel="noopener noreferrer"&gt;&lt;code&gt;$unset&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding suffix to function names
&lt;/h3&gt;

&lt;p&gt;To avoid this problem of reserved names, we can add a prefix or a suffix to the function names.&lt;/p&gt;

&lt;p&gt;With the type of operator as suffix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;andQuery&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;matchStage&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With an underscore:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;_and&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;_match&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or with an emoji. Beautiful, but not practical:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="err"&gt;🔍&lt;/span&gt;&lt;span class="k"&gt;and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="err"&gt;💲&lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Static class methods
&lt;/h3&gt;

&lt;p&gt;By chance, the list of reserved keywords is shorter for method names. We can create static methods on classes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Stage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Query&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Writing got a little bit longer, but it's still readable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;Stage&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;Query&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;Query&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Query&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'shipped'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
            &lt;span class="nc"&gt;Query&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Query&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;gte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;UTCDateTime&lt;/span&gt;&lt;span class="p"&gt;())),&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nc"&gt;Stage&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'inventory'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;localField&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'product_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;foreignField&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'product_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'inventory_docs'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To prevent anyone from creating instances of this class, we can make the constructor private.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Operator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="c1"&gt;// This constructor can't be called&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can also use an &lt;code&gt;enum&lt;/code&gt; without case. Enum accepts static methods and cannot be instantiated.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="n"&gt;enum&lt;/span&gt; &lt;span class="nc"&gt;Query&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both the class and the enum static method can be called the same way.&lt;/p&gt;

&lt;p&gt;The downside with enum is that there is a static &lt;a href="https://www.php.net/manual/en/unitenum.cases.php" rel="noopener noreferrer"&gt;&lt;code&gt;cases&lt;/code&gt;&lt;/a&gt; method that is always available and will pollute the API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Closures in variables
&lt;/h3&gt;

&lt;p&gt;By not finding the ideal solution, we start raving about improbable solutions.&lt;/p&gt;

&lt;p&gt;An idea that comes if we want a short syntax that looks very similar to the MongoDB syntax, without name restrictions, is to use variables to store the closures. Note that &lt;code&gt;(...)&lt;/code&gt; is a &lt;a href="https://php.watch/versions/8.1/first-class-callable-syntax" rel="noopener noreferrer"&gt;new syntax to create closures in PHP 8.1&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$eq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Operator&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$and&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Operator&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't trust the syntax hightlighter.&lt;/p&gt;

&lt;p&gt;It's so wonderful that PHP uses the same dollar &lt;code&gt;$&lt;/code&gt; sign to prefix variables that MongoDB uses for operators.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;$match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nv"&gt;$or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'shipped'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
            &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$gte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;UTCDateTime&lt;/span&gt;&lt;span class="p"&gt;())),&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nv"&gt;$lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'inventory'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;localField&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'product_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;foreignField&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'product_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'inventory_docs'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These closures could be made available by the library as an array.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="n"&gt;enum&lt;/span&gt; &lt;span class="nc"&gt;Query&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="nv"&gt;$queries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;mixed&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;mixed&lt;/span&gt; &lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/** @return array{and:callable,eq:callable,query:callable} */&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;functions&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'and'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'eq'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;'query'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The syntax to get all the variables is a little bit verbose, but it's still readable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'and'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$and&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'eq'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$eq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'query'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Query&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;functions&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can import all the variables into the current scope with the magical &lt;code&gt;extract&lt;/code&gt; function that is so much used in Laravel, but so much hated by PHPStorm and static analysis tools.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nb"&gt;extract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Query&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;functions&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="nb"&gt;var_dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bar&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;// INFO: MixedFunctionCall - Cannot call function on mixed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See psalm analysis: &lt;a href="https://psalm.dev/r/3bbbafd469" rel="noopener noreferrer"&gt;https://psalm.dev/r/3bbbafd469&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;As you can see, naming functions in PHP isn't all that simple when it comes to using reserved keywords. We haven't yet decided how we're going to proceed. If you have any ideas or remarks, please leave a comment.&lt;/p&gt;

&lt;p&gt;Please note that the code has been simplified, the functions will be more complex to offer more features and type safety.&lt;/p&gt;

</description>
      <category>php</category>
      <category>mongodb</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Rendering Twig templates in Storybook</title>
      <dc:creator>Jérôme TAMARELLE</dc:creator>
      <pubDate>Sat, 11 Mar 2023 21:54:59 +0000</pubDate>
      <link>https://forem.com/gromnan/storybook-with-server-side-twig-components-easy-43ii</link>
      <guid>https://forem.com/gromnan/storybook-with-server-side-twig-components-easy-43ii</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Storybook is a frontend workshop for building UI components and pages in isolation. Thousands of teams use it for UI development, testing, and documentation. It’s open source and free.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Update: this is now easier to use Storybook with Twig Components. &lt;a href="https://dev.to/sensiolabs/how-to-share-your-twigcomponent-with-your-team--54b4"&gt;Read this article&lt;/a&gt; from Mathéo.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Historically, it requires a frontend framework like React, Vue or Angular to render components. But I build websites with Twig, so I needed a way to render the templates in my backend application. The package &lt;a href="https://www.npmjs.com/package/@storybook/server" rel="noopener noreferrer"&gt;&lt;code&gt;@storybook/server&lt;/code&gt;&lt;/a&gt; is a solution for this use-case. It was released in March 2022 and is exactly what its name suggests: a server-side rendering solution for Storybook.&lt;/p&gt;

&lt;p&gt;There are very few things to do to connect Storybook to a Symfony application, this is a step-by-step guide to get you started. I'm assuming that you already have a Symfony application with Twig installed, and that you're using &lt;a href="https://symfony.com/doc/current/frontend.html" rel="noopener noreferrer"&gt;webpack encore&lt;/a&gt;. But that's not a requirement, you can use any other frontend tool you like.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the first component
&lt;/h2&gt;

&lt;p&gt;In the websites I build, a components is a single Twig file that can be included in any other template of the project. This can also be a &lt;a href="https://symfony.com/bundles/ux-twig-component/current/index.html" rel="noopener noreferrer"&gt;Twig Component&lt;/a&gt; if you are using &lt;a href="https://symfony.com/doc/current/ux.html" rel="noopener noreferrer"&gt;Symfony UX&lt;/a&gt;. For this example, we'll create a simple button component, in a &lt;code&gt;templates/components&lt;/code&gt; folder. This button can have a label, a size and can be primary or secondary.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight twig"&gt;&lt;code&gt;&lt;span class="c"&gt;{# templates/components/button.html.twig #}&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt;
    &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;
    &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;size&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;primary&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;'primary'&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'secondary'&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
    &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;style&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nf"&gt;escape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'html_attr'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;label&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our goal is to provide a preview of this component in storybook, with the possibility to change its properties.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the story
&lt;/h2&gt;

&lt;p&gt;In StorybookJS, &lt;a href="https://storybook.js.org/docs/html/get-started/whats-a-story" rel="noopener noreferrer"&gt;stories&lt;/a&gt; are individual states of components that can be displayed in isolation for preview and testing. They are typically written in JavaScript when rendering is done on the client-side with a javascript function. For server-side rendering, stories can be written in JSON. An additional key &lt;code&gt;parameters.server.id&lt;/code&gt; is required to specify the path to the preview.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;stories/button.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Components/Button"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"parameters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"server"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"button"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Button"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"argTypes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"control"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"primary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"control"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"boolean"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; 
    &lt;/span&gt;&lt;span class="nl"&gt;"backgroundColor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"control"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"color"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"control"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"select"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"options"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"small"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"medium"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"large"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"stories"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Primary"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"primary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Secondary"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Large"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"large"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Storybook will look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcwt5qt3j3iyn7q3d2sl1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcwt5qt3j3iyn7q3d2sl1.png" alt="Image description" width="800" height="528"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing Storybook server
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://www.npmjs.com/package/@storybook/server" rel="noopener noreferrer"&gt;&lt;code&gt;@storybook/server&lt;/code&gt; documentation&lt;/a&gt; recommends using of &lt;a href="https://www.npmjs.com/package/npx" rel="noopener noreferrer"&gt;&lt;code&gt;npx&lt;/code&gt;&lt;/a&gt; to initialize the project with the &lt;code&gt;server&lt;/code&gt; type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx sb init &lt;span class="nt"&gt;-t&lt;/span&gt; server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Symfony Webpack Encore uses Webpack5 while Storybook is uses Webpack4 by default, so we need to install the &lt;code&gt;@storybook/builder-webpack5&lt;/code&gt; package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @storybook/builder-webpack5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will install npm packages and create a &lt;code&gt;.storybook&lt;/code&gt; folder with &lt;code&gt;main.js&lt;/code&gt; and &lt;code&gt;preview.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The file &lt;code&gt;.storybook/main.js&lt;/code&gt; defines the location of the stories and the addons to be used. We need to add the &lt;code&gt;@storybook/server&lt;/code&gt; framework and the &lt;code&gt;@storybook/builder-webpack5&lt;/code&gt; builder because webpack encore uses webpack5.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// .storybook/main.js&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stories&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stories/**/*.stories.mdx&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stories/**/*.stories.@(json)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;addons&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="cm"&gt;/* optional addons */&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;framework&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@storybook/server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Webpack Encore uses webpack5&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;builder&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@storybook/builder-webpack5&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Last configuration step, we need to configure the URL where the application is served (by default symfony-cli serves on port 8000). The url is arbitrary, it can be anything you want, but it must match the route defined in the Symfony application. Storybook server will concatenate the url with the &lt;code&gt;parameters.server.id&lt;/code&gt; value to get the full url to the component. It must be a valid absolute url.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// .storybook/preview.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parameters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`https://localhost:8000/storybook/component`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Creating a generic Symfony controller
&lt;/h2&gt;

&lt;p&gt;Back to the Symfony application, we need to create a controller behind the url defined in the Storybook configuration. It gets the &lt;code&gt;id&lt;/code&gt; from the url and the &lt;code&gt;args&lt;/code&gt; from the query string.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="c1"&gt;# src/Controller/StorybookController.php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Controller&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\HttpFoundation\Request&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\HttpFoundation\Response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\HttpKernel\Attribute\AsController&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\Routing\Annotation\Route&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Twig\Environment&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="na"&gt;#[AsController]&lt;/span&gt;
&lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StorybookController&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;Environment&lt;/span&gt; &lt;span class="nv"&gt;$twig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;'/storybook/component/{id}'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;// The id can contain slashes, so we need to use a regex&lt;/span&gt;
        &lt;span class="n"&gt;requirements&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'.+'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Response&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// $id is the path to the Twig template in the storybook/ directory&lt;/span&gt;
        &lt;span class="c1"&gt;// Args are read from the query parameters and sent to the template&lt;/span&gt;
        &lt;span class="nv"&gt;$template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'storybook/%s.html.twig'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'args'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s1"&gt;'id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="nv"&gt;$content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;twig&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// During development, storybook is served from a different port than the Symfony app&lt;/span&gt;
        &lt;span class="c1"&gt;// You can use nelmio/cors-bundle to set the Access-Control-Allow-Origin header correctly&lt;/span&gt;
        &lt;span class="nv"&gt;$headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Access-Control-Allow-Origin'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'http://localhost:6006'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP_OK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$headers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Creating a Twig template to render the component
&lt;/h2&gt;

&lt;p&gt;We have created our component in &lt;code&gt;templates/components/button.html.twig&lt;/code&gt; and we configured the stories in &lt;code&gt;stories/button.json&lt;/code&gt;. Now we need to create a Twig template that will render the component in the context of Storybook. This template will be used by the controller we created in the previous step.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight twig"&gt;&lt;code&gt;&lt;span class="c"&gt;{# templates/storybook/button.html.twig #}&lt;/span&gt;

&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'components/button.html.twig'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;args.label&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Button'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nv"&gt;size&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;args.size&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'medium'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nv"&gt;primary&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;args.primary&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'true'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;style&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;args.backgroundColor&lt;/span&gt; &lt;span class="nv"&gt;is&lt;/span&gt; &lt;span class="nv"&gt;defined&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;"background-color: #{args.backgroundColor}"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Running Storybook
&lt;/h2&gt;

&lt;p&gt;Finally, we can run Storybook and see our component in action.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run storybook

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; storybook
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; start-storybook &lt;span class="nt"&gt;-p&lt;/span&gt; 6006
...
╭───────────────────────────────────────────────────╮
│                                                   │
│   Storybook 6.5 &lt;span class="k"&gt;for &lt;/span&gt;Server started                │
│   4.56 s &lt;span class="k"&gt;for &lt;/span&gt;preview                              │
│                                                   │
│    Local:            http://localhost:6006/       │
│    On your network:  http://192.168.1.11:6006/    │
│                                                   │
╰───────────────────────────────────────────────────╯
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Time to play with the component and see how it behaves in different states.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This was my first experience with Storybook, and I'm very satisfied with the result. I hope this article helps you to get started and if you need to convince your frontend team that you don't need to switch to Node to use Storybook. Symfony is an amazing framework for building rich web applications and the need for a frontend framework to build the rich UI is being challenged by the &lt;a href="https://ux.symfony.com/" rel="noopener noreferrer"&gt;Symfony UX&lt;/a&gt; initiative. &lt;/p&gt;

&lt;p&gt;If you have any questions or feedback, feel free to comment below or &lt;a href="https://twitter.com/GromNaN" rel="noopener noreferrer"&gt;reach out to me on Twitter&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>storybook</category>
      <category>symfony</category>
      <category>php</category>
    </item>
    <item>
      <title>Configure Symfony Secrets with Hashicorp Vault</title>
      <dc:creator>Jérôme TAMARELLE</dc:creator>
      <pubDate>Thu, 20 Jan 2022 12:56:43 +0000</pubDate>
      <link>https://forem.com/gromnan/store-secrets-in-vault-with-symfony-51ai</link>
      <guid>https://forem.com/gromnan/store-secrets-in-vault-with-symfony-51ai</guid>
      <description>&lt;p&gt;tl;dr: Integrating Vault and Symfony does not require any PHP code. Using &lt;code&gt;vault-agent&lt;/code&gt; secrets variables can be dumped into &lt;code&gt;.env&lt;/code&gt; file. Dynamic secrets can even be used as feature flags.&lt;/p&gt;




&lt;p&gt;Prisma Media's websites and applications are mostly developed using Symfony framework. Many of them require secrets values: API keys, database credentials, private certificats… they needs to be treated carefully.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we choose Vault to store secrets
&lt;/h2&gt;

&lt;p&gt;If you are there, you may know that storing secrets in your Git repository is a terrible practice that could lead to severe security issues.&lt;/p&gt;

&lt;p&gt;A cryptographic &lt;a href="https://symfony.com/doc/current/configuration/secrets.html" rel="noopener noreferrer"&gt;"Vault" mecanism&lt;/a&gt; is proposed by Symfony. It uses encrypted secrets that can be spread in repositories and artifacts. This is a simple solution for basic needs. That has the advantage of being independent from any external system (simplicity, scalability).&lt;/p&gt;

&lt;p&gt;We preferred HashiCorp Vault, a server solution that we deployed on our infrastructure. It is centralized, auditable and can handle dynamic secrets. This open-source product is not tied to a cloud provider. Easy to &lt;a href="https://learn.hashicorp.com/tutorials/vault/getting-started-dev-server" rel="noopener noreferrer"&gt;run locally for dev&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vault works as a key-value store where secret variables are read and written using a REST API.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0akevfsc2dq2qstbrx6v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0akevfsc2dq2qstbrx6v.png" alt="Vault UI screenshot, editing secrets" width="800" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The need for frequent reload of secrets
&lt;/h2&gt;

&lt;p&gt;Even if we generate the most secure password for the database, it will leak somewhere: logs, APM, error pages... To be safe, credentials needs to change, change all the time.&lt;/p&gt;

&lt;p&gt;To get short-living secrets, Vault has a concept of &lt;a href="https://learn.hashicorp.com/tutorials/vault/getting-started-dynamic-secrets" rel="noopener noreferrer"&gt;Dynamic Secrets&lt;/a&gt;. Unlike key/value secrets where you had to put data into the store yourself, dynamic secrets are generated when they are accessed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxj23rgejo67sj279p39o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxj23rgejo67sj279p39o.png" alt="Dynamic Secrets in Vault" width="726" height="289"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not only we have to load the secrets from Vault, but also they have to be reloaded every time they change.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Dynamic config of Symfony with &lt;code&gt;.env&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1343842728547901441-25" src="https://platform.twitter.com/embed/Tweet.html?id=1343842728547901441"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1343842728547901441-25');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1343842728547901441&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;Thank you Fabien, this is exactly what I need: changing the values without redeploying the application.&lt;/p&gt;

&lt;p&gt;Unlike the YAML/PHP files in &lt;code&gt;config/&lt;/code&gt; directory, that are read only when the container cache is built, the &lt;code&gt;.env&lt;/code&gt; file are read on every HTTP request. Updating this file while the application is running is a good way to update its configuration without impacting performance.&lt;/p&gt;

&lt;p&gt;Read more: &lt;a href="https://speakerdeck.com/nicolasgrekas/configuring-symfony-from-localhost-to-high-availability" rel="noopener noreferrer"&gt;Configuring Symfony (slides by Nicolas Grekas)&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Vault Agent can write secrets into a file
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.vaultproject.io/docs/agent" rel="noopener noreferrer"&gt;vault-agent&lt;/a&gt; is a small utility that can be used as a cache proxy for the Vault server; or as a schedule to write secrets into a file using a template (&lt;a href="https://www.vaultproject.io/docs/agent/template#non-renewable-leased-secrets" rel="noopener noreferrer"&gt;every 5 minutes, configurable&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Example of vault-agent configuration for an app running on AWS EC2 instance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# vault.conf
vault {
  address = "https://vault.example.com"
  retry {
    num_retries = 5
  }
}

auto_auth {
  method "aws" {
    config = {
      type = "iam"
      role = "&amp;lt;iam role&amp;gt;"
      region = "eu-west-1"
      header_value = "vault.example.com"
    }
  }
}

template {
  source = "./.env.local.ctmpl"
  destination = "./.env.local"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The template maps Vault secrets to environment variables. The &lt;a href="https://www.vaultproject.io/docs/agent/template" rel="noopener noreferrer"&gt;templating syntax&lt;/a&gt; allows some flexibility, but it looks very primitive for a developer with Twig practice.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# .env.local.ctmpl
# This will generate a regular .env file
APP_ENV=prod

{{ with secret "secret/example/app" }}
APP_SECRET={{ .Data.data.APP_SECRET }}
{{ end }}

# Real environment variables can be read
{{ $env := (env "ENVIRONMENT") }}
{{ with secret (printf "secret/example/%s/database" $env) }}
DB_HOST={{ .Data.data.DB_HOST }}
DB_NAME={{ .Data.data.DB_NAME }}
DB_USER={{ .Data.data.DB_USER }}
DB_PASSWORD={{ .Data.data.DB_PASSWORD }}
{{ end }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, &lt;code&gt;vault agent&lt;/code&gt; can be launched with any process manager (&lt;code&gt;supervisord&lt;/code&gt; or &lt;code&gt;systemd&lt;/code&gt;) or scheduler (&lt;code&gt;crond&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Daemon &lt;span class="k"&gt;for &lt;/span&gt;prod server
&lt;span class="go"&gt;vault agent -config=vault.conf

&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Single run &lt;span class="k"&gt;for &lt;/span&gt;testing
&lt;span class="go"&gt;vault agent -config=vault.conf -exit-after-aut
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last security recommendation is to not write secrets on disks. We can create a &lt;code&gt;tmpfs&lt;/code&gt; volume and symlink from the project root to that volume.&lt;/p&gt;

&lt;p&gt;For kubernetes, vault-agent runs in a &lt;a href="https://www.vaultproject.io/docs/platform/k8s/injector" rel="noopener noreferrer"&gt;sidecar container&lt;/a&gt; that renders Vault secrets to a shared memory volume.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use dynamic configuration feature flag
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Feature_toggle" rel="noopener noreferrer"&gt;Feature flags&lt;/a&gt; are a benefit of using Vault and supporting dynamic configuration. Even if they are not secrets, flags can be stored in Vault. With fine tuned policies, product managers could manage feature flags and being rejected from other secrets.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example of feature flag to render a block in Twig
&lt;/h3&gt;

&lt;p&gt;In this example, we create a feature flag in Vault, with is a boolean to show or hide a "sales" block on a page.&lt;/p&gt;

&lt;p&gt;Create a variable in Vault KV:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;secret/example/app/&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"FEATURE_FLAG_SALES"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read this secret in the vault agent template&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# .env.local.ctmpl
{{ with secret "secret/example/app" }}
FEATURE_FLAG_SALES={{ .Data.data.FEATURE_FLAG_SALES }}
{{ end }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Share the value of the variable with Twig context.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/config/packages/twig.yaml&lt;/span&gt;
&lt;span class="na"&gt;twig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;feature_flag_sales&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%env(bool:FEATURE_FLAG_SALES)%"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the variable to render the block conditionally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight twig"&gt;&lt;code&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;feature_flag_sales&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;My conditional sales block&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endif&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the &lt;code&gt;vault-agent&lt;/code&gt;.&lt;br&gt;
When the variable updated, the block is shown or hidden after few minutes.&lt;/p&gt;

&lt;h1&gt;
  
  
  Read more...
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.hashicorp.com/tutorials/vault/agent-read-secrets" rel="noopener noreferrer"&gt;Tutorial: Read Secrets From Vault Using Vault Agent&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.hashicorp.com/blog/why-use-the-vault-agent-for-secrets-management" rel="noopener noreferrer"&gt;Article: Why Use the Vault Agent for Secrets Management?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>php</category>
      <category>symfony</category>
      <category>vault</category>
      <category>secrets</category>
    </item>
  </channel>
</rss>
