<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
 
 <title>The blog of Jean-François Lépine</title>
 <link href="https://blog.lepine.pro/atom.xml" rel="self"/>
 <link href="https://blog.lepine.pro"/>
 <updated>2026-05-20T14:11:46+00:00</updated>
 <id>https://blog.lepine.pro</id>
 <author>
   <name>Jean-François Lépine</name>
   <email></email>
 </author>

 
 <entry>
   <title>Qu'est-ce qu'un beau code, pour un LLM ?</title>
   <link href="https://blog.lepine.pro/qu-est-ce-qu-un-beau-code-pour-un-llm"/>
   <updated>2026-04-22T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/qu-est-ce-qu-un-beau-code-pour-un-llm</id>
   <content type="html">&lt;style&gt;
    .callout {
        margin: 2rem 0;
        padding: 1.2rem 1.5rem;
        border-left: 3px solid #2d6a4f;
        background: #e8f4f0;
        border-radius: 0 6px 6px 0;
    }
    .callout p { margin: 0; font-family: &apos;Lora&apos;, serif; font-style: italic; font-size: 1.05rem; color: #1c1c1c; line-height: 1.65; }
    .chart-block { margin: 2rem 0; background: #f5f3ef; border-radius: 8px; padding: 1.75rem; }
    .chart-block svg { width: 100%; height: auto; }
    .chart-title { font-size: 0.75rem; font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase; color: #6b6b6b; margin-bottom: 1.5rem; }
    .chart-legend { display: flex; flex-wrap: wrap; gap: 1.25rem; margin-top: 1rem; padding-top: 1rem; border-top: 1px solid #e2dfd9; }
    .chart-legend-item { display: flex; align-items: center; gap: 0.5rem; font-size: 0.82rem; color: #6b6b6b; }
    .chart-legend-color { width: 24px; height: 3px; border-radius: 2px; flex-shrink: 0; }
    .source-ref { font-size: 0.82rem; color: #6b6b6b; font-style: italic; margin-top: 0.5rem; }
    figure { margin: 2rem 0; }
    figure img { width: 100%; height: auto; border-radius: 8px; display: block; }
    figure figcaption { font-size: 0.88rem; color: #6b6b6b; font-style: italic; text-align: center; margin-top: 0.75rem; line-height: 1.5; }
    .protocol-flow { display: grid; grid-template-columns: 1fr auto 1fr auto 1fr; gap: 0.5rem; align-items: stretch; margin-top: 0.5rem; }
    .protocol-step { background: #fff; border: 1px solid #e2dfd9; border-radius: 6px; padding: 1rem; display: flex; flex-direction: column; justify-content: center; }
    .protocol-step-num { font-size: 0.7rem; font-weight: 700; color: #2d6a4f; letter-spacing: 0.1em; text-transform: uppercase; margin-bottom: 0.4rem; }
    .protocol-step-title { font-size: 0.92rem; font-weight: 600; color: #1c1c1c; margin-bottom: 0.4rem; line-height: 1.3; }
    .protocol-step-desc { font-size: 0.78rem; color: #6b6b6b; line-height: 1.5; }
    .protocol-arrow { display: flex; align-items: center; color: #b8b8b8; font-size: 1.4rem; }
    .protocol-total { margin-top: 1rem; padding: 0.75rem 1rem; background: #2d6a4f; color: #fff; border-radius: 6px; font-size: 0.88rem; text-align: center; font-weight: 600; }
    .kpi-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.75rem; }
    .kpi-card { background: #fff; border: 1px solid #e2dfd9; border-radius: 6px; padding: 1rem 1.1rem; }
    .kpi-label { font-size: 0.7rem; font-weight: 600; color: #6b6b6b; letter-spacing: 0.1em; text-transform: uppercase; margin-bottom: 0.5rem; }
    .kpi-values { font-size: 1.15rem; font-weight: 700; color: #1c1c1c; }
    .kpi-arrow { color: #b8b8b8; margin: 0 0.4rem; font-weight: 400; }
    .kpi-delta { display: inline-block; margin-left: 0.5rem; font-size: 0.78rem; font-weight: 700; padding: 0.15rem 0.45rem; border-radius: 3px; }
    .kpi-delta.up { background: #e8f4f0; color: #2d6a4f; }
    .kpi-delta.down { background: #fde8e8; color: #c0392b; }
    .kpi-delta.flat { background: #f5f3ef; color: #6b6b6b; }
    .oscillation-track { display: grid; grid-template-columns: repeat(6, 1fr); gap: 0.5rem; margin-top: 0.5rem; }
    .oscillation-cell { background: #fff; border: 1px solid #e2dfd9; border-radius: 6px; padding: 0.75rem 0.5rem; text-align: center; }
    .oscillation-version { font-size: 0.68rem; font-weight: 700; color: #6b6b6b; letter-spacing: 0.1em; text-transform: uppercase; margin-bottom: 0.4rem; }
    .oscillation-name { font-family: &apos;Courier New&apos;, monospace; font-size: 0.78rem; font-weight: 600; padding: 0.35rem 0.4rem; border-radius: 4px; }
    .oscillation-name.a { background: #e8f4f0; color: #2d6a4f; }
    .oscillation-name.b { background: #fdf6e3; color: #b8860b; }
    .oscillation-name.neutral { background: #f5f3ef; color: #6b6b6b; }
    @media (max-width: 600px) {
        .protocol-flow { grid-template-columns: 1fr; }
        .protocol-arrow { transform: rotate(90deg); justify-content: center; }
        .kpi-grid { grid-template-columns: 1fr; }
        .oscillation-track { grid-template-columns: repeat(3, 1fr); }
    }
&lt;/style&gt;

&lt;p&gt;Chaque développeur a sa propre idée de ce qu’est un beau code.&lt;/p&gt;

&lt;p&gt;Il y a ceux qui veulent des méthodes courtes, quitte à les multiplier. Ceux qui préfèrent des méthodes longues mais cohérentes. Ceux pour qui un commentaire bien placé sauve une vie, et ceux qui considèrent qu’un bon code n’a pas besoin de commentaires. Il y a des écoles. Robert C. Martin a fait carrière sur l’une d’elles avec &lt;a href=&quot;https://www.oreilly.com/library/view/clean-code-a/9780136083238/&quot;&gt;Clean Code&lt;/a&gt;. Kent Beck sur une autre. Et des équipes entières se déchirent depuis des années sur ces questions.&lt;/p&gt;

&lt;p&gt;Une question qu’on s’est rarement posée avec précision : &lt;strong&gt;&lt;em&gt;et les LLMs, ils ont quel style ?&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On sait bien qu’ils ont des préférences. Quand on demande à GPT, Claude ou Gemini de générer du code, on voit assez vite des patterns. Mais c’est anecdotique. Personne, à ma connaissance, n’avait fait une étude vraiment systématique de la &lt;strong&gt;signature stylistique&lt;/strong&gt; d’un LLM. Combien de méthodes en moyenne ? Combien de commentaires conservés ? Combien d’identifiants renommés ?&lt;/p&gt;

&lt;p&gt;Trois chercheurs de l’université de la Sarre (Norman Peitek, Julia Hess et Sven Apel) viennent de publier sur arXiv un papier qui répond à cette question. Le titre est &lt;a href=&quot;https://arxiv.org/abs/2602.21833&quot;&gt;From Restructuring to Stabilization: A Large-Scale Experiment on Iterative Code Readability Refactoring with Large Language Models&lt;/a&gt;. L’idée est simple : prendre 230 snippets Java, demander cinq fois de suite à GPT-5.1 de les refactoriser pour améliorer la lisibilité, et regarder ce qui se passe.&lt;/p&gt;

&lt;p&gt;Le résultat est, à mon avis, &lt;strong&gt;l’un des plus utiles de ces derniers mois&lt;/strong&gt; pour qui utilise des LLMs en production. Et le point important n’est pas que GPT-5.1 refactorise. &lt;strong&gt;C’est qu’il refactorise toujours dans une direction reconnaissable&lt;/strong&gt; - la sienne.&lt;/p&gt;

&lt;h2 id=&quot;le-protocole&quot;&gt;Le protocole&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/images/llm-beau-code-fonctionnement.webp&quot; alt=&quot;Schéma du protocole : 230 fichiers Java, 3 variantes par fichier (Original, Meaningless, NoComment), 5 itérations × 3 prompts avec GPT-5.1, soit 10 350 snippets analysés au total&quot; /&gt;&lt;/p&gt;
&lt;p class=&quot;source-ref&quot;&gt;Source : Peitek, Hess &amp;amp; Apel, arXiv:2602.21833, 2026.&lt;/p&gt;

&lt;p&gt;Les auteurs ont pris &lt;strong&gt;230 fichiers Java&lt;/strong&gt; du dépôt GitHub &lt;em&gt;The Algorithms – Java&lt;/em&gt;, un repo éducatif d’implémentations d’algorithmes classiques. Le choix n’est pas anodin : code idiomatique, conventions homogènes, licence MIT donc reproductible. Ils ont filtré pour ne garder que des fichiers entre 50 et 200 lignes, avec au moins 50 % de code (pas que des commentaires).&lt;/p&gt;

&lt;p&gt;À partir de chaque fichier, ils ont créé &lt;strong&gt;trois variantes&lt;/strong&gt; :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Original&lt;/strong&gt; : le code tel quel, propre, idiomatique.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Meaningless&lt;/strong&gt; : tous les identifiants (classes, méthodes, variables) ont été remplacés par des noms sans signification. Tous les commentaires ont été rendus aléatoires. La structure du code est préservée mais sa lisibilité humaine est volontairement détruite.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;NoComment&lt;/strong&gt; : tous les commentaires sont retirés, sans rien d’autre toucher.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ensuite, pour chaque variante, ils ont demandé à GPT-5.1 (temperature = 0, donc déterministe) de refactoriser le code &lt;strong&gt;cinq fois de suite&lt;/strong&gt;, en utilisant trois formulations de prompt :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;PromptGeneral&lt;/code&gt; : « Refactor this code for improved readability. »&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;PromptMeaning&lt;/code&gt; : « Refactor this code for improved readability, especially with respect to identifier naming. »&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;PromptComments&lt;/code&gt; : « Refactor this code for improved readability, especially with respect to comments. »&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Total : 230 snippets × 3 variantes × 3 prompts × 5 itérations = &lt;strong&gt;10 350 snippets&lt;/strong&gt; produits par le modèle. Un outil de diff fin (&lt;em&gt;DiffParser&lt;/em&gt;) catégorise ensuite chaque ligne changée : rename, syntaxe seule, changement de commentaire, modification de code, ou changement mixte.&lt;/p&gt;

&lt;p&gt;Étude de bonne taille, et surtout très bien instrumentée.&lt;/p&gt;

&lt;h2 id=&quot;découverte-1--une-dynamique-en-deux-phases&quot;&gt;Découverte 1 : Une dynamique en deux phases&lt;/h2&gt;

&lt;div class=&quot;chart-block&quot;&gt;
    &lt;p class=&quot;chart-title&quot;&gt;Pourcentage de lignes inchangées à chaque itération&lt;/p&gt;
    &lt;svg viewBox=&quot;0 0 620 320&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; role=&quot;img&quot; aria-label=&quot;Courbe montrant le pourcentage de lignes inchangées passant de 45% à 92% en cinq itérations&quot;&gt;
        &lt;!-- Phase backgrounds --&gt;
        &lt;rect x=&quot;60&quot; y=&quot;20&quot; width=&quot;184&quot; height=&quot;240&quot; fill=&quot;#fdf6e3&quot; opacity=&quot;0.6&quot; /&gt;
        &lt;rect x=&quot;244&quot; y=&quot;20&quot; width=&quot;316&quot; height=&quot;240&quot; fill=&quot;#e8f4f0&quot; opacity=&quot;0.6&quot; /&gt;
        &lt;!-- Phase labels (placed at the bottom inside the coloured bands to avoid overlap with data labels) --&gt;
        &lt;text x=&quot;152&quot; y=&quot;252&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; font-weight=&quot;700&quot; fill=&quot;#b8860b&quot; font-family=&quot;system-ui, sans-serif&quot; letter-spacing=&quot;0.05em&quot;&gt;RESTRUCTURATION&lt;/text&gt;
        &lt;text x=&quot;402&quot; y=&quot;252&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; font-weight=&quot;700&quot; fill=&quot;#2d6a4f&quot; font-family=&quot;system-ui, sans-serif&quot; letter-spacing=&quot;0.05em&quot;&gt;STABILISATION&lt;/text&gt;
        &lt;!-- Axes --&gt;
        &lt;line x1=&quot;60&quot; y1=&quot;20&quot; x2=&quot;60&quot; y2=&quot;260&quot; stroke=&quot;#1c1c1c&quot; stroke-width=&quot;1&quot; /&gt;
        &lt;line x1=&quot;60&quot; y1=&quot;260&quot; x2=&quot;560&quot; y2=&quot;260&quot; stroke=&quot;#1c1c1c&quot; stroke-width=&quot;1&quot; /&gt;
        &lt;!-- Horizontal grid --&gt;
        &lt;line x1=&quot;60&quot; y1=&quot;68&quot; x2=&quot;560&quot; y2=&quot;68&quot; stroke=&quot;#e2dfd9&quot; stroke-width=&quot;0.5&quot; stroke-dasharray=&quot;4&quot; /&gt;
        &lt;line x1=&quot;60&quot; y1=&quot;116&quot; x2=&quot;560&quot; y2=&quot;116&quot; stroke=&quot;#e2dfd9&quot; stroke-width=&quot;0.5&quot; stroke-dasharray=&quot;4&quot; /&gt;
        &lt;line x1=&quot;60&quot; y1=&quot;164&quot; x2=&quot;560&quot; y2=&quot;164&quot; stroke=&quot;#e2dfd9&quot; stroke-width=&quot;0.5&quot; stroke-dasharray=&quot;4&quot; /&gt;
        &lt;line x1=&quot;60&quot; y1=&quot;212&quot; x2=&quot;560&quot; y2=&quot;212&quot; stroke=&quot;#e2dfd9&quot; stroke-width=&quot;0.5&quot; stroke-dasharray=&quot;4&quot; /&gt;
        &lt;!-- Y axis labels --&gt;
        &lt;text x=&quot;52&quot; y=&quot;24&quot; text-anchor=&quot;end&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;100%&lt;/text&gt;
        &lt;text x=&quot;52&quot; y=&quot;72&quot; text-anchor=&quot;end&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;80%&lt;/text&gt;
        &lt;text x=&quot;52&quot; y=&quot;120&quot; text-anchor=&quot;end&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;60%&lt;/text&gt;
        &lt;text x=&quot;52&quot; y=&quot;168&quot; text-anchor=&quot;end&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;40%&lt;/text&gt;
        &lt;text x=&quot;52&quot; y=&quot;216&quot; text-anchor=&quot;end&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;20%&lt;/text&gt;
        &lt;text x=&quot;52&quot; y=&quot;264&quot; text-anchor=&quot;end&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;0%&lt;/text&gt;
        &lt;!-- X axis labels --&gt;
        &lt;text x=&quot;120&quot; y=&quot;278&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;v0 → v1&lt;/text&gt;
        &lt;text x=&quot;222&quot; y=&quot;278&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;v1 → v2&lt;/text&gt;
        &lt;text x=&quot;324&quot; y=&quot;278&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;v2 → v3&lt;/text&gt;
        &lt;text x=&quot;426&quot; y=&quot;278&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;v3 → v4&lt;/text&gt;
        &lt;text x=&quot;528&quot; y=&quot;278&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;v4 → v5&lt;/text&gt;
        &lt;text x=&quot;310&quot; y=&quot;305&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;Transition entre versions successives&lt;/text&gt;
        &lt;!-- Curve : 45, 76, 86, 89, 92 → y = 260 - 240*(pct/100) --&gt;
        &lt;polyline points=&quot;120,152 222,77.6 324,53.6 426,46.4 528,39.2&quot; fill=&quot;none&quot; stroke=&quot;#2d6a4f&quot; stroke-width=&quot;2.5&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; /&gt;
        &lt;!-- Points + labels --&gt;
        &lt;circle cx=&quot;120&quot; cy=&quot;152&quot; r=&quot;5&quot; fill=&quot;#2d6a4f&quot; /&gt;
        &lt;text x=&quot;120&quot; y=&quot;142&quot; text-anchor=&quot;middle&quot; font-size=&quot;12&quot; font-weight=&quot;700&quot; fill=&quot;#1c1c1c&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;45 %&lt;/text&gt;
        &lt;circle cx=&quot;222&quot; cy=&quot;77.6&quot; r=&quot;5&quot; fill=&quot;#2d6a4f&quot; /&gt;
        &lt;text x=&quot;222&quot; y=&quot;68&quot; text-anchor=&quot;middle&quot; font-size=&quot;12&quot; font-weight=&quot;700&quot; fill=&quot;#1c1c1c&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;76 %&lt;/text&gt;
        &lt;circle cx=&quot;324&quot; cy=&quot;53.6&quot; r=&quot;5&quot; fill=&quot;#2d6a4f&quot; /&gt;
        &lt;text x=&quot;324&quot; y=&quot;44&quot; text-anchor=&quot;middle&quot; font-size=&quot;12&quot; font-weight=&quot;700&quot; fill=&quot;#1c1c1c&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;86 %&lt;/text&gt;
        &lt;circle cx=&quot;426&quot; cy=&quot;46.4&quot; r=&quot;5&quot; fill=&quot;#2d6a4f&quot; /&gt;
        &lt;text x=&quot;426&quot; y=&quot;36.5&quot; text-anchor=&quot;middle&quot; font-size=&quot;12&quot; font-weight=&quot;700&quot; fill=&quot;#1c1c1c&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;89 %&lt;/text&gt;
        &lt;circle cx=&quot;528&quot; cy=&quot;39.2&quot; r=&quot;5&quot; fill=&quot;#2d6a4f&quot; /&gt;
        &lt;text x=&quot;528&quot; y=&quot;29&quot; text-anchor=&quot;middle&quot; font-size=&quot;12&quot; font-weight=&quot;700&quot; fill=&quot;#1c1c1c&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;92 %&lt;/text&gt;
    &lt;/svg&gt;
    &lt;p class=&quot;source-ref&quot;&gt;Variante Original, PromptGeneral. Source : Peitek et al., 2026.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;Il y a deux phases distinctes dans le processus, et c’est reproductible.&lt;/p&gt;

&lt;p&gt;À la première refactorisation (v0 → v1), GPT-5.1 touche beaucoup de code. Seulement &lt;strong&gt;45 % des lignes&lt;/strong&gt; restent inchangées. Il renomme, casse en plusieurs méthodes, supprime des commentaires, en ajoute d’autres, change le format. &lt;strong&gt;C’est une vraie restructuration.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;À la deuxième itération (v1 → v2), c’est déjà différent. &lt;strong&gt;76 % des lignes&lt;/strong&gt; ne bougent plus. Les changements deviennent &lt;strong&gt;locaux, chirurgicaux&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;À partir de la troisième, on entre dans une &lt;strong&gt;phase de stabilisation&lt;/strong&gt; : &lt;strong&gt;86 %, 89 %, puis 92 %&lt;/strong&gt; de lignes inchangées entre versions successives. Le modèle a trouvé sa version « finale » et n’y touche plus qu’à la marge.&lt;/p&gt;

&lt;figure&gt;
    &lt;img src=&quot;/images/llm-beau-code-convergence.webp&quot; alt=&quot;Schéma illustrant la convergence : de v0 à v5, le LLM modifie de moins en moins le code, passant de gros changements à un style stable&quot; /&gt;
    &lt;figcaption&gt;Le principe de la convergence : à chaque itération, le LLM modifie moins le code, jusqu&apos;à se stabiliser.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Première information utile : &lt;strong&gt;un refactoring LLM n’est pas un processus convergent rapide&lt;/strong&gt;. Il y a une vraie phase de restructuration qui mobilise une à deux itérations. Si on demande à GPT de refactoriser une fois et qu’on compare au résultat, on voit beaucoup de changements. Si on lui redemande, on voit encore beaucoup de changements - mais &lt;strong&gt;d’une autre nature&lt;/strong&gt;. Ce n’est pas la même chose, et pour qui industrialise du refactoring assisté, &lt;strong&gt;la distinction change tout&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;découverte-2--le-llm-converge-indépendamment-du-point-de-départ&quot;&gt;Découverte 2 : Le LLM converge, indépendamment du point de départ&lt;/h2&gt;

&lt;div class=&quot;chart-block&quot;&gt;
    &lt;p class=&quot;chart-title&quot;&gt;Convergence du % de lignes inchangées : les trois variantes&lt;/p&gt;
    &lt;svg viewBox=&quot;0 0 620 320&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; role=&quot;img&quot; aria-label=&quot;Trois courbes Original, Meaningless et NoComment partant de hauteurs différentes et convergeant à droite&quot;&gt;
        &lt;!-- Convergence zone --&gt;
        &lt;rect x=&quot;430&quot; y=&quot;20&quot; width=&quot;130&quot; height=&quot;240&quot; fill=&quot;#e8f4f0&quot; opacity=&quot;0.7&quot; /&gt;
        &lt;text x=&quot;495&quot; y=&quot;38&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; font-weight=&quot;700&quot; fill=&quot;#2d6a4f&quot; font-family=&quot;system-ui, sans-serif&quot; letter-spacing=&quot;0.05em&quot;&gt;ZONE DE CONVERGENCE&lt;/text&gt;
        &lt;!-- Axes --&gt;
        &lt;line x1=&quot;60&quot; y1=&quot;20&quot; x2=&quot;60&quot; y2=&quot;260&quot; stroke=&quot;#1c1c1c&quot; stroke-width=&quot;1&quot; /&gt;
        &lt;line x1=&quot;60&quot; y1=&quot;260&quot; x2=&quot;560&quot; y2=&quot;260&quot; stroke=&quot;#1c1c1c&quot; stroke-width=&quot;1&quot; /&gt;
        &lt;!-- Grid --&gt;
        &lt;line x1=&quot;60&quot; y1=&quot;68&quot; x2=&quot;560&quot; y2=&quot;68&quot; stroke=&quot;#e2dfd9&quot; stroke-width=&quot;0.5&quot; stroke-dasharray=&quot;4&quot; /&gt;
        &lt;line x1=&quot;60&quot; y1=&quot;116&quot; x2=&quot;560&quot; y2=&quot;116&quot; stroke=&quot;#e2dfd9&quot; stroke-width=&quot;0.5&quot; stroke-dasharray=&quot;4&quot; /&gt;
        &lt;line x1=&quot;60&quot; y1=&quot;164&quot; x2=&quot;560&quot; y2=&quot;164&quot; stroke=&quot;#e2dfd9&quot; stroke-width=&quot;0.5&quot; stroke-dasharray=&quot;4&quot; /&gt;
        &lt;line x1=&quot;60&quot; y1=&quot;212&quot; x2=&quot;560&quot; y2=&quot;212&quot; stroke=&quot;#e2dfd9&quot; stroke-width=&quot;0.5&quot; stroke-dasharray=&quot;4&quot; /&gt;
        &lt;!-- Y labels --&gt;
        &lt;text x=&quot;52&quot; y=&quot;24&quot; text-anchor=&quot;end&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;100%&lt;/text&gt;
        &lt;text x=&quot;52&quot; y=&quot;72&quot; text-anchor=&quot;end&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;80%&lt;/text&gt;
        &lt;text x=&quot;52&quot; y=&quot;120&quot; text-anchor=&quot;end&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;60%&lt;/text&gt;
        &lt;text x=&quot;52&quot; y=&quot;168&quot; text-anchor=&quot;end&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;40%&lt;/text&gt;
        &lt;text x=&quot;52&quot; y=&quot;216&quot; text-anchor=&quot;end&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;20%&lt;/text&gt;
        &lt;text x=&quot;52&quot; y=&quot;264&quot; text-anchor=&quot;end&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;0%&lt;/text&gt;
        &lt;!-- X labels --&gt;
        &lt;text x=&quot;120&quot; y=&quot;278&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;v0 → v1&lt;/text&gt;
        &lt;text x=&quot;222&quot; y=&quot;278&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;v1 → v2&lt;/text&gt;
        &lt;text x=&quot;324&quot; y=&quot;278&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;v2 → v3&lt;/text&gt;
        &lt;text x=&quot;426&quot; y=&quot;278&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;v3 → v4&lt;/text&gt;
        &lt;text x=&quot;528&quot; y=&quot;278&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;v4 → v5&lt;/text&gt;
        &lt;!-- Original: 45, 76, 86, 89, 92 --&gt;
        &lt;polyline points=&quot;120,152 222,77.6 324,53.6 426,46.4 528,39.2&quot; fill=&quot;none&quot; stroke=&quot;#2d6a4f&quot; stroke-width=&quot;2.5&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; /&gt;
        &lt;circle cx=&quot;120&quot; cy=&quot;152&quot; r=&quot;4&quot; fill=&quot;#2d6a4f&quot; /&gt;
        &lt;circle cx=&quot;528&quot; cy=&quot;39.2&quot; r=&quot;4&quot; fill=&quot;#2d6a4f&quot; /&gt;
        &lt;!-- NoComment: 44, 71, 82, 87, 89 --&gt;
        &lt;polyline points=&quot;120,154.4 222,89.6 324,63.2 426,51.2 528,46.4&quot; fill=&quot;none&quot; stroke=&quot;#c77d1a&quot; stroke-width=&quot;2.5&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; /&gt;
        &lt;circle cx=&quot;120&quot; cy=&quot;154.4&quot; r=&quot;4&quot; fill=&quot;#c77d1a&quot; /&gt;
        &lt;circle cx=&quot;528&quot; cy=&quot;46.4&quot; r=&quot;4&quot; fill=&quot;#c77d1a&quot; /&gt;
        &lt;!-- Meaningless: 31, 65, 80, 87, 90 --&gt;
        &lt;polyline points=&quot;120,185.6 222,104 324,68 426,51.2 528,44&quot; fill=&quot;none&quot; stroke=&quot;#c0392b&quot; stroke-width=&quot;2.5&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; /&gt;
        &lt;circle cx=&quot;120&quot; cy=&quot;185.6&quot; r=&quot;4&quot; fill=&quot;#c0392b&quot; /&gt;
        &lt;circle cx=&quot;528&quot; cy=&quot;44&quot; r=&quot;4&quot; fill=&quot;#c0392b&quot; /&gt;
    &lt;/svg&gt;
    &lt;div class=&quot;chart-legend&quot;&gt;
        &lt;div class=&quot;chart-legend-item&quot;&gt;&lt;span class=&quot;chart-legend-color&quot; style=&quot;background:#2d6a4f&quot;&gt;&lt;/span&gt;&lt;strong&gt;Original&lt;/strong&gt; - code propre, idiomatique&lt;/div&gt;
        &lt;div class=&quot;chart-legend-item&quot;&gt;&lt;span class=&quot;chart-legend-color&quot; style=&quot;background:#c77d1a&quot;&gt;&lt;/span&gt;&lt;strong&gt;NoComment&lt;/strong&gt; - commentaires retirés&lt;/div&gt;
        &lt;div class=&quot;chart-legend-item&quot;&gt;&lt;span class=&quot;chart-legend-color&quot; style=&quot;background:#c0392b&quot;&gt;&lt;/span&gt;&lt;strong&gt;Meaningless&lt;/strong&gt; - identifiants obfusqués&lt;/div&gt;
    &lt;/div&gt;
    &lt;p class=&quot;source-ref&quot;&gt;Pourcentage de lignes inchangées par transition. Les trois courbes finissent dans la même zone (~89-92 %). Source : Peitek et al., 2026.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;C’est ici qu’on entre dans &lt;strong&gt;le résultat le plus contre-intuitif du papier&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Pour vérifier si la convergence observée est vraie (et pas un artefact du code de départ), les auteurs ont mesuré ce qui se passe avec les variantes &lt;strong&gt;Meaningless&lt;/strong&gt; et &lt;strong&gt;NoComment&lt;/strong&gt;. Souvenez-vous : Meaningless est volontairement illisible, NoComment a perdu tous ses commentaires.&lt;/p&gt;

&lt;p&gt;Si le modèle se contentait de polir le code qu’on lui donne, on s’attendrait à ce que les trois variantes restent différentes après cinq itérations. La Meaningless aurait des noms peu informatifs, la NoComment aurait peu de commentaires. Logique.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ce n’est pas du tout ce qui se passe.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Les trois variantes &lt;strong&gt;convergent vers des représentations très proches&lt;/strong&gt; après cinq itérations. Sur le nombre de méthodes : les trois partent à 3,1 méthodes en moyenne (même structure de base) et finissent toutes autour de &lt;strong&gt;6 méthodes&lt;/strong&gt; après cinq refactorings. Sur le nombre de lignes de code : les trois partent à 56 lignes et finissent autour de &lt;strong&gt;73&lt;/strong&gt;. Sur les commentaires inline : Original et Meaningless partent à 1,3, et toutes les variantes convergent vers &lt;strong&gt;0,2&lt;/strong&gt; - quasi-élimination.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Le LLM a une représentation interne de ce qu’est du « code lisible ». Cette représentation existe - elle est démontrée empiriquement par la convergence. Et elle ne dépend que marginalement du code de départ.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Le LLM ne se contente pas de polir le code qu’on lui donne en respectant son style. &lt;strong&gt;Il le pousse vers son propre style.&lt;/strong&gt; Et cette « cible » est suffisamment stable pour que, partant de trois codes très différents, on aboutisse à trois codes très proches.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Déléguer le refactoring à un LLM, ce n’est pas neutre stylistiquement.&lt;/strong&gt; C’est adopter implicitement le style du modèle.&lt;/p&gt;

&lt;h2 id=&quot;découverte-3--la-signature-stylistique-de-gpt-51&quot;&gt;Découverte 3 : La signature stylistique de GPT-5.1&lt;/h2&gt;

&lt;div class=&quot;chart-block&quot;&gt;
    &lt;p class=&quot;chart-title&quot;&gt;Signature stylistique GPT-5.1 : après 5 itérations&lt;/p&gt;
    &lt;div class=&quot;kpi-grid&quot;&gt;
        &lt;div class=&quot;kpi-card&quot;&gt;
            &lt;div class=&quot;kpi-label&quot;&gt;Méthodes par fichier&lt;/div&gt;
            &lt;div class=&quot;kpi-values&quot;&gt;3,1 &lt;span class=&quot;kpi-arrow&quot;&gt;→&lt;/span&gt; 6,0 &lt;span class=&quot;kpi-delta up&quot;&gt;+93 %&lt;/span&gt;&lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;kpi-card&quot;&gt;
            &lt;div class=&quot;kpi-label&quot;&gt;Lignes de code&lt;/div&gt;
            &lt;div class=&quot;kpi-values&quot;&gt;58 &lt;span class=&quot;kpi-arrow&quot;&gt;→&lt;/span&gt; 73 &lt;span class=&quot;kpi-delta up&quot;&gt;+26 %&lt;/span&gt;&lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;kpi-card&quot;&gt;
            &lt;div class=&quot;kpi-label&quot;&gt;Commentaires inline&lt;/div&gt;
            &lt;div class=&quot;kpi-values&quot;&gt;1,3 &lt;span class=&quot;kpi-arrow&quot;&gt;→&lt;/span&gt; 0,2 &lt;span class=&quot;kpi-delta down&quot;&gt;−85 %&lt;/span&gt;&lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;kpi-card&quot;&gt;
            &lt;div class=&quot;kpi-label&quot;&gt;Lignes vides&lt;/div&gt;
            &lt;div class=&quot;kpi-values&quot;&gt;tendance ↗ &lt;span class=&quot;kpi-delta flat&quot;&gt;stable à partir de v3&lt;/span&gt;&lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;p class=&quot;source-ref&quot;&gt;Moyennes sur 230 snippets Java, variante Original, PromptGeneral. Source : Peitek et al., 2026.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;Sur les 230 snippets refactorisés cinq fois, voici les tendances mesurées :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Le &lt;strong&gt;nombre de méthodes&lt;/strong&gt; est passé en moyenne de 3,1 à 6 par fichier. Presque doublé. GPT-5.1 croit à la décomposition. Une longue méthode devient plusieurs petites.&lt;/li&gt;
  &lt;li&gt;Le &lt;strong&gt;nombre de lignes de code&lt;/strong&gt; augmente régulièrement à chaque itération, de 58 à 73. Le découpage en plus de méthodes ne se fait pas à coût zéro : signatures supplémentaires, appels, parfois paramètres.&lt;/li&gt;
  &lt;li&gt;Les &lt;strong&gt;commentaires inline&lt;/strong&gt; (du type &lt;code&gt;int x = 0; // counter&lt;/code&gt;) sont quasiment éliminés. De 1,3 à 0,2 en moyenne.&lt;/li&gt;
  &lt;li&gt;Les &lt;strong&gt;lignes vides&lt;/strong&gt; augmentent progressivement. Le code devient plus aéré, plus respirable visuellement.&lt;/li&gt;
  &lt;li&gt;Les &lt;strong&gt;identifiants&lt;/strong&gt; sont normalisés vers des conventions Java standard (camelCase, noms longs et descriptifs, suffixes explicites comme &lt;code&gt;Counter&lt;/code&gt;, &lt;code&gt;Result&lt;/code&gt;, &lt;code&gt;Helper&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Méthodes courtes et nombreuses, suppression des commentaires inline, aération à la &lt;a href=&quot;https://peps.python.org/pep-0008/&quot;&gt;PEP-8&lt;/a&gt;, noms longs et descriptifs : dit autrement, et en forçant un peu le trait, &lt;strong&gt;dans cette expérience, GPT-5.1 se comporte comme un disciple de &lt;em&gt;Clean Code&lt;/em&gt;.&lt;/strong&gt; On peut raisonnablement soupçonner que ce type de littérature a influencé les patterns appris, sans pouvoir le démontrer.&lt;/p&gt;

&lt;p&gt;Ce qui compte ici, c’est la &lt;strong&gt;netteté&lt;/strong&gt; du pattern - reproductible, mesurable, sur 230 codes différents. Et c’est précisément ce qui rend le sujet sensible : &lt;strong&gt;beaucoup d’équipes ne se reconnaissent pas dans Clean Code&lt;/strong&gt;, et se retrouvent malgré tout à recevoir ce style en sortie de leur LLM.&lt;/p&gt;

&lt;h2 id=&quot;découverte-4--le-llm-ne-sait-pas-sarrêter&quot;&gt;Découverte 4 : Le LLM ne sait pas s’arrêter&lt;/h2&gt;

&lt;div class=&quot;chart-block&quot;&gt;
    &lt;p class=&quot;chart-title&quot;&gt;Similarité entre versions successives : code déjà propre&lt;/p&gt;
    &lt;svg viewBox=&quot;0 0 620 320&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; role=&quot;img&quot; aria-label=&quot;Courbe de similarité montant de 0,86 à 0,90 sans jamais toucher la ligne 1,00&quot;&gt;
        &lt;!-- Axes : y=20 represents 1.00, y=260 represents 0.70. range = 0.30 over 240 px → 800 px per unit --&gt;
        &lt;!-- Cap line at 1.00 → y = 20 --&gt;
        &lt;line x1=&quot;60&quot; y1=&quot;20&quot; x2=&quot;560&quot; y2=&quot;20&quot; stroke=&quot;#c0392b&quot; stroke-width=&quot;1.5&quot; stroke-dasharray=&quot;6 4&quot; /&gt;
        &lt;text x=&quot;560&quot; y=&quot;14&quot; text-anchor=&quot;end&quot; font-size=&quot;11&quot; font-weight=&quot;700&quot; fill=&quot;#c0392b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;1,00 - le modèle ne touche plus à rien&lt;/text&gt;
        &lt;!-- Axes --&gt;
        &lt;line x1=&quot;60&quot; y1=&quot;20&quot; x2=&quot;60&quot; y2=&quot;260&quot; stroke=&quot;#1c1c1c&quot; stroke-width=&quot;1&quot; /&gt;
        &lt;line x1=&quot;60&quot; y1=&quot;260&quot; x2=&quot;560&quot; y2=&quot;260&quot; stroke=&quot;#1c1c1c&quot; stroke-width=&quot;1&quot; /&gt;
        &lt;!-- Grid (every 0.05) --&gt;
        &lt;line x1=&quot;60&quot; y1=&quot;60&quot; x2=&quot;560&quot; y2=&quot;60&quot; stroke=&quot;#e2dfd9&quot; stroke-width=&quot;0.5&quot; stroke-dasharray=&quot;4&quot; /&gt;
        &lt;line x1=&quot;60&quot; y1=&quot;100&quot; x2=&quot;560&quot; y2=&quot;100&quot; stroke=&quot;#e2dfd9&quot; stroke-width=&quot;0.5&quot; stroke-dasharray=&quot;4&quot; /&gt;
        &lt;line x1=&quot;60&quot; y1=&quot;140&quot; x2=&quot;560&quot; y2=&quot;140&quot; stroke=&quot;#e2dfd9&quot; stroke-width=&quot;0.5&quot; stroke-dasharray=&quot;4&quot; /&gt;
        &lt;line x1=&quot;60&quot; y1=&quot;180&quot; x2=&quot;560&quot; y2=&quot;180&quot; stroke=&quot;#e2dfd9&quot; stroke-width=&quot;0.5&quot; stroke-dasharray=&quot;4&quot; /&gt;
        &lt;line x1=&quot;60&quot; y1=&quot;220&quot; x2=&quot;560&quot; y2=&quot;220&quot; stroke=&quot;#e2dfd9&quot; stroke-width=&quot;0.5&quot; stroke-dasharray=&quot;4&quot; /&gt;
        &lt;!-- Y labels (1.00 down to 0.70) --&gt;
        &lt;text x=&quot;52&quot; y=&quot;24&quot; text-anchor=&quot;end&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;1,00&lt;/text&gt;
        &lt;text x=&quot;52&quot; y=&quot;64&quot; text-anchor=&quot;end&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;0,95&lt;/text&gt;
        &lt;text x=&quot;52&quot; y=&quot;104&quot; text-anchor=&quot;end&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;0,90&lt;/text&gt;
        &lt;text x=&quot;52&quot; y=&quot;144&quot; text-anchor=&quot;end&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;0,85&lt;/text&gt;
        &lt;text x=&quot;52&quot; y=&quot;184&quot; text-anchor=&quot;end&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;0,80&lt;/text&gt;
        &lt;text x=&quot;52&quot; y=&quot;224&quot; text-anchor=&quot;end&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;0,75&lt;/text&gt;
        &lt;text x=&quot;52&quot; y=&quot;264&quot; text-anchor=&quot;end&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;0,70&lt;/text&gt;
        &lt;!-- X labels --&gt;
        &lt;text x=&quot;120&quot; y=&quot;278&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;v0 → v1&lt;/text&gt;
        &lt;text x=&quot;222&quot; y=&quot;278&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;v1 → v2&lt;/text&gt;
        &lt;text x=&quot;324&quot; y=&quot;278&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;v2 → v3&lt;/text&gt;
        &lt;text x=&quot;426&quot; y=&quot;278&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;v3 → v4&lt;/text&gt;
        &lt;text x=&quot;528&quot; y=&quot;278&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;v4 → v5&lt;/text&gt;
        &lt;!-- Curve : 0.86 → 0.87 → 0.88 → 0.89 → 0.90 --&gt;
        &lt;!-- y = 20 + (1.00 - sim) * 800 --&gt;
        &lt;!-- 0.86 → 132 ; 0.87 → 124 ; 0.88 → 116 ; 0.89 → 108 ; 0.90 → 100 --&gt;
        &lt;polyline points=&quot;120,132 222,124 324,116 426,108 528,100&quot; fill=&quot;none&quot; stroke=&quot;#2d6a4f&quot; stroke-width=&quot;2.5&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; /&gt;
        &lt;circle cx=&quot;120&quot; cy=&quot;132&quot; r=&quot;5&quot; fill=&quot;#2d6a4f&quot; /&gt;
        &lt;text x=&quot;120&quot; y=&quot;122&quot; text-anchor=&quot;middle&quot; font-size=&quot;12&quot; font-weight=&quot;700&quot; fill=&quot;#1c1c1c&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;0,86&lt;/text&gt;
        &lt;circle cx=&quot;222&quot; cy=&quot;124&quot; r=&quot;5&quot; fill=&quot;#2d6a4f&quot; /&gt;
        &lt;circle cx=&quot;324&quot; cy=&quot;116&quot; r=&quot;5&quot; fill=&quot;#2d6a4f&quot; /&gt;
        &lt;circle cx=&quot;426&quot; cy=&quot;108&quot; r=&quot;5&quot; fill=&quot;#2d6a4f&quot; /&gt;
        &lt;circle cx=&quot;528&quot; cy=&quot;100&quot; r=&quot;5&quot; fill=&quot;#2d6a4f&quot; /&gt;
        &lt;text x=&quot;528&quot; y=&quot;90&quot; text-anchor=&quot;middle&quot; font-size=&quot;12&quot; font-weight=&quot;700&quot; fill=&quot;#1c1c1c&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;0,90&lt;/text&gt;
        &lt;!-- Gap annotation --&gt;
        &lt;line x1=&quot;528&quot; y1=&quot;100&quot; x2=&quot;528&quot; y2=&quot;20&quot; stroke=&quot;#c0392b&quot; stroke-width=&quot;0.8&quot; stroke-dasharray=&quot;2 3&quot; /&gt;
        &lt;text x=&quot;540&quot; y=&quot;60&quot; text-anchor=&quot;start&quot; font-size=&quot;10&quot; fill=&quot;#c0392b&quot; font-style=&quot;italic&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;écart résiduel&lt;/text&gt;
    &lt;/svg&gt;
    &lt;p class=&quot;source-ref&quot;&gt;Variante Original. Même sur du code propre, GPT-5.1 continue de modifier ~10 % des lignes à chaque passage. Source : Peitek et al., 2026.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;Sur la variante &lt;strong&gt;Original&lt;/strong&gt; (code déjà propre, déjà idiomatique), on s’attendrait à ce que le modèle laisse tranquille. &lt;strong&gt;Ce n’est pas ce qui se passe.&lt;/strong&gt; La similarité entre versions successives s’approche de 0,90 à la fin, mais &lt;strong&gt;ne touche jamais le plafond&lt;/strong&gt;. Le modèle ne s’arrête pas spontanément.&lt;/p&gt;

&lt;p&gt;Les auteurs appellent ça une &lt;strong&gt;tendance au sur-refactoring&lt;/strong&gt; : quand on demande de refactoriser, le modèle refactorise, même quand il n’y a rien à faire. Pour qui industrialise ce type de flux, &lt;strong&gt;c’est un problème concret&lt;/strong&gt;. Modifier du code qui n’en avait pas besoin, c’est du risque de bug, du bruit dans le &lt;code&gt;git blame&lt;/code&gt;, de la friction en revue.&lt;/p&gt;

&lt;p&gt;La parade est connue mais peu utilisée : il faut des &lt;strong&gt;critères d’arrêt explicites&lt;/strong&gt;. Ne pas dire « refactorise », mais « refactorise si nécessaire, sinon réponds inchangé ». Et même comme ça, le résultat n’est pas garanti.&lt;/p&gt;

&lt;h2 id=&quot;découverte-5--leffet--rename-oscillation-&quot;&gt;Découverte 5 : L’effet « rename oscillation »&lt;/h2&gt;

&lt;div class=&quot;chart-block&quot;&gt;
    &lt;p class=&quot;chart-title&quot;&gt;Oscillation d&apos;un identifiant sous PromptMeaning&lt;/p&gt;
    &lt;div class=&quot;oscillation-track&quot;&gt;
        &lt;div class=&quot;oscillation-cell&quot;&gt;
            &lt;div class=&quot;oscillation-version&quot;&gt;v0&lt;/div&gt;
            &lt;div class=&quot;oscillation-name neutral&quot;&gt;count&lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;oscillation-cell&quot;&gt;
            &lt;div class=&quot;oscillation-version&quot;&gt;v1&lt;/div&gt;
            &lt;div class=&quot;oscillation-name a&quot;&gt;numberOfItems&lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;oscillation-cell&quot;&gt;
            &lt;div class=&quot;oscillation-version&quot;&gt;v2&lt;/div&gt;
            &lt;div class=&quot;oscillation-name b&quot;&gt;itemCount&lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;oscillation-cell&quot;&gt;
            &lt;div class=&quot;oscillation-version&quot;&gt;v3&lt;/div&gt;
            &lt;div class=&quot;oscillation-name a&quot;&gt;numberOfItems&lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;oscillation-cell&quot;&gt;
            &lt;div class=&quot;oscillation-version&quot;&gt;v4&lt;/div&gt;
            &lt;div class=&quot;oscillation-name b&quot;&gt;itemCount&lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;oscillation-cell&quot;&gt;
            &lt;div class=&quot;oscillation-version&quot;&gt;v5&lt;/div&gt;
            &lt;div class=&quot;oscillation-name a&quot;&gt;numberOfItems&lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;p class=&quot;source-ref&quot;&gt;Exemple représentatif. Le modèle alterne entre deux noms équivalents au fil des itérations, sans amélioration nette. Source : Peitek et al., 2026.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;Avec le prompt &lt;code&gt;PromptMeaning&lt;/code&gt; (qui cible explicitement les noms), un phénomène inattendu apparaît : &lt;strong&gt;les noms oscillent&lt;/strong&gt;. Le modèle choisit un nom en v1, le change en v2, &lt;strong&gt;revient&lt;/strong&gt; au premier en v3. Pas systématique - mais suffisamment fréquent pour être documenté comme un risque structurel.&lt;/p&gt;

&lt;p&gt;L’explication probable : sans guide externe (convention d’équipe, dictionnaire de domaine), le modèle hésite entre plusieurs noms équivalents. Le contexte change légèrement à chaque itération (d’autres lignes ont bougé), et son arbitrage bascule. &lt;strong&gt;Un dev senior trancherait une fois et s’y tiendrait. Le LLM, lui, peut changer d’avis.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Pour qui industrialise ce flux, c’est un signal d’alerte : les diffs successifs sont &lt;strong&gt;bruités par des changements qui ne correspondent à aucune amélioration&lt;/strong&gt;. Le type de bruit qui pollue les revues et érode la confiance dans l’outil.&lt;/p&gt;

&lt;h2 id=&quot;découverte-6--le-prompt-influence-le-détail-pas-la-trajectoire-générale&quot;&gt;Découverte 6 : Le prompt influence le détail, pas la trajectoire générale&lt;/h2&gt;

&lt;p&gt;Les auteurs ont testé trois prompts différents (&lt;code&gt;PromptGeneral&lt;/code&gt;, &lt;code&gt;PromptMeaning&lt;/code&gt;, &lt;code&gt;PromptComments&lt;/code&gt;) pour voir si la formulation change quelque chose à la dynamique de refactoring.&lt;/p&gt;

&lt;p&gt;Le résultat est nuancé. Les prompts ciblés influencent bien le &lt;strong&gt;type&lt;/strong&gt; de changements introduits - &lt;code&gt;PromptMeaning&lt;/code&gt; produit plus de renames, &lt;code&gt;PromptComments&lt;/code&gt; produit plus de modifications de commentaires. Mais la &lt;strong&gt;dynamique globale&lt;/strong&gt; (restructuration puis stabilisation, convergence vers le même idéal stylistique, sur-refactoring résiduel) est la même quelle que soit la formulation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rassurant, parce que le prompt garde un effet local&lt;/strong&gt; : on peut bien orienter les renames ou les commentaires. &lt;strong&gt;Inquiétant, parce qu’il ne suffit pas à changer la trajectoire stylistique de fond&lt;/strong&gt; : on peut demander gentiment, insister, préciser, le modèle reviendra toujours vers &lt;em&gt;son&lt;/em&gt; idée du beau code.&lt;/p&gt;

&lt;h2 id=&quot;ce-que-ça-éclaire-pour-moi&quot;&gt;Ce que ça éclaire pour moi&lt;/h2&gt;

&lt;div class=&quot;callout&quot;&gt;
    &lt;p&gt;On mesure déjà la complexité. On mesure déjà le couplage. Mais &lt;strong&gt;on mesure encore mal la cohérence stylistique structurelle&lt;/strong&gt; d&apos;une codebase. Et c&apos;est précisément là que les LLMs vont laisser leurs traces.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;La maintenabilité d’un projet tient aussi à la &lt;strong&gt;cohérence stylistique interne&lt;/strong&gt; de sa codebase. Une codebase où toutes les méthodes sont longues mais cohérentes est plus facile à maintenir qu’une codebase qui mélange des fichiers façon Clean Code et des fichiers façon « grosse fonction utilitaire de 200 lignes ». Le cerveau du développeur s’habitue à un style - c’est ce qui permet de naviguer rapidement, de prévoir où trouver quoi, de reconnaître l’inhabituel.&lt;/p&gt;

&lt;p&gt;Introduire un LLM dans le flux de refactoring d’une codebase existante, c’est prendre le risque de &lt;strong&gt;fragmenter ce style interne&lt;/strong&gt;. Les fichiers qu’il touche migrent vers sa cible à lui. Les autres restent dans l’idéal historique de l’équipe. La codebase devient un patchwork (méthodes de cinq lignes ici, de cinquante là) sans que les métriques classiques en montrent quoi que ce soit. &lt;strong&gt;La complexité cyclomatique moyenne peut être identique. Le couplage peut ne pas avoir bougé. Mais la cohérence s’érode, silencieusement.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;C’est là le vrai trou dans nos outils. &lt;a href=&quot;https://github.com/phpmetrics/PhpMetrics&quot;&gt;PhpMetrics&lt;/a&gt;, &lt;a href=&quot;https://github.com/halleck45/ast-metrics&quot;&gt;AstMetrics&lt;/a&gt;, les linters et les formateurs ont été construits pour quantifier la maintenabilité - mais via des métriques &lt;em&gt;absolues&lt;/em&gt; (complexité, couplage) ou &lt;em&gt;de surface&lt;/em&gt; (mise en forme). &lt;strong&gt;La dispersion stylistique &lt;em&gt;structurelle&lt;/em&gt;&lt;/strong&gt; (variance du nombre de méthodes par fichier, des longueurs de méthodes, des conventions de nommage, de la densité de commentaires) &lt;strong&gt;reste largement non mesurée&lt;/strong&gt;. Et c’est précisément le terrain où les LLMs vont laisser leurs traces.&lt;/p&gt;

&lt;h2 id=&quot;ce-que-ça-implique-concrètement&quot;&gt;Ce que ça implique, concrètement&lt;/h2&gt;

&lt;p&gt;Quatre choses ressortent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Un LLM a un style, et il faut le documenter.&lt;/strong&gt; Si l’équipe utilise GPT-5.1 pour refactoriser, elle pousse vers Clean Code. Si elle utilise Claude, le style est différent. Si elle utilise Qwen ou Mistral, idem. Avant de déléguer du refactoring, il est utile d’avoir une idée de la signature stylistique du modèle utilisé - et de décider si elle est compatible avec celle de la codebase.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Des critères d’arrêt explicites.&lt;/strong&gt; Le sur-refactoring n’est pas un bug - c’est le comportement par défaut du modèle. Pour qu’il s’arrête quand il n’y a plus rien à faire, il faut l’inscrire dans le prompt. &lt;em&gt;Refactorise uniquement si le code en a besoin. Si tu juges qu’il est déjà acceptable, réponds avec le code inchangé.&lt;/em&gt; Et même comme ça, vérifier en aval que les diffs sont réellement améliorants.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Préserver explicitement les commentaires qui ont de la valeur.&lt;/strong&gt; Le modèle élimine systématiquement les commentaires inline, et c’est parfois une vraie perte. Un commentaire qui dit &lt;em&gt;pourquoi&lt;/em&gt; (« on prend cette branche d’abord parce qu’elle est la plus fréquente en production ») a une valeur que le code ne véhicule pas. Si le flux LLM les supprime, on appauvrit la codebase.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Faire attention au naming.&lt;/strong&gt; L’oscillation sur les noms est réelle et bruyante. Refactoriser plusieurs fois la même portion de code avec un LLM sans demander de &lt;strong&gt;réutiliser les noms existants&lt;/strong&gt; revient à voir le même fichier renommer ses variables à chaque passage, sans amélioration nette.&lt;/p&gt;

&lt;p&gt;Au-delà de ces points techniques, il y a une question plus large : &lt;strong&gt;&lt;em&gt;qui définit le style de notre codebase ?&lt;/em&gt;&lt;/strong&gt; Pendant des décennies, c’étaient les équipes humaines, à travers leurs revues, leurs guides internes, leurs disputes. Maintenant, si on n’y prend pas garde, &lt;strong&gt;c’est implicitement le LLM qu’on utilise&lt;/strong&gt; - et donc les biais hérités d’un corpus d’entraînement dont la composition n’est pas neutre.&lt;/p&gt;

&lt;p&gt;Que cette référence soit bonne ou mauvaise est un autre débat. Le point ici, c’est que &lt;strong&gt;c’est une décision par défaut&lt;/strong&gt; (prise pour nous, sans nous) qui mériterait d’être consciente.&lt;/p&gt;

&lt;h2 id=&quot;limites-et-précautions-de-lecture&quot;&gt;Limites et précautions de lecture&lt;/h2&gt;

&lt;p&gt;Le papier a plusieurs limites que les auteurs mentionnent honnêtement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Un seul modèle testé.&lt;/strong&gt; C’est GPT-5.1, et c’est GPT-5.1 uniquement. La signature stylistique de Claude, de Qwen, de Mistral est probablement différente. Une comparaison cross-modèles serait un prolongement naturel - c’est très probablement ce qui suivra.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Une seule langue testée&lt;/strong&gt;, Java. Sur du Python, du PHP ou du Go, les patterns stylistiques préférés du modèle peuvent ne pas être les mêmes. Les conventions sont différentes par langage, et le modèle s’y adapte. Les chiffres précis (multiplier les méthodes par 2, augmenter de 26 % les lignes de code) ne sont pas directement transposables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Un corpus académique.&lt;/strong&gt; Les snippets du repo &lt;em&gt;The Algorithms&lt;/em&gt; sont pédagogiques, courts, isolés. Le comportement sur du code de production réel (multi-fichier, avec des dépendances, des side-effects) n’est pas testé ici. On peut raisonnablement supposer que les grandes tendances tiennent, mais les chiffres précis ne sont pas garantis.&lt;/p&gt;

&lt;p&gt;Le point important n’est donc pas de savoir si le style de GPT-5.1 est bon ou mauvais. &lt;strong&gt;Le point important, c’est qu’il existe.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Quand une équipe introduit un LLM dans son flux de développement, elle introduit aussi &lt;strong&gt;une préférence stylistique externe&lt;/strong&gt;. On peut l’accepter. On peut la mesurer. On peut la contraindre. &lt;strong&gt;Mais il vaut mieux éviter de la subir sans s’en rendre compte.&lt;/strong&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>LLMs in Production: Why an 8-Word Prompt Beat All the Others</title>
   <link href="https://blog.lepine.pro/en/what-evaluating-700-texts-taught-us-about-prompting/"/>
   <updated>2026-03-24T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/en/what-evaluating-700-texts-taught-us-about-prompting</id>
   <content type="html">&lt;style&gt;
    .callout {
        margin: 2rem 0;
        padding: 1.2rem 1.5rem;
        border-left: 3px solid #2d6a4f;
        background: #e8f4f0;
        border-radius: 0 6px 6px 0;
    }
    .callout p {
        margin: 0;
        font-family: &apos;Lora&apos;, serif;
        font-style: italic;
        font-size: 1.05rem;
        color: #1c1c1c;
        line-height: 1.65;
    }
    .callout-warning {
        border-left-color: #b8860b;
        background: #fdf6e3;
    }
    .prompts-grid { margin: 2rem 0; display: grid; gap: 0.85rem; }
    .prompt-card { border: 1px solid #e2dfd9; border-radius: 8px; overflow: hidden; }
    .prompt-card.winner { border-color: #2d6a4f; box-shadow: 0 0 0 1px #2d6a4f; }
    .prompt-card-header {
        display: flex; align-items: center; justify-content: space-between;
        padding: 0.55rem 1rem; background: #f5f3ef; border-bottom: 1px solid #e2dfd9; gap: 1rem;
    }
    .prompt-card.winner .prompt-card-header { background: #e8f4f0; border-bottom-color: #b7dece; }
    .prompt-name { font-weight: 600; font-size: 0.85rem; color: #1c1c1c; }
    .prompt-score { font-weight: 700; font-size: 0.85rem; color: #6b6b6b; white-space: nowrap; }
    .prompt-card.winner .prompt-score { color: #2d6a4f; }
    .prompt-card-body { padding: 0.9rem 1rem; }
    .prompt-text {
        font-family: &apos;Courier New&apos;, monospace; font-size: 0.8rem; color: #1c1c1c;
        background: #fafafa; padding: 0.7rem 0.9rem; border-radius: 5px;
        border: 1px solid #e2dfd9; margin-bottom: 0.55rem; line-height: 1.6; white-space: pre-wrap;
    }
    .prompt-card.winner .prompt-text { background: #e8f4f0; border-color: #b7dece; color: #2d6a4f; font-weight: 600; }
    .prompt-desc { color: #6b6b6b; font-size: 0.83rem; line-height: 1.55; }
    .chart-section { margin: 2rem 0; background: #f5f3ef; border-radius: 8px; padding: 1.75rem; }
    .chart-title { font-size: 0.75rem; font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase; color: #6b6b6b; margin-bottom: 1.75rem; }
    .chart-bars { display: flex; align-items: flex-end; gap: 1rem; height: 160px; padding-bottom: 0.5rem; border-bottom: 1px solid #e2dfd9; margin-bottom: 0.75rem; }
    .bar-group { flex: 1; display: flex; flex-direction: column; align-items: center; height: 100%; justify-content: flex-end; gap: 0.45rem; }
    .bar { width: 100%; border-radius: 4px 4px 0 0; }
    .bar-group:nth-child(1) .bar { background: #b8cdd8; height: 44%; }
    .bar-group:nth-child(2) .bar { background: #f4c07a; height: 51%; }
    .bar-group:nth-child(3) .bar { background: #2d6a4f; height: 59%; }
    .bar-group:nth-child(4) .bar { background: #f4a0a0; height: 47%; }
    .bar-value { font-size: 0.82rem; font-weight: 700; color: #1c1c1c; }
    .bar-group:nth-child(3) .bar-value { color: #2d6a4f; }
    .bar-label { font-size: 0.72rem; font-weight: 600; color: #6b6b6b; text-align: center; line-height: 1.35; }
    .bar-group:nth-child(3) .bar-label { color: #2d6a4f; }
    .chart-legend { display: grid; gap: 0.35rem; margin-top: 1rem; }
    .legend-row { display: flex; gap: 0.65rem; font-size: 0.8rem; color: #6b6b6b; line-height: 1.5; align-items: baseline; }
    .legend-key { font-weight: 700; min-width: 24px; color: #1c1c1c; flex-shrink: 0; }
    .chart-note { margin-top: 1rem; padding-top: 1rem; border-top: 1px solid #e2dfd9; font-size: 0.78rem; color: #6b6b6b; font-style: italic; }
    .method-box { margin: 2rem 0; border: 1px solid #e2dfd9; border-radius: 8px; overflow: hidden; }
    .method-box-header { background: #f5f3ef; padding: 0.65rem 1.25rem; font-size: 0.72rem; font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase; color: #6b6b6b; border-bottom: 1px solid #e2dfd9; }
    .method-box-body { padding: 1.25rem; }
    .method-row { display: flex; gap: 0.75rem; margin-bottom: 0.7rem; font-size: 0.9rem; line-height: 1.65; align-items: baseline; }
    .method-row:last-child { margin-bottom: 0; }
    .method-key { font-weight: 600; min-width: 120px; flex-shrink: 0; color: #6b6b6b; font-size: 0.76rem; text-transform: uppercase; letter-spacing: 0.05em; }
    .resource-box { margin: 2rem 0; border: 1px solid #e2dfd9; border-radius: 8px; overflow: hidden; }
    .resource-box-header { background: #f5f3ef; padding: 0.65rem 1.25rem; font-size: 0.72rem; font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase; color: #6b6b6b; border-bottom: 1px solid #e2dfd9; }
    .resource-list { padding: 1.1rem 1.25rem; display: grid; gap: 0.7rem; }
    .resource-item { font-size: 0.88rem; line-height: 1.55; }
    .resource-item a { font-weight: 600; }
    .resource-item span { color: #6b6b6b; font-size: 0.83rem; }
    .decay-chart { margin: 2rem 0; background: #f5f3ef; border-radius: 8px; padding: 1.75rem; }
    .decay-chart svg { width: 100%; height: auto; }
    .decay-legend { display: flex; flex-wrap: wrap; gap: 1.25rem; margin-top: 1rem; padding-top: 1rem; border-top: 1px solid #e2dfd9; }
    .decay-legend-item { display: flex; align-items: center; gap: 0.5rem; font-size: 0.82rem; color: #6b6b6b; }
    .decay-legend-color { width: 24px; height: 3px; border-radius: 2px; flex-shrink: 0; }
    .source-ref { font-size: 0.82rem; color: #6b6b6b; font-style: italic; margin-top: 0.5rem; }
    .prompt-card-body details { margin-bottom: 0.55rem; }
    .prompt-card-body summary {
        cursor: pointer; font-size: 0.82rem; color: #6b6b6b; padding: 0.4rem 0;
        user-select: none; list-style: none;
    }
    .prompt-card-body summary::-webkit-details-marker { display: none; }
    .prompt-card-body summary::before { content: &quot;▸ &quot;; font-size: 0.75rem; }
    .prompt-card-body details[open] summary::before { content: &quot;▾ &quot;; }
    .prompt-card-body details[open] .prompt-text { margin-top: 0.5rem; }
&lt;/style&gt;

&lt;p&gt;I had a simple need: get the best possible score on a language assessment task. A human-annotated dataset, 700 texts, and a way to test multiple prompts under the same conditions.&lt;/p&gt;

&lt;p&gt;I did what everyone does. I started with an elaborate, structured prompt, following best practices. Then I tested variations. And the result surprised me: &lt;strong&gt;an 8-word prompt beat a prompt designed by 21 researchers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;At first I thought it was an anomaly. Then I looked into the literature, and I discovered this result had a name: &lt;em&gt;instruction dilution&lt;/em&gt;. A documented phenomenon that affects all language models. The idea is simple and somewhat counterintuitive: when you add information to a prompt, even correct, even relevant information, performance can degrade. Not because the information is wrong, but because it dilutes the useful signal into noise.&lt;/p&gt;

&lt;p&gt;What follows is what recent research teaches us about this phenomenon, illustrated by my experiment on those 700 texts.&lt;/p&gt;

&lt;h2 id=&quot;the-experiment-700-texts-4-prompts&quot;&gt;The experiment: 700 texts, 4 prompts&lt;/h2&gt;

&lt;p&gt;The context: automated assessment of learners’ English language proficiency, according to the CEFR framework (the six levels A1 to C2). The model tested: GPT-4.1. The corpus: 700 written compositions, each evaluated by three certified human examiners.&lt;/p&gt;

&lt;div class=&quot;method-box&quot;&gt;
    &lt;div class=&quot;method-box-header&quot;&gt;Setup&lt;/div&gt;
    &lt;div class=&quot;method-box-body&quot;&gt;
        &lt;div class=&quot;method-row&quot;&gt;
            &lt;span class=&quot;method-key&quot;&gt;Corpus&lt;/span&gt;
            &lt;span&gt;700 examples under CC BY-NC-SA 4.0 license. Each text comes with a reference level and evaluations from three certified examiners.&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;method-row&quot;&gt;
            &lt;span class=&quot;method-key&quot;&gt;Model&lt;/span&gt;
            &lt;span&gt;GPT-4.1, via API, same parameters for all prompts tested.&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;method-row&quot;&gt;
            &lt;span class=&quot;method-key&quot;&gt;Metric&lt;/span&gt;
            &lt;span&gt;Exact match: did the model assign exactly the right level?&lt;/span&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;We built four prompts. From the most elaborate to the most minimalist.&lt;/p&gt;

&lt;div class=&quot;prompts-grid&quot;&gt;
    &lt;div class=&quot;prompt-card&quot;&gt;
        &lt;div class=&quot;prompt-card-header&quot;&gt;
            &lt;span class=&quot;prompt-name&quot;&gt;P1: Few-shot, following the OpenAI guide&lt;/span&gt;
            &lt;span class=&quot;prompt-score&quot;&gt;44%&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;prompt-card-body&quot;&gt;
            &lt;details&gt;
                &lt;summary&gt;Show full prompt&lt;/summary&gt;
                &lt;div class=&quot;prompt-text&quot;&gt;Classify the CEFR level of the following written text.

###

Rules:
- Output only the CEFR level: A1, A2, B1, B2, C1, or C2
- No explanation, no justification
- If uncertain, output the closest level

Examples:
Text: &quot;I have a dog. His name is Max. He is big and black.&quot;
Level: A1

Text: &quot;Last summer I visited my grandparents. We went to the market every morning.&quot;
Level: A2

(...)

###

Text: &quot;&quot;&quot;&quot;&quot;&quot;
Level:&lt;/div&gt;
            &lt;/details&gt;
            &lt;p class=&quot;prompt-desc&quot;&gt;Instructions up front, &lt;code&gt;###&lt;/code&gt; and &lt;code&gt;&quot;&quot;&quot;&lt;/code&gt; separators, few-shot examples, format constraints. Follows official OpenAI guide recommendations.&lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;prompt-card&quot;&gt;
        &lt;div class=&quot;prompt-card-header&quot;&gt;
            &lt;span class=&quot;prompt-name&quot;&gt;P2: Prompt from an academic publication&lt;/span&gt;
            &lt;span class=&quot;prompt-score&quot;&gt;51%&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;prompt-card-body&quot;&gt;
            &lt;details&gt;
                &lt;summary&gt;Show full prompt&lt;/summary&gt;
                &lt;div class=&quot;prompt-text&quot;&gt;
You are an expert in language proficiency classification based on the Common European Framework of Reference for Languages (CEFR). Your task is to analyze the given text or narrative and determine the best CEFR level [A1, A2, B1, B2, C1, or C2] based on the CEFR descriptors of reading comprehension of learners below:

A1 - Learners of this level can give information about matters of personal relevance (e.g. likes and dislikes, family, pets) using simple words/signs and basic expressions. Learners can also produce simple isolated phrases and sentences.

A2 - Learners of this level can produce a series of simple phrases and sentences linked with simple connectors like &quot;and&quot;, &quot;but&quot; and &quot;because&quot;. Learners have sufficient vocabulary for the expression of basic communicative needs and for coping with simple survival needs.

B1 - Learners of this level can produce straightforward connected texts on a range of familiar subjects within their field of interest, by linking a series of shorter discrete elements into a linear sequence. Learners have a good range of vocabulary related to familiar topics and everyday situations.

B2 - Learners of this level can produce clear, detailed texts on a variety of subjects related to their field of interest, synthesising and evaluating information and arguments from a number of sources. Learners have a good range of vocabulary for matters connected to their field and most general topics.

C1 - Learners of this level can produce clear, well-structured texts of complex subjects, underlining the relevant salient issues, expanding and supporting points of view at some length with subsidiary points, reasons and relevant examples, and rounding off with an appropriate conclusion. Learners can also employ the structure and conventions of a variety of genres, varying the tone, style and register according to addressee, text type and theme.

C2 - Learners of this level can produce clear, smoothly flowing, complex texts in an appropriate and effective style and a logical structure which helps the reader identify significant points. Learners have a good command of a very broad lexical repertoire including idiomatic expressions and colloquialisms; shows awareness of connotative levels of meaning.

Provide only the CEFR level as output directly, without explanation or justification.

Text: «TEXT»

Answer:
&lt;/div&gt;
            &lt;/details&gt;
            &lt;p class=&quot;prompt-desc&quot;&gt;From &lt;a href=&quot;https://arxiv.org/pdf/2506.01419&quot; target=&quot;_blank&quot;&gt;&lt;em&gt;Universal CEFR: Enabling Open Multilingual Research on Language Proficiency Assessment&lt;/em&gt;&lt;/a&gt; (arXiv:2506.01419). Written by researchers specializing in automated assessment.&lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;prompt-card winner&quot;&gt;
        &lt;div class=&quot;prompt-card-header&quot;&gt;
            &lt;span class=&quot;prompt-name&quot;&gt;P3: One sentence (best result 🏆)&lt;/span&gt;
            &lt;span class=&quot;prompt-score&quot;&gt;59%&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;prompt-card-body&quot;&gt;
            &lt;div class=&quot;prompt-text&quot;&gt;Assess the CEFR level of this written production.&lt;/div&gt;
            &lt;p class=&quot;prompt-desc&quot;&gt;No role. No criteria. No list of levels. The model infers everything (and does it better than when you explain it).&lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;prompt-card&quot;&gt;
        &lt;div class=&quot;prompt-card-header&quot;&gt;
            &lt;span class=&quot;prompt-name&quot;&gt;P4: P3 slightly enriched&lt;/span&gt;
            &lt;span class=&quot;prompt-score&quot;&gt;47%&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;prompt-card-body&quot;&gt;
            &lt;div class=&quot;prompt-text&quot;&gt;Assess the CEFR level of this written production by an English learner. Give a score between A1, A2, B1, B2, C1, C2.&lt;/div&gt;
            &lt;p class=&quot;prompt-desc&quot;&gt;We add context and the list of levels. Two seemingly useful pieces of information. Result: -12 points compared to P3.&lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;h2 id=&quot;the-results&quot;&gt;The results&lt;/h2&gt;

&lt;div class=&quot;chart-section&quot;&gt;
    &lt;p class=&quot;chart-title&quot;&gt;Accuracy (Exact Match) (GPT-4.1, n=700)&lt;/p&gt;
    &lt;div class=&quot;chart-bars&quot;&gt;
        &lt;div class=&quot;bar-group&quot;&gt;
            &lt;span class=&quot;bar-value&quot;&gt;44%&lt;/span&gt;
            &lt;div class=&quot;bar&quot;&gt;&lt;/div&gt;
            &lt;span class=&quot;bar-label&quot;&gt;P1&lt;br /&gt;Few-shot&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;bar-group&quot;&gt;
            &lt;span class=&quot;bar-value&quot;&gt;51%&lt;/span&gt;
            &lt;div class=&quot;bar&quot;&gt;&lt;/div&gt;
            &lt;span class=&quot;bar-label&quot;&gt;P2&lt;br /&gt;Academic&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;bar-group&quot;&gt;
            &lt;span class=&quot;bar-value&quot;&gt;59%&lt;/span&gt;
            &lt;div class=&quot;bar&quot;&gt;&lt;/div&gt;
            &lt;span class=&quot;bar-label&quot;&gt;P3&lt;br /&gt;One sentence&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;bar-group&quot;&gt;
            &lt;span class=&quot;bar-value&quot;&gt;47%&lt;/span&gt;
            &lt;div class=&quot;bar&quot;&gt;&lt;/div&gt;
            &lt;span class=&quot;bar-label&quot;&gt;P4&lt;br /&gt;Enriched&lt;/span&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;chart-legend&quot;&gt;
        &lt;div class=&quot;legend-row&quot;&gt;&lt;span class=&quot;legend-key&quot;&gt;P1&lt;/span&gt;&lt;span&gt;Few-shot following the OpenAI guide: 44%&lt;/span&gt;&lt;/div&gt;
        &lt;div class=&quot;legend-row&quot;&gt;&lt;span class=&quot;legend-key&quot;&gt;P2&lt;/span&gt;&lt;span&gt;Academic research prompt (Universal CEFR): 51%&lt;/span&gt;&lt;/div&gt;
        &lt;div class=&quot;legend-row&quot;&gt;&lt;span class=&quot;legend-key&quot;&gt;P3&lt;/span&gt;&lt;span&gt;&lt;em&gt;&quot;Assess the CEFR level of this written production&quot;&lt;/em&gt;: &lt;strong&gt;59%&lt;/strong&gt;&lt;/span&gt;&lt;/div&gt;
        &lt;div class=&quot;legend-row&quot;&gt;&lt;span class=&quot;legend-key&quot;&gt;P4&lt;/span&gt;&lt;span&gt;P3 with context and level list: 47%&lt;/span&gt;&lt;/div&gt;
    &lt;/div&gt;
    &lt;p class=&quot;chart-note&quot;&gt;Exact match on the A1-C2 scale. GPT-4.1 via API. Academic corpus CC BY-NC-SA 4.0, evaluated by three certified human examiners.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;P3 wins, and by a wide margin. It beats the academic prompt by 8 points, the few-shot prompt by 15 points, and its own enriched version by 12 points.&lt;/p&gt;

&lt;p&gt;That last gap is the one that intrigued me most. Between P3 and P4, we only added two pieces of information that GPT-4.1 already knows: that the learner is studying English, and the list of CEFR levels. By repeating them, we didn’t help the model. We got in its way.&lt;/p&gt;

&lt;h2 id=&quot;why-adding-information-degrades-results&quot;&gt;Why adding information degrades results&lt;/h2&gt;

&lt;p&gt;This 12-point gap between P3 and P4 is explained by what the literature calls &lt;em&gt;information dilution&lt;/em&gt;: when you rephrase what the model already knows, you dilute the useful signal into noise.&lt;/p&gt;

&lt;p&gt;The mechanism isn’t mysterious. Transformer attention allocates finite capacity on each pass. When part of that capacity is consumed by redundant information, even correct information, less remains for the task itself. Attention doesn’t disappear: &lt;strong&gt;it scatters across non-informative tokens&lt;/strong&gt;. &lt;a href=&quot;https://arxiv.org/abs/2504.11004&quot;&gt;Jiang et al. (2024)&lt;/a&gt; describe &lt;em&gt;“reduced perceptual ability due to the limited context window”&lt;/em&gt;: the context window is a finite resource, and every token consumed by noise is one less token for signal.&lt;/p&gt;

&lt;p&gt;GPT-4.1 knows the CEFR. It was trained on massive amounts of language assessment data. When you ask it in one sentence to evaluate according to the CEFR, it activates exactly what it needs. When you provide detailed examples and instructions, however well-crafted, you’re offering a reformulation of what it already knows. And that reformulation creates friction. That’s why P1, the most elaborate prompt, finishes last.&lt;/p&gt;

&lt;div class=&quot;callout&quot;&gt;
    &lt;p&gt;A longer prompt is not a more precise prompt. On a domain the model has mastered, it&apos;s often a noisier prompt.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;The question that follows: how systematic is this phenomenon? And what happens when you push models beyond just a few instructions?&lt;/p&gt;

&lt;h2 id=&quot;three-degradation-profiles&quot;&gt;Three degradation profiles&lt;/h2&gt;

&lt;p&gt;The &lt;a href=&quot;https://arxiv.org/abs/2507.11538&quot;&gt;IFScale&lt;/a&gt; benchmark (Jaroslawicz et al., 2025) tested 20 models on tasks with 10 to 500 simultaneous instructions. The results reveal three distinct degradation profiles.&lt;/p&gt;

&lt;div class=&quot;decay-chart&quot;&gt;
    &lt;p class=&quot;chart-title&quot;&gt;Three degradation profiles by instruction density&lt;/p&gt;
    &lt;svg viewBox=&quot;0 0 620 310&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; role=&quot;img&quot; aria-label=&quot;Chart showing three decay curves: threshold, linear, and exponential&quot;&gt;
        &lt;!-- Grid --&gt;
        &lt;line x1=&quot;60&quot; y1=&quot;20&quot; x2=&quot;60&quot; y2=&quot;260&quot; stroke=&quot;#e2dfd9&quot; stroke-width=&quot;1&quot; /&gt;
        &lt;line x1=&quot;60&quot; y1=&quot;260&quot; x2=&quot;560&quot; y2=&quot;260&quot; stroke=&quot;#e2dfd9&quot; stroke-width=&quot;1&quot; /&gt;
        &lt;!-- Horizontal grid lines --&gt;
        &lt;line x1=&quot;60&quot; y1=&quot;20&quot; x2=&quot;560&quot; y2=&quot;20&quot; stroke=&quot;#e2dfd9&quot; stroke-width=&quot;0.5&quot; stroke-dasharray=&quot;4&quot; /&gt;
        &lt;line x1=&quot;60&quot; y1=&quot;80&quot; x2=&quot;560&quot; y2=&quot;80&quot; stroke=&quot;#e2dfd9&quot; stroke-width=&quot;0.5&quot; stroke-dasharray=&quot;4&quot; /&gt;
        &lt;line x1=&quot;60&quot; y1=&quot;140&quot; x2=&quot;560&quot; y2=&quot;140&quot; stroke=&quot;#e2dfd9&quot; stroke-width=&quot;0.5&quot; stroke-dasharray=&quot;4&quot; /&gt;
        &lt;line x1=&quot;60&quot; y1=&quot;200&quot; x2=&quot;560&quot; y2=&quot;200&quot; stroke=&quot;#e2dfd9&quot; stroke-width=&quot;0.5&quot; stroke-dasharray=&quot;4&quot; /&gt;
        &lt;!-- Y axis labels --&gt;
        &lt;text x=&quot;52&quot; y=&quot;24&quot; text-anchor=&quot;end&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;100%&lt;/text&gt;
        &lt;text x=&quot;52&quot; y=&quot;84&quot; text-anchor=&quot;end&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;75%&lt;/text&gt;
        &lt;text x=&quot;52&quot; y=&quot;144&quot; text-anchor=&quot;end&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;50%&lt;/text&gt;
        &lt;text x=&quot;52&quot; y=&quot;204&quot; text-anchor=&quot;end&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;25%&lt;/text&gt;
        &lt;text x=&quot;52&quot; y=&quot;264&quot; text-anchor=&quot;end&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;0%&lt;/text&gt;
        &lt;!-- X axis labels --&gt;
        &lt;text x=&quot;60&quot; y=&quot;278&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;10&lt;/text&gt;
        &lt;text x=&quot;162&quot; y=&quot;278&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;100&lt;/text&gt;
        &lt;text x=&quot;264&quot; y=&quot;278&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;200&lt;/text&gt;
        &lt;text x=&quot;366&quot; y=&quot;278&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;300&lt;/text&gt;
        &lt;text x=&quot;468&quot; y=&quot;278&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;400&lt;/text&gt;
        &lt;text x=&quot;560&quot; y=&quot;278&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;500&lt;/text&gt;
        &lt;!-- X axis title --&gt;
        &lt;text x=&quot;310&quot; y=&quot;300&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;Number of instructions&lt;/text&gt;
        &lt;!-- Threshold decay (green) --&gt;
        &lt;polyline points=&quot;60,22 101,22 162,25 213,32 264,56 315,73 366,82 468,92 560,95&quot; fill=&quot;none&quot; stroke=&quot;#2d6a4f&quot; stroke-width=&quot;2.5&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; /&gt;
        &lt;!-- Linear decay (orange) --&gt;
        &lt;polyline points=&quot;60,30 101,37 162,49 213,63 264,80 315,97 366,111 468,128 560,143&quot; fill=&quot;none&quot; stroke=&quot;#c77d1a&quot; stroke-width=&quot;2.5&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; /&gt;
        &lt;!-- Exponential decay (red) --&gt;
        &lt;polyline points=&quot;60,56 101,128 162,188 213,217 264,226 315,231 366,236 468,241 560,243&quot; fill=&quot;none&quot; stroke=&quot;#c0392b&quot; stroke-width=&quot;2.5&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; /&gt;
        &lt;!-- Data point labels --&gt;
        &lt;text x=&quot;565&quot; y=&quot;90&quot; font-size=&quot;10&quot; fill=&quot;#2d6a4f&quot; font-family=&quot;system-ui, sans-serif&quot; font-weight=&quot;600&quot;&gt;~69%&lt;/text&gt;
        &lt;text x=&quot;565&quot; y=&quot;139&quot; font-size=&quot;10&quot; fill=&quot;#c77d1a&quot; font-family=&quot;system-ui, sans-serif&quot; font-weight=&quot;600&quot;&gt;~49%&lt;/text&gt;
        &lt;text x=&quot;565&quot; y=&quot;250&quot; font-size=&quot;10&quot; fill=&quot;#c0392b&quot; font-family=&quot;system-ui, sans-serif&quot; font-weight=&quot;600&quot;&gt;~7%&lt;/text&gt;
    &lt;/svg&gt;
    &lt;div class=&quot;decay-legend&quot;&gt;
        &lt;div class=&quot;decay-legend-item&quot;&gt;
            &lt;span class=&quot;decay-legend-color&quot; style=&quot;background:#2d6a4f&quot;&gt;&lt;/span&gt;
            &lt;span&gt;&lt;strong&gt;Threshold&lt;/strong&gt;: near-perfect, then sharp drop (o3, gemini-2.5-pro)&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;decay-legend-item&quot;&gt;
            &lt;span class=&quot;decay-legend-color&quot; style=&quot;background:#c77d1a&quot;&gt;&lt;/span&gt;
            &lt;span&gt;&lt;strong&gt;Linear&lt;/strong&gt;: steady, predictable degradation (gpt-4.1, claude-3.7-sonnet)&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;decay-legend-item&quot;&gt;
            &lt;span class=&quot;decay-legend-color&quot; style=&quot;background:#c0392b&quot;&gt;&lt;/span&gt;
            &lt;span&gt;&lt;strong&gt;Exponential&lt;/strong&gt;: rapid collapse, low plateau (claude-3.5-haiku, llama-4-scout)&lt;/span&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;p class=&quot;source-ref&quot;&gt;Source: IFScale (Jaroslawicz et al., arXiv:2507.11538), 2025.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;Reasoning models like o3 or gemini-2.5-pro follow a threshold profile: performance stays near-perfect up to 150 or 250 instructions, then drops sharply. Gemini-2.5-pro goes from 98.4% at 100 instructions to 68.9% at 500. &lt;strong&gt;The model absorbs and absorbs, then gives way all at once.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Large general-purpose models like gpt-4.1 or claude-3.7-sonnet degrade steadily and predictably. GPT-4.1 goes from 95.4% to 48.9%, claude-3.7-sonnet from 94.8% to 52.7%. Each added instruction costs a bit of performance. This is the most actionable profile, because you can estimate the loss before incurring it.&lt;/p&gt;

&lt;p&gt;Smaller models like claude-3.5-haiku or llama-4-scout collapse quickly then stabilize at a low plateau, between 7 and 15%. Beyond about a hundred instructions, adding or removing anything barely changes the result: the model has already disengaged.&lt;/p&gt;

&lt;div class=&quot;callout callout-warning&quot;&gt;
    &lt;p&gt;It&apos;s not &quot;shorter = better.&quot; It&apos;s that there&apos;s a threshold beyond which performance collapses; and that threshold depends on the model.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;The nuance matters. Reasoning models resist remarkably well up to a critical point. General-purpose models degrade gradually but manageably. Smaller models simply aren’t designed for dense prompts. Knowing the degradation profile of the model you’re using means knowing how many instructions you can afford.&lt;/p&gt;

&lt;h2 id=&quot;omission-vs-modification-how-models-fail&quot;&gt;Omission vs modification: how models fail&lt;/h2&gt;

&lt;p&gt;IFScale also reveals something more subtle: models don’t fail the same way depending on instruction density.&lt;/p&gt;

&lt;p&gt;At low density, when a model gets it wrong, it gets it wrong by a little. It approximates, it misinterprets a constraint. The researchers call this a &lt;em&gt;modification&lt;/em&gt; error: the model tried, and fell short.&lt;/p&gt;

&lt;p&gt;At high density, the failure mode changes radically. The model doesn’t get it wrong, it forgets. Instructions aren’t misinterpreted, they’re simply ignored. This is an &lt;em&gt;omission&lt;/em&gt; error. And the numbers are striking: at 500 instructions, llama-4-scout shows an omission/modification ratio of 34.88x. For every approximation error, 35 instructions are simply forgotten. &lt;strong&gt;The model isn’t doing its best with an imperfect result. It’s giving up.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This shift has a direct consequence for how we design prompts. At moderate density, rephrasing or clarifying an instruction can help. At high density, it’s pointless, because the problem isn’t that the model misunderstands. It’s that it’s no longer reading.&lt;/p&gt;

&lt;p&gt;There’s also a cognitive competition effect worth mentioning. The attention spent on instruction-following degrades the quality of the main task itself. IFScale shows that o3, at 500 instructions, produces about 1,500 output tokens where every third word must be an imposed keyword. The model devotes so much capacity to respecting formal constraints that not enough remains for the task.&lt;/p&gt;

&lt;p&gt;The connection to my experiment is direct. P1 and P2 add instructions that compete with the main evaluation task. Even though those instructions are correct, they consume attention. P3 leaves all the attention to the model for what actually matters.&lt;/p&gt;

&lt;h2 id=&quot;positional-bias-lost-in-the-middle-and-beyond&quot;&gt;Positional bias: Lost in the Middle and beyond&lt;/h2&gt;

&lt;p&gt;Instruction dilution is amplified by another well-documented problem: positional bias.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://arxiv.org/abs/2307.03172&quot;&gt;Liu et al. (Stanford, 2023)&lt;/a&gt; showed that LLMs exhibit a U-shaped attention bias. They process information at the beginning and end of the prompt better, and significantly degrade information placed in the middle. This is the &lt;em&gt;Lost in the Middle&lt;/em&gt; phenomenon, and it affects even models explicitly trained for long contexts.&lt;/p&gt;

&lt;p&gt;IFScale adds an unexpected nuance about primacy bias, the model’s tendency to favor earlier instructions. This bias follows a non-linear curve. At low density, below 100 instructions, it’s weak: the model processes instructions relatively uniformly. At moderate density, between 150 and 200 instructions, it peaks. The model becomes selective and favors the first instructions at the expense of later ones. Beyond 300 instructions, the bias converges to a low level, not because the model has become fair again, but because it’s switched to “uniform failure” mode: it ignores instructions evenly, regardless of position.&lt;/p&gt;

&lt;p&gt;In practice, on moderate-length prompts (the most common case in production), &lt;strong&gt;position matters enormously&lt;/strong&gt;. What’s at the beginning frames the task. What’s at the end acts as the last instruction before generation. What’s in the middle is the most vulnerable to being forgotten. And that’s one more reason to keep prompts short: the longer the prompt, the larger the “middle,” and the more pronounced the U-shaped bias.&lt;/p&gt;

&lt;h2 id=&quot;its-not-always-the-shortest-that-wins&quot;&gt;It’s not always the shortest that wins&lt;/h2&gt;

&lt;p&gt;The message of this article is not “make shorter prompts.” It’s that beyond a certain threshold, adding information becomes counterproductive, and that threshold is lower than you think.&lt;/p&gt;

&lt;p&gt;This threshold depends on several things. First, the model: reasoning models like o3 or gemini-2.5-pro resist instruction density far better than smaller models. A prompt that works on gpt-4.1 may collapse on claude-3.5-haiku.&lt;/p&gt;

&lt;p&gt;Then, the task. My P3 wins because &lt;strong&gt;GPT-4.1 already knows the CEFR&lt;/strong&gt;. It was trained on massive volumes of language assessment data. On a domain unknown to the model, a proprietary business framework, unpublished specialized jargon, a detailed prompt remains necessary. It’s precisely because the model doesn’t know that you need to tell it.&lt;/p&gt;

&lt;p&gt;And finally, the nature of the added information. The entire difference lies between &lt;strong&gt;redundant&lt;/strong&gt; and &lt;strong&gt;new&lt;/strong&gt; information. Rephrasing what the model knows (the CEFR descriptors, the list of levels) creates noise. Providing what the model doesn’t know (a proprietary grading scale, specific business criteria) creates signal.&lt;/p&gt;

&lt;div class=&quot;callout&quot;&gt;
    &lt;p&gt;The rule isn&apos;t &quot;be short.&quot; The rule is: don&apos;t rephrase what the model knows. Only add what it doesn&apos;t know.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;In practice, this requires knowing the model’s boundaries, what’s part of its training and what isn’t. That boundary is rarely documented. It’s discovered by testing.&lt;/p&gt;

&lt;h2 id=&quot;prompt-compression-compress-rather-than-remove&quot;&gt;Prompt compression: compress rather than remove&lt;/h2&gt;

&lt;p&gt;In many production use cases (RAG, document analysis, long contexts), you simply can’t shorten the prompt. The context is long because the problem demands it.&lt;/p&gt;

&lt;p&gt;Research on &lt;em&gt;prompt compression&lt;/em&gt; offers an interesting alternative: rather than removing information, you compress it. You eliminate noise while preserving signal.&lt;/p&gt;

&lt;p&gt;The first family of approaches, called text-to-text, transforms text into shorter text by pruning non-informative tokens or by summarization. &lt;a href=&quot;https://arxiv.org/abs/2403.12968&quot;&gt;LLMLingua-2&lt;/a&gt; from Microsoft achieves compression ratios of 3 to 6x with performance comparable to uncompressed text. On NaturalQuestions, F1 stays at 71.90 with 3.9x compression. On GSM8K, exact match reaches 79.08 at 5x. You divide context size by 4 or 5, and performance stays nearly identical.&lt;/p&gt;

&lt;p&gt;The second family, text-to-vector, encodes text into compact vector representations that the literature calls &lt;em&gt;gist tokens&lt;/em&gt;. The gisting technique achieves up to 26x compression with minimal loss. The idea: instead of having the model read 10,000 tokens, you encode them into a few hundred vectors that capture the essential information.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://arxiv.org/abs/2504.11004&quot;&gt;LLM-DCP&lt;/a&gt; (Jiang et al., 2024) goes further by modeling compression as a Markov Decision Process. Each token is evaluated sequentially, keep or remove, based on its contribution to the task. The result: 12.9x compression ratio.&lt;/p&gt;

&lt;div class=&quot;callout&quot;&gt;
    &lt;p&gt;Compression is an alternative to truncation. It preserves signal while eliminating noise (exactly what instruction dilution prevents us from doing manually).&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;When context is long, the question isn’t “how to shorten?” but “how to compress without losing signal?” It’s an engineering problem, not a writing problem.&lt;/p&gt;

&lt;h2 id=&quot;practical-implications&quot;&gt;Practical implications&lt;/h2&gt;

&lt;p&gt;From all of this, I take away a few principles that I now apply daily.&lt;/p&gt;

&lt;p&gt;The first is atomicity. One instruction per prompt, stated in a single sentence if possible. When a task is complex, I break it into steps rather than making the prompt longer.&lt;/p&gt;

&lt;p&gt;The second is position. Main instruction at the beginning, critical constraints at the end, context and examples in the middle, knowing they’ll be less well retained.&lt;/p&gt;

&lt;p&gt;The third is testing at scale. A prompt that works on 10 examples may collapse on 700. IFScale results show that degradation profiles are predictable: it’s worth testing at different densities to find your model’s threshold.&lt;/p&gt;

&lt;p&gt;And finally, when a prompt contains multiple constraints, a reminder at the end (“make sure you respect all constraints above”) can reduce the omission rate. It’s a palliative, not a solution, but it works on linear degradation profiles.&lt;/p&gt;

&lt;p&gt;I drew &lt;a href=&quot;/en/4-prompting-rules-learned-from-evaluating-700-texts/&quot;&gt;concrete rules for production prompting&lt;/a&gt; from these observations, what changes when you move from conversation to API, how to separate the what from the how, and how to stabilize and evaluate prompts at scale.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Instruction dilution isn’t an intuition. It’s a measurable, reproducible phenomenon, and research is starting to understand it well. Beyond a certain threshold, adding information to a prompt degrades the model’s performance. That threshold depends on the model, the task, and the nature of the added information.&lt;/p&gt;

&lt;p&gt;Prompting guides provide techniques. Research explains why some work and others don’t. Not because the guides are wrong, but because they’re written for a context, conversation, that doesn’t transfer directly to another, production at scale.&lt;/p&gt;

&lt;p&gt;To write prompts that work at scale, you need to understand what’s happening in the model’s attention. Know what it already knows, what it doesn’t, and where the tipping point lies. And that can only be learned through experimentation. By getting it wrong on 700 examples and understanding why.&lt;/p&gt;

&lt;div class=&quot;resource-box&quot;&gt;
    &lt;div class=&quot;resource-box-header&quot;&gt;Sources and further reading&lt;/div&gt;
    &lt;div class=&quot;resource-list&quot;&gt;
        &lt;div class=&quot;resource-item&quot;&gt;
            &lt;a href=&quot;https://arxiv.org/abs/2507.11538&quot; target=&quot;_blank&quot;&gt;IFScale: Benchmarking Instruction Following Across Scales&lt;/a&gt;&lt;br /&gt;
            &lt;span&gt;Jaroslawicz et al., 2025. Benchmark of 20 models on 10 to 500 simultaneous instructions.&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;resource-item&quot;&gt;
            &lt;a href=&quot;https://arxiv.org/abs/2307.03172&quot; target=&quot;_blank&quot;&gt;Lost in the Middle: How Language Models Use Long Contexts&lt;/a&gt;&lt;br /&gt;
            &lt;span&gt;Liu et al. (Stanford), 2023. U-shaped positional bias in LLMs.&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;resource-item&quot;&gt;
            &lt;a href=&quot;https://arxiv.org/abs/2504.11004&quot; target=&quot;_blank&quot;&gt;LLM-DCP: Prompt Compression as a Markov Decision Process&lt;/a&gt;&lt;br /&gt;
            &lt;span&gt;Jiang et al., 2024. Prompt compression via sequential decision process.&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;resource-item&quot;&gt;
            &lt;a href=&quot;https://arxiv.org/abs/2403.12968&quot; target=&quot;_blank&quot;&gt;LLMLingua-2: Data Distillation for Efficient and Faithful Task-Agnostic Prompt Compression&lt;/a&gt;&lt;br /&gt;
            &lt;span&gt;Microsoft, 2024. Text-to-text compression 3-6x with preserved performance.&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;resource-item&quot;&gt;
            &lt;a href=&quot;https://arxiv.org/abs/2404.01077&quot; target=&quot;_blank&quot;&gt;A Survey on Prompt Compression&lt;/a&gt;&lt;br /&gt;
            &lt;span&gt;Taxonomy of text-to-text and text-to-vector approaches.&lt;/span&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>LLMs en prod : pourquoi un prompt de 8 mots a battu tous les autres</title>
   <link href="https://blog.lepine.pro/fr/ce-que-levaluation-de-700-textes-nous-a-appris-sur-le-prompting/"/>
   <updated>2026-03-24T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/fr/ce-que-levaluation-de-700-textes-nous-a-appris-sur-le-prompting</id>
   <content type="html">&lt;style&gt;
    .callout {
        margin: 2rem 0;
        padding: 1.2rem 1.5rem;
        border-left: 3px solid #2d6a4f;
        background: #e8f4f0;
        border-radius: 0 6px 6px 0;
    }
    .callout p {
        margin: 0;
        font-family: &apos;Lora&apos;, serif;
        font-style: italic;
        font-size: 1.05rem;
        color: #1c1c1c;
        line-height: 1.65;
    }
    .callout-warning {
        border-left-color: #b8860b;
        background: #fdf6e3;
    }
    .prompts-grid { margin: 2rem 0; display: grid; gap: 0.85rem; }
    .prompt-card { border: 1px solid #e2dfd9; border-radius: 8px; overflow: hidden; }
    .prompt-card.winner { border-color: #2d6a4f; box-shadow: 0 0 0 1px #2d6a4f; }
    .prompt-card-header {
        display: flex; align-items: center; justify-content: space-between;
        padding: 0.55rem 1rem; background: #f5f3ef; border-bottom: 1px solid #e2dfd9; gap: 1rem;
    }
    .prompt-card.winner .prompt-card-header { background: #e8f4f0; border-bottom-color: #b7dece; }
    .prompt-name { font-weight: 600; font-size: 0.85rem; color: #1c1c1c; }
    .prompt-score { font-weight: 700; font-size: 0.85rem; color: #6b6b6b; white-space: nowrap; }
    .prompt-card.winner .prompt-score { color: #2d6a4f; }
    .prompt-card-body { padding: 0.9rem 1rem; }
    .prompt-text {
        font-family: &apos;Courier New&apos;, monospace; font-size: 0.8rem; color: #1c1c1c;
        background: #fafafa; padding: 0.7rem 0.9rem; border-radius: 5px;
        border: 1px solid #e2dfd9; margin-bottom: 0.55rem; line-height: 1.6; white-space: pre-wrap;
    }
    .prompt-card.winner .prompt-text { background: #e8f4f0; border-color: #b7dece; color: #2d6a4f; font-weight: 600; }
    .prompt-desc { color: #6b6b6b; font-size: 0.83rem; line-height: 1.55; }
    .chart-section { margin: 2rem 0; background: #f5f3ef; border-radius: 8px; padding: 1.75rem; }
    .chart-title { font-size: 0.75rem; font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase; color: #6b6b6b; margin-bottom: 1.75rem; }
    .chart-bars { display: flex; align-items: flex-end; gap: 1rem; height: 160px; padding-bottom: 0.5rem; border-bottom: 1px solid #e2dfd9; margin-bottom: 0.75rem; }
    .bar-group { flex: 1; display: flex; flex-direction: column; align-items: center; height: 100%; justify-content: flex-end; gap: 0.45rem; }
    .bar { width: 100%; border-radius: 4px 4px 0 0; }
    .bar-group:nth-child(1) .bar { background: #b8cdd8; height: 44%; }
    .bar-group:nth-child(2) .bar { background: #f4c07a; height: 51%; }
    .bar-group:nth-child(3) .bar { background: #2d6a4f; height: 59%; }
    .bar-group:nth-child(4) .bar { background: #f4a0a0; height: 47%; }
    .bar-value { font-size: 0.82rem; font-weight: 700; color: #1c1c1c; }
    .bar-group:nth-child(3) .bar-value { color: #2d6a4f; }
    .bar-label { font-size: 0.72rem; font-weight: 600; color: #6b6b6b; text-align: center; line-height: 1.35; }
    .bar-group:nth-child(3) .bar-label { color: #2d6a4f; }
    .chart-legend { display: grid; gap: 0.35rem; margin-top: 1rem; }
    .legend-row { display: flex; gap: 0.65rem; font-size: 0.8rem; color: #6b6b6b; line-height: 1.5; align-items: baseline; }
    .legend-key { font-weight: 700; min-width: 24px; color: #1c1c1c; flex-shrink: 0; }
    .chart-note { margin-top: 1rem; padding-top: 1rem; border-top: 1px solid #e2dfd9; font-size: 0.78rem; color: #6b6b6b; font-style: italic; }
    .method-box { margin: 2rem 0; border: 1px solid #e2dfd9; border-radius: 8px; overflow: hidden; }
    .method-box-header { background: #f5f3ef; padding: 0.65rem 1.25rem; font-size: 0.72rem; font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase; color: #6b6b6b; border-bottom: 1px solid #e2dfd9; }
    .method-box-body { padding: 1.25rem; }
    .method-row { display: flex; gap: 0.75rem; margin-bottom: 0.7rem; font-size: 0.9rem; line-height: 1.65; align-items: baseline; }
    .method-row:last-child { margin-bottom: 0; }
    .method-key { font-weight: 600; min-width: 120px; flex-shrink: 0; color: #6b6b6b; font-size: 0.76rem; text-transform: uppercase; letter-spacing: 0.05em; }
    .resource-box { margin: 2rem 0; border: 1px solid #e2dfd9; border-radius: 8px; overflow: hidden; }
    .resource-box-header { background: #f5f3ef; padding: 0.65rem 1.25rem; font-size: 0.72rem; font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase; color: #6b6b6b; border-bottom: 1px solid #e2dfd9; }
    .resource-list { padding: 1.1rem 1.25rem; display: grid; gap: 0.7rem; }
    .resource-item { font-size: 0.88rem; line-height: 1.55; }
    .resource-item a { font-weight: 600; }
    .resource-item span { color: #6b6b6b; font-size: 0.83rem; }
    .decay-chart { margin: 2rem 0; background: #f5f3ef; border-radius: 8px; padding: 1.75rem; }
    .decay-chart svg { width: 100%; height: auto; }
    .decay-legend { display: flex; flex-wrap: wrap; gap: 1.25rem; margin-top: 1rem; padding-top: 1rem; border-top: 1px solid #e2dfd9; }
    .decay-legend-item { display: flex; align-items: center; gap: 0.5rem; font-size: 0.82rem; color: #6b6b6b; }
    .decay-legend-color { width: 24px; height: 3px; border-radius: 2px; flex-shrink: 0; }
    .source-ref { font-size: 0.82rem; color: #6b6b6b; font-style: italic; margin-top: 0.5rem; }
    .prompt-card-body details { margin-bottom: 0.55rem; }
    .prompt-card-body summary {
        cursor: pointer; font-size: 0.82rem; color: #6b6b6b; padding: 0.4rem 0;
        user-select: none; list-style: none;
    }
    .prompt-card-body summary::-webkit-details-marker { display: none; }
    .prompt-card-body summary::before { content: &quot;▸ &quot;; font-size: 0.75rem; }
    .prompt-card-body details[open] summary::before { content: &quot;▾ &quot;; }
    .prompt-card-body details[open] .prompt-text { margin-top: 0.5rem; }
&lt;/style&gt;

&lt;p&gt;J’avais un besoin simple : obtenir le meilleur score possible sur une tâche d’évaluation linguistique. Un jeu de données annoté par des humains, 700 textes, et de quoi tester plusieurs prompts dans les mêmes conditions.&lt;/p&gt;

&lt;p&gt;J’ai fait ce qu’on fait tous. J’ai commencé par un prompt élaboré, structuré, conforme aux bonnes pratiques. Puis j’ai testé des variantes. Et le résultat m’a surpris : &lt;strong&gt;un prompt de huit mots a battu un prompt conçu par 21 chercheurs&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;J’ai d’abord pensé à une anomalie. Puis j’ai cherché dans la littérature, et j’ai découvert que ce résultat avait un nom : &lt;em&gt;l’instruction dilution&lt;/em&gt;. Un phénomène documenté, qui touche tous les modèles de langage. L’idée est simple et un peu contre-intuitive : quand on ajoute des informations à un prompt, même correctes, même pertinentes, les performances peuvent se dégrader. Non pas parce que les informations sont fausses, mais parce qu’elles diluent le signal utile dans du bruit.&lt;/p&gt;

&lt;p&gt;Ce qui suit, c’est ce que la recherche récente nous apprend sur ce phénomène, illustré par mon expérience sur ces 700 textes.&lt;/p&gt;

&lt;h2 id=&quot;lexpérience--700-textes-4-prompts&quot;&gt;L’expérience : 700 textes, 4 prompts&lt;/h2&gt;

&lt;p&gt;Le contexte : évaluation automatique du niveau de langue d’apprenants en anglais, selon le référentiel CECRL (les six niveaux A1 à C2). Le modèle testé : GPT-4.1. Le corpus : 700 expressions écrites, chacune évaluée par trois examinateurs humains certifiés.&lt;/p&gt;

&lt;div class=&quot;method-box&quot;&gt;
    &lt;div class=&quot;method-box-header&quot;&gt;Dispositif&lt;/div&gt;
    &lt;div class=&quot;method-box-body&quot;&gt;
        &lt;div class=&quot;method-row&quot;&gt;
            &lt;span class=&quot;method-key&quot;&gt;Corpus&lt;/span&gt;
            &lt;span&gt;700 exemples sous licence CC BY-NC-SA 4.0. Chaque texte est accompagné d&apos;un niveau de référence et des évaluations de trois examinateurs certifiés.&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;method-row&quot;&gt;
            &lt;span class=&quot;method-key&quot;&gt;Modèle&lt;/span&gt;
            &lt;span&gt;GPT-4.1, via API, mêmes paramètres pour tous les prompts testés.&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;method-row&quot;&gt;
            &lt;span class=&quot;method-key&quot;&gt;Métrique&lt;/span&gt;
            &lt;span&gt;Exact match : le modèle a-t-il attribué exactement le bon niveau ?&lt;/span&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;On a construit quatre prompts. Du plus élaboré au plus minimaliste.&lt;/p&gt;

&lt;div class=&quot;prompts-grid&quot;&gt;
    &lt;div class=&quot;prompt-card&quot;&gt;
        &lt;div class=&quot;prompt-card-header&quot;&gt;
            &lt;span class=&quot;prompt-name&quot;&gt;P1: Few-shot, conforme au guide OpenAI&lt;/span&gt;
            &lt;span class=&quot;prompt-score&quot;&gt;44%&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;prompt-card-body&quot;&gt;
            &lt;details&gt;
                &lt;summary&gt;Voir le prompt complet&lt;/summary&gt;
                &lt;div class=&quot;prompt-text&quot;&gt;Classify the CEFR level of the following written text.

###

Rules:
- Output only the CEFR level: A1, A2, B1, B2, C1, or C2
- No explanation, no justification
- If uncertain, output the closest level

Examples:
Text: &quot;I have a dog. His name is Max. He is big and black.&quot;
Level: A1

Text: &quot;Last summer I visited my grandparents. We went to the market every morning.&quot;
Level: A2

(...)

###

Text: &quot;&quot;&quot;&quot;&quot;&quot;
Level:&lt;/div&gt;
            &lt;/details&gt;
            &lt;p class=&quot;prompt-desc&quot;&gt;Instructions en tête, séparateurs &lt;code&gt;###&lt;/code&gt; et &lt;code&gt;&quot;&quot;&quot;&lt;/code&gt;, exemples few-shot, contraintes sur le format. Conforme aux recommandations du guide officiel OpenAI.&lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;prompt-card&quot;&gt;
        &lt;div class=&quot;prompt-card-header&quot;&gt;
            &lt;span class=&quot;prompt-name&quot;&gt;P2: Prompt issu d&apos;une publication académique&lt;/span&gt;
            &lt;span class=&quot;prompt-score&quot;&gt;51%&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;prompt-card-body&quot;&gt;
            &lt;details&gt;
                &lt;summary&gt;Voir le prompt complet&lt;/summary&gt;
                &lt;div class=&quot;prompt-text&quot;&gt;
You are an expert in language proficiency classification based on the Common European Framework of Reference for Languages (CEFR). Your task is to analyze the given text or narrative and determine the best CEFR level [A1, A2, B1, B2, C1, or C2] based on the CEFR descriptors of reading comprehension of learners below:

A1 - Learners of this level can give information about matters of personal relevance (e.g. likes and dislikes, family, pets) using simple words/signs and basic expressions. Learners can also produce simple isolated phrases and sentences.

A2 - Learners of this level can produce a series of simple phrases and sentences linked with simple connectors like &quot;and&quot;, &quot;but&quot; and &quot;because&quot;. Learners have sufficient vocabulary for the expression of basic communicative needs and for coping with simple survival needs.

B1 - Learners of this level can produce straightforward connected texts on a range of familiar subjects within their field of interest, by linking a series of shorter discrete elements into a linear sequence. Learners have a good range of vocabulary related to familiar topics and everyday situations.

B2 - Learners of this level can produce clear, detailed texts on a variety of subjects related to their field of interest, synthesising and evaluating information and arguments from a number of sources. Learners have a good range of vocabulary for matters connected to their field and most general topics.

C1 - Learners of this level can produce clear, well-structured texts of complex subjects, underlining the relevant salient issues, expanding and supporting points of view at some length with subsidiary points, reasons and relevant examples, and rounding off with an appropriate conclusion. Learners can also employ the structure and conventions of a variety of genres, varying the tone, style and register according to addressee, text type and theme.

C2 - Learners of this level can produce clear, smoothly flowing, complex texts in an appropriate and effective style and a logical structure which helps the reader identify significant points. Learners have a good command of a very broad lexical repertoire including idiomatic expressions and colloquialisms; shows awareness of connotative levels of meaning.

Provide only the CEFR level as output directly, without explanation or justification.

Text: «TEXT»

Answer:
&lt;/div&gt;
            &lt;/details&gt;
            &lt;p class=&quot;prompt-desc&quot;&gt;Extrait de &lt;a href=&quot;https://arxiv.org/pdf/2506.01419&quot; target=&quot;_blank&quot;&gt;&lt;em&gt;Universal CEFR: Enabling Open Multilingual Research on Language Proficiency Assessment&lt;/em&gt;&lt;/a&gt; (arXiv:2506.01419). Rédigé par des chercheurs spécialisés en évaluation automatique.&lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;prompt-card winner&quot;&gt;
        &lt;div class=&quot;prompt-card-header&quot;&gt;
            &lt;span class=&quot;prompt-name&quot;&gt;P3: Une phrase (le meilleur résultat 🏆)&lt;/span&gt;
            &lt;span class=&quot;prompt-score&quot;&gt;59%&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;prompt-card-body&quot;&gt;
            &lt;div class=&quot;prompt-text&quot;&gt;Évalue le niveau CECRL de cette production écrite.&lt;/div&gt;
            &lt;p class=&quot;prompt-desc&quot;&gt;Pas de rôle. Pas de critères. Pas de liste des niveaux. Le modèle infère tout (et le fait mieux que quand on lui explique).&lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;prompt-card&quot;&gt;
        &lt;div class=&quot;prompt-card-header&quot;&gt;
            &lt;span class=&quot;prompt-name&quot;&gt;P4: P3 légèrement enrichi&lt;/span&gt;
            &lt;span class=&quot;prompt-score&quot;&gt;47%&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;prompt-card-body&quot;&gt;
            &lt;div class=&quot;prompt-text&quot;&gt;Évalue le niveau CECRL de cette production écrite d&apos;un apprenant d&apos;anglais. Donne un score entre A1, A2, B1, B2, C1, C2.&lt;/div&gt;
            &lt;p class=&quot;prompt-desc&quot;&gt;On ajoute le contexte et la liste des niveaux. Deux informations utiles, en apparence. Résultat : −12 points par rapport à P3.&lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;h2 id=&quot;les-résultats&quot;&gt;Les résultats&lt;/h2&gt;

&lt;div class=&quot;chart-section&quot;&gt;
    &lt;p class=&quot;chart-title&quot;&gt;Accuracy (Exact Match) (GPT-4.1, n=700)&lt;/p&gt;
    &lt;div class=&quot;chart-bars&quot;&gt;
        &lt;div class=&quot;bar-group&quot;&gt;
            &lt;span class=&quot;bar-value&quot;&gt;44%&lt;/span&gt;
            &lt;div class=&quot;bar&quot;&gt;&lt;/div&gt;
            &lt;span class=&quot;bar-label&quot;&gt;P1&lt;br /&gt;Few-shot&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;bar-group&quot;&gt;
            &lt;span class=&quot;bar-value&quot;&gt;51%&lt;/span&gt;
            &lt;div class=&quot;bar&quot;&gt;&lt;/div&gt;
            &lt;span class=&quot;bar-label&quot;&gt;P2&lt;br /&gt;Académique&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;bar-group&quot;&gt;
            &lt;span class=&quot;bar-value&quot;&gt;59%&lt;/span&gt;
            &lt;div class=&quot;bar&quot;&gt;&lt;/div&gt;
            &lt;span class=&quot;bar-label&quot;&gt;P3&lt;br /&gt;Une phrase&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;bar-group&quot;&gt;
            &lt;span class=&quot;bar-value&quot;&gt;47%&lt;/span&gt;
            &lt;div class=&quot;bar&quot;&gt;&lt;/div&gt;
            &lt;span class=&quot;bar-label&quot;&gt;P4&lt;br /&gt;Enrichie&lt;/span&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;chart-legend&quot;&gt;
        &lt;div class=&quot;legend-row&quot;&gt;&lt;span class=&quot;legend-key&quot;&gt;P1&lt;/span&gt;&lt;span&gt;Few-shot conforme au guide OpenAI : 44%&lt;/span&gt;&lt;/div&gt;
        &lt;div class=&quot;legend-row&quot;&gt;&lt;span class=&quot;legend-key&quot;&gt;P2&lt;/span&gt;&lt;span&gt;Prompt de recherche académique (Universal CEFR) : 51%&lt;/span&gt;&lt;/div&gt;
        &lt;div class=&quot;legend-row&quot;&gt;&lt;span class=&quot;legend-key&quot;&gt;P3&lt;/span&gt;&lt;span&gt;&lt;em&gt;« Évalue le niveau CECRL de cette expression écrite »&lt;/em&gt; : &lt;strong&gt;59%&lt;/strong&gt;&lt;/span&gt;&lt;/div&gt;
        &lt;div class=&quot;legend-row&quot;&gt;&lt;span class=&quot;legend-key&quot;&gt;P4&lt;/span&gt;&lt;span&gt;P3 avec contexte et liste des niveaux : 47%&lt;/span&gt;&lt;/div&gt;
    &lt;/div&gt;
    &lt;p class=&quot;chart-note&quot;&gt;Exact match sur l&apos;échelle A1–C2. GPT-4.1 via API. Corpus académique CC BY-NC-SA 4.0, évalué par trois examinateurs humains certifiés.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;P3 gagne, et d’assez loin. Il bat le prompt académique de 8 points, le prompt few-shot de 15 points, et sa propre version enrichie de 12 points.&lt;/p&gt;

&lt;p&gt;Ce dernier écart est celui qui m’a le plus intrigué. Entre P3 et P4, on a seulement ajouté deux informations que GPT-4.1 possède déjà : le fait que l’apprenant étudie l’anglais, et la liste des niveaux CECRL. En les répétant, on ne l’a pas aidé. On l’a gêné.&lt;/p&gt;

&lt;h2 id=&quot;pourquoi-ajouter-de-linformation-dégrade-les-résultats&quot;&gt;Pourquoi ajouter de l’information dégrade les résultats&lt;/h2&gt;

&lt;p&gt;Cet écart de 12 points entre P3 et P4 s’explique par ce que la littérature appelle &lt;em&gt;l’information dilution&lt;/em&gt; : quand on reformule ce que le modèle sait déjà, on dilue le signal utile dans du bruit.&lt;/p&gt;

&lt;p&gt;Le mécanisme n’est pas mystérieux. L’attention des transformers alloue une capacité finie à chaque passe. Quand une partie de cette capacité est consommée par des informations redondantes, même correctes, il en reste moins pour la tâche elle-même. L’attention ne disparaît pas : &lt;strong&gt;elle se disperse sur des tokens non informatifs&lt;/strong&gt;. &lt;a href=&quot;https://arxiv.org/abs/2504.11004&quot;&gt;Jiang et al. (2024)&lt;/a&gt; parlent de &lt;em&gt;“reduced perceptual ability due to the limited context window”&lt;/em&gt; : la fenêtre de contexte est une ressource finie, et chaque token consommé par du bruit est un token en moins pour le signal.&lt;/p&gt;

&lt;p&gt;GPT-4.1 connaît le CECRL. Il a été entraîné sur des données massives d’évaluation linguistique. Quand on lui demande en une phrase d’évaluer selon le CECRL, il active exactement ce qu’il faut. Quand on lui fournit des exemples et des instructions détaillées, aussi bien formulés soient-ils, on lui propose une reformulation de ce qu’il sait déjà. Et cette reformulation crée une friction. C’est pour ça que P1, le prompt le plus élaboré, termine dernier.&lt;/p&gt;

&lt;div class=&quot;callout&quot;&gt;
    &lt;p&gt;Un prompt plus long n&apos;est pas un prompt plus précis. Sur un domaine que le modèle maîtrise, c&apos;est souvent un prompt plus bruyant.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;La question qui se pose alors : à quel point ce phénomène est-il systématique ? Et que se passe-t-il quand on pousse les modèles au-delà de quelques instructions ?&lt;/p&gt;

&lt;h2 id=&quot;trois-profils-de-dégradation&quot;&gt;Trois profils de dégradation&lt;/h2&gt;

&lt;p&gt;Le benchmark &lt;a href=&quot;https://arxiv.org/abs/2507.11538&quot;&gt;IFScale&lt;/a&gt; (Jaroslawicz et al., 2025) a testé 20 modèles sur des tâches comportant de 10 à 500 instructions simultanées. Les résultats révèlent trois profils de dégradation distincts.&lt;/p&gt;

&lt;div class=&quot;decay-chart&quot;&gt;
    &lt;p class=&quot;chart-title&quot;&gt;Trois profils de dégradation selon la densité d&apos;instructions&lt;/p&gt;
    &lt;svg viewBox=&quot;0 0 620 310&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; role=&quot;img&quot; aria-label=&quot;Graphique montrant trois courbes de dégradation : seuil, linéaire et exponentielle&quot;&gt;
        &lt;!-- Grid --&gt;
        &lt;line x1=&quot;60&quot; y1=&quot;20&quot; x2=&quot;60&quot; y2=&quot;260&quot; stroke=&quot;#e2dfd9&quot; stroke-width=&quot;1&quot; /&gt;
        &lt;line x1=&quot;60&quot; y1=&quot;260&quot; x2=&quot;560&quot; y2=&quot;260&quot; stroke=&quot;#e2dfd9&quot; stroke-width=&quot;1&quot; /&gt;
        &lt;!-- Horizontal grid lines --&gt;
        &lt;line x1=&quot;60&quot; y1=&quot;20&quot; x2=&quot;560&quot; y2=&quot;20&quot; stroke=&quot;#e2dfd9&quot; stroke-width=&quot;0.5&quot; stroke-dasharray=&quot;4&quot; /&gt;
        &lt;line x1=&quot;60&quot; y1=&quot;80&quot; x2=&quot;560&quot; y2=&quot;80&quot; stroke=&quot;#e2dfd9&quot; stroke-width=&quot;0.5&quot; stroke-dasharray=&quot;4&quot; /&gt;
        &lt;line x1=&quot;60&quot; y1=&quot;140&quot; x2=&quot;560&quot; y2=&quot;140&quot; stroke=&quot;#e2dfd9&quot; stroke-width=&quot;0.5&quot; stroke-dasharray=&quot;4&quot; /&gt;
        &lt;line x1=&quot;60&quot; y1=&quot;200&quot; x2=&quot;560&quot; y2=&quot;200&quot; stroke=&quot;#e2dfd9&quot; stroke-width=&quot;0.5&quot; stroke-dasharray=&quot;4&quot; /&gt;
        &lt;!-- Y axis labels --&gt;
        &lt;text x=&quot;52&quot; y=&quot;24&quot; text-anchor=&quot;end&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;100%&lt;/text&gt;
        &lt;text x=&quot;52&quot; y=&quot;84&quot; text-anchor=&quot;end&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;75%&lt;/text&gt;
        &lt;text x=&quot;52&quot; y=&quot;144&quot; text-anchor=&quot;end&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;50%&lt;/text&gt;
        &lt;text x=&quot;52&quot; y=&quot;204&quot; text-anchor=&quot;end&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;25%&lt;/text&gt;
        &lt;text x=&quot;52&quot; y=&quot;264&quot; text-anchor=&quot;end&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;0%&lt;/text&gt;
        &lt;!-- X axis labels --&gt;
        &lt;text x=&quot;60&quot; y=&quot;278&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;10&lt;/text&gt;
        &lt;text x=&quot;162&quot; y=&quot;278&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;100&lt;/text&gt;
        &lt;text x=&quot;264&quot; y=&quot;278&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;200&lt;/text&gt;
        &lt;text x=&quot;366&quot; y=&quot;278&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;300&lt;/text&gt;
        &lt;text x=&quot;468&quot; y=&quot;278&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;400&lt;/text&gt;
        &lt;text x=&quot;560&quot; y=&quot;278&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;500&lt;/text&gt;
        &lt;!-- X axis title --&gt;
        &lt;text x=&quot;310&quot; y=&quot;300&quot; text-anchor=&quot;middle&quot; font-size=&quot;11&quot; fill=&quot;#6b6b6b&quot; font-family=&quot;system-ui, sans-serif&quot;&gt;Nombre d&apos;instructions&lt;/text&gt;
        &lt;!-- Threshold decay (green) --&gt;
        &lt;polyline points=&quot;60,22 101,22 162,25 213,32 264,56 315,73 366,82 468,92 560,95&quot; fill=&quot;none&quot; stroke=&quot;#2d6a4f&quot; stroke-width=&quot;2.5&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; /&gt;
        &lt;!-- Linear decay (orange) --&gt;
        &lt;polyline points=&quot;60,30 101,37 162,49 213,63 264,80 315,97 366,111 468,128 560,143&quot; fill=&quot;none&quot; stroke=&quot;#c77d1a&quot; stroke-width=&quot;2.5&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; /&gt;
        &lt;!-- Exponential decay (red) --&gt;
        &lt;polyline points=&quot;60,56 101,128 162,188 213,217 264,226 315,231 366,236 468,241 560,243&quot; fill=&quot;none&quot; stroke=&quot;#c0392b&quot; stroke-width=&quot;2.5&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; /&gt;
        &lt;!-- Data point labels --&gt;
        &lt;text x=&quot;565&quot; y=&quot;90&quot; font-size=&quot;10&quot; fill=&quot;#2d6a4f&quot; font-family=&quot;system-ui, sans-serif&quot; font-weight=&quot;600&quot;&gt;~69%&lt;/text&gt;
        &lt;text x=&quot;565&quot; y=&quot;139&quot; font-size=&quot;10&quot; fill=&quot;#c77d1a&quot; font-family=&quot;system-ui, sans-serif&quot; font-weight=&quot;600&quot;&gt;~49%&lt;/text&gt;
        &lt;text x=&quot;565&quot; y=&quot;250&quot; font-size=&quot;10&quot; fill=&quot;#c0392b&quot; font-family=&quot;system-ui, sans-serif&quot; font-weight=&quot;600&quot;&gt;~7%&lt;/text&gt;
    &lt;/svg&gt;
    &lt;div class=&quot;decay-legend&quot;&gt;
        &lt;div class=&quot;decay-legend-item&quot;&gt;
            &lt;span class=&quot;decay-legend-color&quot; style=&quot;background:#2d6a4f&quot;&gt;&lt;/span&gt;
            &lt;span&gt;&lt;strong&gt;Seuil&lt;/strong&gt; : quasi parfait, puis chute brutale (o3, gemini-2.5-pro)&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;decay-legend-item&quot;&gt;
            &lt;span class=&quot;decay-legend-color&quot; style=&quot;background:#c77d1a&quot;&gt;&lt;/span&gt;
            &lt;span&gt;&lt;strong&gt;Linéaire&lt;/strong&gt; : dégradation régulière et prévisible (gpt-4.1, claude-3.7-sonnet)&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class=&quot;decay-legend-item&quot;&gt;
            &lt;span class=&quot;decay-legend-color&quot; style=&quot;background:#c0392b&quot;&gt;&lt;/span&gt;
            &lt;span&gt;&lt;strong&gt;Exponentiel&lt;/strong&gt; : effondrement rapide, plateau bas (claude-3.5-haiku, llama-4-scout)&lt;/span&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;p class=&quot;source-ref&quot;&gt;Source : IFScale (Jaroslawicz et al., arXiv:2507.11538), 2025.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;Les modèles de raisonnement comme o3 ou gemini-2.5-pro suivent un profil à seuil : les performances restent quasi parfaites jusqu’à 150 ou 250 instructions, puis chutent brutalement. Gemini-2.5-pro passe de 98,4 % à 100 instructions à 68,9 % à 500. &lt;strong&gt;Le modèle encaisse, encaisse, puis cède d’un coup.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Les grands modèles généralistes comme gpt-4.1 ou claude-3.7-sonnet se dégradent de façon régulière et prévisible. GPT-4.1 passe de 95,4 % à 48,9 %, claude-3.7-sonnet de 94,8 % à 52,7 %. Chaque instruction ajoutée coûte un peu de performance. C’est le profil le plus actionnable, parce qu’on peut estimer la perte avant de la subir.&lt;/p&gt;

&lt;p&gt;Les petits modèles comme claude-3.5-haiku ou llama-4-scout s’effondrent rapidement puis se stabilisent sur un plateau bas, entre 7 et 15 %. Au-delà d’une centaine d’instructions, ajouter ou retirer quoi que ce soit ne change presque plus rien : le modèle a déjà décroché.&lt;/p&gt;

&lt;div class=&quot;callout callout-warning&quot;&gt;
    &lt;p&gt;Ce n&apos;est pas « plus court = mieux ». C&apos;est qu&apos;il existe un seuil au-delà duquel la performance s&apos;effondre ; et ce seuil dépend du modèle.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;La nuance est importante. Les modèles de raisonnement résistent remarquablement bien jusqu’à un point critique. Les généralistes se dégradent graduellement mais de façon gérable. Les petits modèles ne sont tout simplement pas conçus pour les prompts denses. Connaître le profil de dégradation du modèle qu’on utilise, c’est savoir combien d’instructions on peut se permettre.&lt;/p&gt;

&lt;h2 id=&quot;omission-vs-modification--comment-les-modèles-échouent&quot;&gt;Omission vs modification : comment les modèles échouent&lt;/h2&gt;

&lt;p&gt;IFScale révèle aussi quelque chose de plus subtil : les modèles n’échouent pas de la même façon selon la densité d’instructions.&lt;/p&gt;

&lt;p&gt;À faible densité, quand un modèle se trompe, il se trompe de peu. Il approxime, il interprète mal une contrainte. C’est ce que les chercheurs appellent une erreur de &lt;em&gt;modification&lt;/em&gt; : le modèle a essayé, et il s’est trompé.&lt;/p&gt;

&lt;p&gt;À haute densité, le mode d’échec change radicalement. Le modèle ne se trompe plus, il oublie. Les instructions ne sont pas mal interprétées, elles sont purement et simplement ignorées. C’est une erreur d’&lt;em&gt;omission&lt;/em&gt;. Et les chiffres sont frappants : à 500 instructions, llama-4-scout présente un ratio omission/modification de 34,88x. Pour chaque erreur d’approximation, 35 instructions sont simplement oubliées. &lt;strong&gt;Le modèle ne fait pas de son mieux avec un résultat imparfait. Il abandonne.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ce basculement a une conséquence directe sur la façon dont on conçoit les prompts. À densité modérée, reformuler ou clarifier une instruction peut aider. À haute densité, ça ne sert à rien, parce que le problème n’est pas que le modèle comprend mal. C’est qu’il ne lit même plus.&lt;/p&gt;

&lt;p&gt;Il y a aussi un effet de compétition cognitive qui mérite d’être mentionné. L’attention dépensée sur le suivi des instructions dégrade la qualité de la tâche principale elle-même. IFScale montre que o3, à 500 instructions, produit environ 1 500 tokens de sortie où chaque troisième mot doit être un mot-clé imposé. Le modèle consacre tellement de capacité au respect des contraintes formelles qu’il n’en reste plus assez pour la tâche.&lt;/p&gt;

&lt;p&gt;Le lien avec mon expérience est direct. P1 et P2 ajoutent des instructions qui entrent en compétition avec la tâche principale d’évaluation. Même si ces instructions sont correctes, elles consomment de l’attention. P3 laisse toute l’attention au modèle pour ce qui compte vraiment.&lt;/p&gt;

&lt;h2 id=&quot;le-biais-positionnel--lost-in-the-middle-et-au-delà&quot;&gt;Le biais positionnel : Lost in the Middle et au-delà&lt;/h2&gt;

&lt;p&gt;L’instruction dilution est amplifiée par un autre problème bien documenté : le biais positionnel.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://arxiv.org/abs/2307.03172&quot;&gt;Liu et al. (Stanford, 2023)&lt;/a&gt; ont montré que les LLMs présentent un biais d’attention en forme de U. Ils traitent mieux ce qui est au début et à la fin du prompt, et dégradent significativement les informations placées au milieu. C’est le phénomène &lt;em&gt;Lost in the Middle&lt;/em&gt;, et il touche même les modèles explicitement entraînés pour les longs contextes.&lt;/p&gt;

&lt;p&gt;IFScale apporte une nuance inattendue sur la tendance du modèle à favoriser les premières instructions, ce qu’on appelle le biais de primauté. Ce biais suit une courbe non linéaire. À faible densité, en dessous de 100 instructions, il est faible : le modèle traite les instructions de façon relativement uniforme. À densité modérée, entre 150 et 200 instructions, il atteint un pic. Le modèle devient sélectif et favorise les premières instructions aux dépens des suivantes. Au-delà de 300 instructions, le biais converge vers un niveau bas, non pas parce que le modèle est redevenu équitable, mais parce qu’il passe en mode « échec uniforme » : il ignore les instructions de façon homogène, quelle que soit leur position.&lt;/p&gt;

&lt;p&gt;En pratique, sur des prompts de longueur modérée (le cas le plus courant en production), &lt;strong&gt;la position compte énormément&lt;/strong&gt;. Ce qui est au début cadre la tâche. Ce qui est à la fin agit comme dernière instruction avant la génération. Ce qui est au milieu est le plus vulnérable à l’oubli. Et c’est une raison de plus de garder les prompts courts : plus le prompt est long, plus le « milieu » est grand, et plus le biais en U est prononcé.&lt;/p&gt;

&lt;h2 id=&quot;ce-nest-pas-toujours-le-plus-court-qui-gagne&quot;&gt;Ce n’est pas toujours le plus court qui gagne&lt;/h2&gt;

&lt;p&gt;Le message de cet article n’est pas « faites des prompts courts ». C’est qu’au-delà d’un certain seuil, l’ajout d’information devient contre-productif, et ce seuil est plus bas qu’on ne le pense.&lt;/p&gt;

&lt;p&gt;Ce seuil dépend de plusieurs choses. D’abord du modèle : les modèles de raisonnement comme o3 ou gemini-2.5-pro résistent beaucoup mieux à la densité d’instructions que les modèles plus petits. Un prompt qui fonctionne sur gpt-4.1 peut s’effondrer sur claude-3.5-haiku.&lt;/p&gt;

&lt;p&gt;Ensuite de la tâche. Mon P3 gagne parce que &lt;strong&gt;GPT-4.1 connaît déjà le CECRL&lt;/strong&gt;. Il a été entraîné sur des volumes massifs de données d’évaluation linguistique. Sur un domaine inconnu du modèle, un framework métier propriétaire, un jargon spécialisé non publié, un prompt détaillé reste nécessaire. C’est justement parce que le modèle ne sait pas qu’il faut lui dire.&lt;/p&gt;

&lt;p&gt;Et enfin de la nature de l’information ajoutée. Toute la différence est entre &lt;strong&gt;information redondante&lt;/strong&gt; et &lt;strong&gt;information nouvelle&lt;/strong&gt;. Reformuler ce que le modèle sait (les descripteurs CECRL, la liste des niveaux) crée du bruit. Fournir ce que le modèle ne sait pas (un barème propriétaire, des critères métier spécifiques) crée du signal.&lt;/p&gt;

&lt;div class=&quot;callout&quot;&gt;
    &lt;p&gt;La règle n&apos;est pas « soyez court ». La règle est : ne reformulez pas ce que le modèle sait. Ajoutez seulement ce qu&apos;il ne sait pas.&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;En pratique, ça demande de connaître les frontières du modèle, ce qui fait partie de son entraînement et ce qui n’en fait pas partie. Cette frontière est rarement documentée. Elle se découvre en testant.&lt;/p&gt;

&lt;h2 id=&quot;prompt-compression--compresser-plutôt-que-supprimer&quot;&gt;Prompt compression : compresser plutôt que supprimer&lt;/h2&gt;

&lt;p&gt;Dans beaucoup de cas d’usage en production (RAG, analyse de documents, contextes longs), on ne peut pas simplement raccourcir le prompt. Le contexte est long parce que le problème l’exige.&lt;/p&gt;

&lt;p&gt;La recherche sur la &lt;em&gt;prompt compression&lt;/em&gt; offre une alternative intéressante : plutôt que de supprimer de l’information, on la compresse. On élimine le bruit tout en préservant le signal.&lt;/p&gt;

&lt;p&gt;La première famille d’approches, dite text-to-text, transforme le texte en un texte plus court, par élagage des tokens non informatifs ou par résumé. &lt;a href=&quot;https://arxiv.org/abs/2403.12968&quot;&gt;LLMLingua-2&lt;/a&gt; de Microsoft atteint des taux de compression de 3 à 6x avec des performances comparables au texte non compressé. Sur NaturalQuestions, le F1 reste à 71,90 avec 3,9x de compression. Sur GSM8K, l’exact match atteint 79,08 à 5x. On divise la taille du contexte par 4 ou 5, et la performance reste quasi identique.&lt;/p&gt;

&lt;p&gt;La seconde famille, text-to-vector, encode le texte en représentations vectorielles compactes, ce que la littérature appelle des &lt;em&gt;gist tokens&lt;/em&gt;. La technique du gisting atteint jusqu’à 26x de compression avec une perte minimale. L’idée : au lieu de faire lire 10 000 tokens au modèle, on les encode en quelques centaines de vecteurs qui capturent l’essentiel de l’information.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://arxiv.org/abs/2504.11004&quot;&gt;LLM-DCP&lt;/a&gt; (Jiang et al., 2024) va plus loin en modélisant la compression comme un processus de décision de Markov. Chaque token est évalué séquentiellement, garder ou supprimer, en fonction de sa contribution à la tâche. Le résultat : 12,9x de compression.&lt;/p&gt;

&lt;div class=&quot;callout&quot;&gt;
    &lt;p&gt;La compression est une alternative à la troncature. Elle préserve le signal en éliminant le bruit (exactement ce que l&apos;instruction dilution nous empêche de faire manuellement).&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;Quand le contexte est long, la question n’est pas « comment raccourcir ? » mais « comment compresser sans perdre le signal ? ». C’est un problème d’ingénierie, pas de rédaction.&lt;/p&gt;

&lt;h2 id=&quot;implications-pratiques&quot;&gt;Implications pratiques&lt;/h2&gt;

&lt;p&gt;De tout ça, je retiens quelques principes que j’applique maintenant au quotidien.&lt;/p&gt;

&lt;p&gt;Le premier, c’est l’atomicité. Une instruction par prompt, formulée en une phrase si possible. Quand une tâche est complexe, je la découpe en étapes plutôt que d’allonger le prompt.&lt;/p&gt;

&lt;p&gt;Le second, c’est la position. L’instruction principale va au début, les contraintes critiques à la fin, le contexte et les exemples au milieu, en sachant qu’ils seront moins bien retenus.&lt;/p&gt;

&lt;p&gt;Le troisième, c’est le test sur volume. Un prompt qui fonctionne sur 10 exemples peut s’effondrer sur 700. Les résultats d’IFScale montrent que les profils de dégradation sont prévisibles : il vaut la peine de tester à différentes densités pour trouver le seuil de son modèle.&lt;/p&gt;

&lt;p&gt;Et enfin, quand un prompt contient plusieurs contraintes, un rappel en fin de prompt (« assure-toi de respecter toutes les contraintes ci-dessus ») peut réduire le taux d’omission. C’est un palliatif, pas une solution, mais il fonctionne sur les profils de dégradation linéaire.&lt;/p&gt;

&lt;h2 id=&quot;mise-à-jour--la-piste-de-la-répétition-du-prompt&quot;&gt;Mise à jour : la piste de la répétition du prompt&lt;/h2&gt;

&lt;p&gt;Suite à la publication de cet article, &lt;a href=&quot;https://www.linkedin.com/in/xiaoouwang/&quot;&gt;Xiaoou Wang&lt;/a&gt; (Ingénieur en humanités numériques, Université Côte d’Azur) m’a suggéré une approche complémentaire que je n’avais pas couverte : la &lt;strong&gt;répétition du prompt&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;L’idée vient d’un paper de Google Research : &lt;a href=&quot;https://arxiv.org/abs/2512.14982&quot;&gt;Prompt Repetition Improves Non-Reasoning LLMs&lt;/a&gt; (Leviathan, Kalman, Matias, 2025). Le principe est simple : au lieu d’envoyer &lt;code&gt;&amp;lt;QUERY&amp;gt;&lt;/code&gt;, on envoie &lt;code&gt;&amp;lt;QUERY&amp;gt;&amp;lt;QUERY&amp;gt;&lt;/code&gt;. On répète le prompt tel quel.&lt;/p&gt;

&lt;p&gt;Le mécanisme exploite une propriété des transformers causaux. Dans un modèle classique, les tokens passés ne peuvent pas « voir » les tokens futurs. Le début du prompt est traité sans le contexte de la fin. En répétant le prompt, chaque token de la seconde copie peut attendre à &lt;em&gt;tous&lt;/em&gt; les tokens de la première. On corrige le biais directionnel sans modifier le contenu.&lt;/p&gt;

&lt;p&gt;Les résultats sont nets : sur 7 modèles (Gemini, GPT, Claude, Deepseek) et 7 benchmarks, la répétition gagne dans 47 cas sur 70, avec &lt;strong&gt;zéro défaite&lt;/strong&gt; (test de McNemar). Et contrairement à d’autres techniques comme le Chain of Thought, la répétition n’augmente ni la latence ni le nombre de tokens générés. Le surcoût se limite au prefill, qui est parallélisable.&lt;/p&gt;

&lt;p&gt;C’est une approche qui s’inscrit bien dans ce que nous avons vu. Là où l’instruction dilution nous dit de ne pas ajouter de bruit, la répétition ne modifie pas le contenu : elle renforce le signal existant en donnant au modèle une seconde passe d’attention sur les mêmes informations. Les chercheurs notent d’ailleurs que les modèles de raisonnement entraînés par RL apprennent souvent à répéter eux-mêmes des parties du prompt dans leur chaîne de pensée. La répétition externalise ce comportement dans le prefill.&lt;/p&gt;

&lt;p&gt;Je ne l’ai pas encore testée personnellement sur mes données, mais les benchmarks sont solides et la technique est triviale à déployer. C’est une piste que je compte explorer, et qui mérite d’être connue.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;L’instruction dilution n’est pas une intuition. C’est un phénomène mesurable, reproductible, et la recherche commence à bien le comprendre. Au-delà d’un certain seuil, ajouter de l’information à un prompt dégrade les performances du modèle. Ce seuil dépend du modèle, de la tâche, et de la nature de l’information ajoutée.&lt;/p&gt;

&lt;p&gt;Les guides de prompting donnent des techniques. La recherche explique pourquoi certaines marchent et d’autres non. Non pas parce que les guides sont faux, mais parce qu’ils sont écrits pour un contexte, la conversation, qui ne se transfère pas directement à un autre, la production à l’échelle.&lt;/p&gt;

&lt;p&gt;Pour écrire des prompts qui fonctionnent à l’échelle, il faut comprendre ce qui se passe dans l’attention du modèle. Savoir ce qu’il sait déjà, ce qu’il ne sait pas, et où se situe le point de basculement. Et ça, ça ne s’apprend que par l’expérimentation. En se trompant sur 700 exemples et en comprenant pourquoi.&lt;/p&gt;

&lt;p&gt;Si vous voulez systématiser cette expérimentation, des outils comme &lt;a href=&quot;https://github.com/promptfoo/promptfoo&quot;&gt;promptfoo&lt;/a&gt; ou le &lt;a href=&quot;https://platform.openai.com/playground&quot;&gt;Playground d’OpenAI&lt;/a&gt; permettent de comparer des variantes de prompts sur un jeu de données, de mesurer les régressions entre versions, et de trouver ce fameux seuil de basculement pour chaque modèle.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>LLMs can code. But can they maintain?</title>
   <link href="https://blog.lepine.pro/en/llms-can-code-but-can-they-maintain/"/>
   <updated>2026-03-10T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/en/llms-can-code-but-can-they-maintain</id>
   <content type="html">&lt;style&gt;
/* Diagram comparatif */
.swe-diagram {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  gap: 0;
  align-items: stretch;
  margin: 2.5rem 0;
  border: 2px solid #e5e7eb;
  border-radius: 10px;
  overflow: hidden;
  background: white;
  box-shadow: 0 4px 16px rgba(0,0,0,0.06);
}
.swe-diagram-panel {
  padding: 1.6rem 1.4rem;
}
.swe-diagram-panel h4 {
  font-family: &apos;Poppins&apos;, sans-serif;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  margin-bottom: 0.7rem;
  font-weight: 700;
}
.swe-diagram-left h4 {
  color: #6b7280;
}
.swe-diagram-right h4 {
  color: #c0392b;
}
.swe-diagram-panel p {
  font-size: 0.9rem;
  line-height: 1.55;
  color: #374151;
  margin: 0;
  text-align: left;
}
.swe-diagram-left {
  background: #f9fafb;
  border-right: 1px solid #e5e7eb;
}
.swe-diagram-right {
  background: #fef2f2;
  border-left: 3px solid #c0392b;
}
.swe-diagram-arrow {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 1rem;
  background: white;
  font-size: 1.6rem;
  color: #c0392b;
  font-weight: bold;
}
@media (max-width: 560px) {
  .swe-diagram { grid-template-columns: 1fr; }
  .swe-diagram-arrow { padding: 0.5rem 0; font-size: 1.2rem; }
  .swe-diagram-left { border-right: none; border-bottom: 1px solid #e5e7eb; }
  .swe-diagram-right { border-left: none; border-top: 3px solid #c0392b; }
}

/* Charts */
.swe-chart {
  margin: 2.5rem auto;
  max-width: 660px;
  background: white;
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  overflow: hidden;
}
.swe-chart-title {
  font-family: &apos;Poppins&apos;, sans-serif;
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: #6b7280;
  padding: 0.9rem 1.2rem 0.6rem;
  border-bottom: 1px solid #e5e7eb;
}
.swe-chart-inner {
  padding: 1.2rem 1rem 0.8rem;
}
.swe-chart-caption {
  font-size: 0.75rem;
  color: #9ca3af;
  padding: 0.6rem 1.2rem 1rem;
  font-style: italic;
  border-top: 1px solid #f3f4f6;
  line-height: 1.5;
  text-align: left;
}

/* Bar chart */
.swe-bars { display: flex; flex-direction: column; gap: 5px; }
.swe-bar-row {
  display: grid;
  grid-template-columns: 130px 1fr 52px;
  align-items: center;
  gap: 8px;
  font-family: &apos;Poppins&apos;, sans-serif;
  font-size: 0.72rem;
}
.swe-bar-label {
  color: #6b7280;
  text-align: right;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.swe-bar-track {
  background: #f3f4f6;
  border-radius: 3px;
  height: 20px;
  position: relative;
  overflow: hidden;
}
.swe-bar-fill {
  height: 100%;                                                                                                                                 
  border-radius: 3px;
  transition: width 0.8s ease;
}
.swe-bar-fill.swe-low  { background: #d1d5db; }
.swe-bar-fill.swe-mid  { background: #86efac; }
.swe-bar-fill.swe-top  { background: #c0392b; }
.swe-bar-value {
  color: #111827;
  font-weight: 600;
  font-size: 0.72rem;
}
@media (max-width: 560px) {
  .swe-bar-row { grid-template-columns: 90px 1fr 44px; }
}

/* Pull quotes */
#post .swe-pullquote {
  border-left: 3px solid #c0392b;
  padding: 1rem 0 1rem 1.5rem;
  margin: 2rem 0;
  font-size: 1.15rem;
  font-style: italic;
  color: #111827;
  line-height: 1.6;
  background: none;
  border-radius: 0;
}
#post .swe-pullquote p {
  margin: 0;
  color: #111827;
  font-size: 1.15rem;
  line-height: 1.6;
}

/* Note box */
.swe-note {
  background: #fef2f2;
  border-left: 3px solid #c0392b;
  padding: 1rem 1.2rem;
  margin: 2rem 0;
  font-size: 0.88rem;
  line-height: 1.6;
  color: #374151;
  border-radius: 0 6px 6px 0;
}
.swe-note strong {
  color: #c0392b;
}
&lt;/style&gt;

&lt;p&gt;I’ve been coding for nearly thirty years. Twenty of them professionally. And I’m going to say something that would have seemed absurd just four years ago: artificial intelligences vastly outperform me in terms of code production. In speed, in volume, often in edge case coverage.&lt;/p&gt;

&lt;p&gt;This isn’t a surrender. It’s an honest observation, and I’m at peace with it. These tools have made me more effective than I’ve ever been. Copilot, Claude, GPT — depending on the context, they regularly impress me. For implementing a known algorithm, wiring up an API, writing unit tests, or refactoring a function, their power is real and now undeniable.&lt;/p&gt;

&lt;p&gt;But for a while, something had been nagging at me. An intuition I couldn’t quite articulate. This paper articulated it for me.&lt;/p&gt;

&lt;p&gt;It’s titled &lt;strong&gt;&lt;a href=&quot;https://arxiv.org/abs/2603.03823&quot;&gt;SWE-CI: Evaluating Agent Capabilities in Maintaining Codebases via Continuous Integration&lt;/a&gt;&lt;/strong&gt;, published in early March 2026 on arXiv by researchers from Sun Yat-sen University and Alibaba Group. It asks a simple and unsettling question: &lt;em&gt;we know LLMs write code — but do they write code that holds up over time?&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;the-problem-nobody-measures&quot;&gt;The problem nobody measures&lt;/h2&gt;

&lt;p&gt;To understand what this work contributes, you need to understand how LLMs are evaluated on code today. Classic benchmarks (&lt;a href=&quot;https://github.com/openai/human-eval&quot;&gt;HumanEval&lt;/a&gt;, &lt;a href=&quot;https://www.swebench.com/&quot;&gt;SWE-bench&lt;/a&gt;, &lt;a href=&quot;https://livecodebench.github.io/&quot;&gt;LiveCodeBench&lt;/a&gt;) all ask the same fundamental question: &lt;em&gt;the agent receives a problem, produces a solution — does it pass the tests?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is what researchers call “snapshot” evaluation: a photo at a single point in time. The model fixes a bug, generates a function, proposes a patch. We check. It works or it doesn’t.&lt;/p&gt;

&lt;div class=&quot;swe-diagram&quot;&gt;
  &lt;div class=&quot;swe-diagram-panel swe-diagram-left&quot;&gt;
    &lt;h4&gt;Classic evaluation (snapshot)&lt;/h4&gt;
    &lt;p&gt;One problem → one solution → tests pass. The agent is evaluated on a single act of production. What came before and what comes after does not exist.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;swe-diagram-arrow&quot;&gt;→&lt;/div&gt;
  &lt;div class=&quot;swe-diagram-panel swe-diagram-right&quot;&gt;
    &lt;h4&gt;What SWE-CI measures&lt;/h4&gt;
    &lt;p&gt;Start from a real codebase, evolve the project across dozens of successive iterations, and measure whether the code &lt;em&gt;remains maintainable&lt;/em&gt; over time.&lt;/p&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;The problem? In real life, software isn’t born in a single night and doesn’t die after its first deployment. It lives, mutates, ages. Features get added, interfaces change, colleagues (or agents) pick up what we’ve written. What matters then isn’t just that a working patch was produced — it’s that this patch didn’t mortgage the next fifty.&lt;/p&gt;

&lt;blockquote class=&quot;swe-pullquote&quot;&gt;An agent that hard-codes a fragile workaround and an agent that writes clean, extensible code can both pass the same tests. Their difference only becomes visible at the third or fourth change.&lt;/blockquote&gt;

&lt;p&gt;This is precisely what &lt;a href=&quot;https://en.wikipedia.org/wiki/Lehman%27s_laws_of_software_evolution&quot;&gt;Lehman’s laws of software evolution&lt;/a&gt; theorized back in the 1970s: software quality degrades naturally as it evolves. And classic literature estimates that maintenance accounts for 60 to 80% of the total lifecycle cost of software. Maintenance, not initial development.&lt;/p&gt;

&lt;h2 id=&quot;how-swe-ci-works&quot;&gt;How SWE-CI works&lt;/h2&gt;

&lt;p&gt;The benchmark is carefully constructed. The researchers combed GitHub for serious Python projects: at least three years of active maintenance, at least 500 stars, a real test suite, a permissive license. From 4,923 filtered projects, they ultimately retained &lt;strong&gt;100 cases from 68 distinct repositories&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For each case, they select two commits on the main branch: a starting commit (the “base”) and a target commit (the “oracle”), separated on average by &lt;strong&gt;233 days and 71 commits&lt;/strong&gt; of real development history. Between the two, at least 500 lines of source code have changed.&lt;/p&gt;

&lt;p&gt;The agent must evolve the base toward the oracle, but not all at once. It proceeds through successive iterations, as a team would in continuous integration. At each turn:&lt;/p&gt;

&lt;p&gt;An “architect” agent analyzes the failing tests, identifies root causes in the code, and produces a requirements document in natural language — no more than five priority requirements, framed in terms of expected behavior, without prescribing the implementation.&lt;/p&gt;

&lt;p&gt;A “developer” agent reads this document, understands the behavioral contracts, plans its modifications, and writes the code. Without running the tests itself — the external system does that.&lt;/p&gt;

&lt;p&gt;This dual protocol reproduces what happens in a real team. The architect doesn’t code. The developer doesn’t over-engineer. And it’s the cumulative result across the entire sequence that is measured.&lt;/p&gt;

&lt;h3 id=&quot;how-to-measure-maintainability&quot;&gt;How to measure maintainability&lt;/h3&gt;

&lt;p&gt;The researchers introduce two original metrics. The first, the &lt;em&gt;normalized change&lt;/em&gt;, measures at each iteration how many additional tests pass relative to the base — with a symmetric penalty if tests that were passing get broken (what we call a regression).&lt;/p&gt;

&lt;p&gt;The second, the &lt;strong&gt;EvoScore&lt;/strong&gt;, aggregates these measurements across the entire sequence with increasing weight toward the later iterations. The idea is simple and sound: truly maintainable code is code that remains &lt;em&gt;easy to modify&lt;/em&gt; as evolution progresses. An agent that succeeds in the early iterations by accumulating technical debt, then collapses afterward, will be penalized. An agent that progresses steadily, even slowly, will be rewarded.&lt;/p&gt;

&lt;h2 id=&quot;what-the-results-show&quot;&gt;What the results show&lt;/h2&gt;

&lt;p&gt;The researchers evaluated &lt;strong&gt;18 models from 8 different providers&lt;/strong&gt;, spending over 10 billion tokens in total. Three major observations emerge.&lt;/p&gt;

&lt;h3 id=&quot;1-llms-are-improving--fast&quot;&gt;1. LLMs are improving — fast&lt;/h3&gt;

&lt;p&gt;Across all model families, recent versions systematically outperform their predecessors. And models released after early 2026 show particularly marked gains. This isn’t linear progression: it’s acceleration. What was difficult a year ago is beginning to be solved.&lt;/p&gt;

&lt;p&gt;Over the entire observation period, the Claude Opus series stands out clearly at the top, with GLM-5 as another remarkable performer.&lt;/p&gt;

&lt;div class=&quot;swe-chart&quot;&gt;
  &lt;div class=&quot;swe-chart-title&quot;&gt;EvoScore by model family — general trend&lt;/div&gt;
  &lt;div class=&quot;swe-chart-inner&quot;&gt;
    &lt;svg viewBox=&quot;0 0 600 200&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; style=&quot;font-family:&apos;Poppins&apos;,sans-serif;&quot;&gt;
      &lt;line x1=&quot;60&quot; y1=&quot;10&quot; x2=&quot;60&quot; y2=&quot;165&quot; stroke=&quot;#e5e7eb&quot; stroke-width=&quot;1&quot; /&gt;
      &lt;line x1=&quot;60&quot; y1=&quot;165&quot; x2=&quot;580&quot; y2=&quot;165&quot; stroke=&quot;#e5e7eb&quot; stroke-width=&quot;1&quot; /&gt;
      &lt;text x=&quot;52&quot; y=&quot;168&quot; text-anchor=&quot;end&quot; font-size=&quot;9&quot; fill=&quot;#9ca3af&quot;&gt;0.2&lt;/text&gt;
      &lt;text x=&quot;52&quot; y=&quot;130&quot; text-anchor=&quot;end&quot; font-size=&quot;9&quot; fill=&quot;#9ca3af&quot;&gt;0.4&lt;/text&gt;
      &lt;text x=&quot;52&quot; y=&quot;92&quot; text-anchor=&quot;end&quot; font-size=&quot;9&quot; fill=&quot;#9ca3af&quot;&gt;0.6&lt;/text&gt;
      &lt;text x=&quot;52&quot; y=&quot;54&quot; text-anchor=&quot;end&quot; font-size=&quot;9&quot; fill=&quot;#9ca3af&quot;&gt;0.8&lt;/text&gt;
      &lt;text x=&quot;52&quot; y=&quot;16&quot; text-anchor=&quot;end&quot; font-size=&quot;9&quot; fill=&quot;#9ca3af&quot;&gt;1.0&lt;/text&gt;
      &lt;line x1=&quot;60&quot; y1=&quot;130&quot; x2=&quot;580&quot; y2=&quot;130&quot; stroke=&quot;#f3f4f6&quot; stroke-width=&quot;1&quot; /&gt;
      &lt;line x1=&quot;60&quot; y1=&quot;92&quot; x2=&quot;580&quot; y2=&quot;92&quot; stroke=&quot;#f3f4f6&quot; stroke-width=&quot;1&quot; /&gt;
      &lt;line x1=&quot;60&quot; y1=&quot;54&quot; x2=&quot;580&quot; y2=&quot;54&quot; stroke=&quot;#f3f4f6&quot; stroke-width=&quot;1&quot; /&gt;
      &lt;text x=&quot;80&quot; y=&quot;178&quot; text-anchor=&quot;middle&quot; font-size=&quot;9&quot; fill=&quot;#9ca3af&quot;&gt;2025-08&lt;/text&gt;
      &lt;text x=&quot;170&quot; y=&quot;178&quot; text-anchor=&quot;middle&quot; font-size=&quot;9&quot; fill=&quot;#9ca3af&quot;&gt;2025-10&lt;/text&gt;
      &lt;text x=&quot;260&quot; y=&quot;178&quot; text-anchor=&quot;middle&quot; font-size=&quot;9&quot; fill=&quot;#9ca3af&quot;&gt;2025-12&lt;/text&gt;
      &lt;text x=&quot;350&quot; y=&quot;178&quot; text-anchor=&quot;middle&quot; font-size=&quot;9&quot; fill=&quot;#9ca3af&quot;&gt;2026-01&lt;/text&gt;
      &lt;text x=&quot;500&quot; y=&quot;178&quot; text-anchor=&quot;middle&quot; font-size=&quot;9&quot; fill=&quot;#9ca3af&quot;&gt;2026-02&lt;/text&gt;
      &lt;!-- Claude line --&gt;
      &lt;polyline points=&quot;80,155 170,140 260,110 350,95 500,28&quot; fill=&quot;none&quot; stroke=&quot;#c0392b&quot; stroke-width=&quot;2.5&quot; stroke-linejoin=&quot;round&quot; /&gt;
      &lt;circle cx=&quot;80&quot; cy=&quot;155&quot; r=&quot;3.5&quot; fill=&quot;#c0392b&quot; /&gt;
      &lt;circle cx=&quot;170&quot; cy=&quot;140&quot; r=&quot;3.5&quot; fill=&quot;#c0392b&quot; /&gt;
      &lt;circle cx=&quot;260&quot; cy=&quot;110&quot; r=&quot;3.5&quot; fill=&quot;#c0392b&quot; /&gt;
      &lt;circle cx=&quot;350&quot; cy=&quot;95&quot; r=&quot;3.5&quot; fill=&quot;#c0392b&quot; /&gt;
      &lt;circle cx=&quot;500&quot; cy=&quot;28&quot; r=&quot;3.5&quot; fill=&quot;#c0392b&quot; /&gt;
      &lt;text x=&quot;508&quot; y=&quot;24&quot; font-size=&quot;9&quot; fill=&quot;#c0392b&quot; font-weight=&quot;600&quot;&gt;Claude Opus&lt;/text&gt;
      &lt;!-- GLM line --&gt;
      &lt;polyline points=&quot;80,158 170,148 260,122 350,108 500,42&quot; fill=&quot;none&quot; stroke=&quot;#5a8a60&quot; stroke-width=&quot;1.8&quot; stroke-linejoin=&quot;round&quot; stroke-dasharray=&quot;4,2&quot; /&gt;
      &lt;circle cx=&quot;500&quot; cy=&quot;42&quot; r=&quot;3&quot; fill=&quot;#5a8a60&quot; /&gt;
      &lt;text x=&quot;508&quot; y=&quot;46&quot; font-size=&quot;9&quot; fill=&quot;#5a8a60&quot;&gt;GLM-5&lt;/text&gt;
      &lt;!-- Other models --&gt;
      &lt;polyline points=&quot;80,162 170,158 260,148 350,138 500,118&quot; fill=&quot;none&quot; stroke=&quot;#d1d5db&quot; stroke-width=&quot;1.5&quot; stroke-linejoin=&quot;round&quot; /&gt;
      &lt;text x=&quot;508&quot; y=&quot;122&quot; font-size=&quot;9&quot; fill=&quot;#9ca3af&quot;&gt;Other models&lt;/text&gt;
    &lt;/svg&gt;
  &lt;/div&gt;
  &lt;div class=&quot;swe-chart-caption&quot;&gt;Schematic representation of EvoScore (γ=1) progression by model release date. Post-2026 models show markedly stronger gains. Source: SWE-CI, Figure 4.&lt;/div&gt;
&lt;/div&gt;

&lt;h3 id=&quot;2-providers-have-different-priorities&quot;&gt;2. Providers have different priorities&lt;/h3&gt;

&lt;p&gt;The γ parameter of the EvoScore allows varying the weight given to early versus late iterations. When you raise γ, you favor models that maintain quality over the long term. When you lower it, you reward immediate gains.&lt;/p&gt;

&lt;p&gt;What the researchers observe is revealing: rankings change depending on γ. MiniMax, DeepSeek, and GPT favor long-term gains. Kimi and GLM prioritize quick returns. Qwen, Doubao, and Claude remain relatively stable regardless of weighting. The authors interpret this as a reflection of training choices — each provider orients its models differently, and it shows.&lt;/p&gt;

&lt;h3 id=&quot;3-regression-remains-the-great-unsolved-problem&quot;&gt;3. Regression remains the great unsolved problem&lt;/h3&gt;

&lt;p&gt;This is the most striking observation, and the most directly useful for anyone using AI in their projects.&lt;/p&gt;

&lt;p&gt;A regression, in development, is when a change breaks something that was working before. It’s every experienced developer’s nightmare. And this is precisely where current LLMs struggle the most.&lt;/p&gt;

&lt;div class=&quot;swe-chart&quot;&gt;
  &lt;div class=&quot;swe-chart-title&quot;&gt;&quot;Zero regression&quot; rate — proportion of trials with no regression introduced&lt;/div&gt;
  &lt;div class=&quot;swe-chart-inner&quot;&gt;
    &lt;div class=&quot;swe-bars&quot;&gt;
      &lt;div class=&quot;swe-bar-row&quot;&gt;
        &lt;div class=&quot;swe-bar-label&quot;&gt;Claude Opus 4.6&lt;/div&gt;
        &lt;div class=&quot;swe-bar-track&quot;&gt;&lt;div class=&quot;swe-bar-fill swe-top&quot; style=&quot;width:84%&quot;&gt;&lt;/div&gt;&lt;/div&gt;
        &lt;div class=&quot;swe-bar-value&quot;&gt;0.76&lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;swe-bar-row&quot;&gt;
        &lt;div class=&quot;swe-bar-label&quot;&gt;Claude Opus 4.5&lt;/div&gt;
        &lt;div class=&quot;swe-bar-track&quot;&gt;&lt;div class=&quot;swe-bar-fill swe-top&quot; style=&quot;width:57%&quot;&gt;&lt;/div&gt;&lt;/div&gt;
        &lt;div class=&quot;swe-bar-value&quot;&gt;0.51&lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;swe-bar-row&quot;&gt;
        &lt;div class=&quot;swe-bar-label&quot;&gt;Kimi-K2.5&lt;/div&gt;
        &lt;div class=&quot;swe-bar-track&quot;&gt;&lt;div class=&quot;swe-bar-fill swe-mid&quot; style=&quot;width:41%&quot;&gt;&lt;/div&gt;&lt;/div&gt;
        &lt;div class=&quot;swe-bar-value&quot;&gt;0.37&lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;swe-bar-row&quot;&gt;
        &lt;div class=&quot;swe-bar-label&quot;&gt;GLM-5&lt;/div&gt;
        &lt;div class=&quot;swe-bar-track&quot;&gt;&lt;div class=&quot;swe-bar-fill swe-mid&quot; style=&quot;width:40%&quot;&gt;&lt;/div&gt;&lt;/div&gt;
        &lt;div class=&quot;swe-bar-value&quot;&gt;0.36&lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;swe-bar-row&quot;&gt;
        &lt;div class=&quot;swe-bar-label&quot;&gt;GPT-5.2&lt;/div&gt;
        &lt;div class=&quot;swe-bar-track&quot;&gt;&lt;div class=&quot;swe-bar-fill swe-low&quot; style=&quot;width:26%&quot;&gt;&lt;/div&gt;&lt;/div&gt;
        &lt;div class=&quot;swe-bar-value&quot;&gt;0.23&lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;swe-bar-row&quot;&gt;
        &lt;div class=&quot;swe-bar-label&quot;&gt;Qwen3.5-plus&lt;/div&gt;
        &lt;div class=&quot;swe-bar-track&quot;&gt;&lt;div class=&quot;swe-bar-fill swe-low&quot; style=&quot;width:22%&quot;&gt;&lt;/div&gt;&lt;/div&gt;
        &lt;div class=&quot;swe-bar-value&quot;&gt;0.20&lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;swe-bar-row&quot;&gt;
        &lt;div class=&quot;swe-bar-label&quot;&gt;DeepSeek-V3.2&lt;/div&gt;
        &lt;div class=&quot;swe-bar-track&quot;&gt;&lt;div class=&quot;swe-bar-fill swe-low&quot; style=&quot;width:22%&quot;&gt;&lt;/div&gt;&lt;/div&gt;
        &lt;div class=&quot;swe-bar-value&quot;&gt;0.20&lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;swe-bar-row&quot;&gt;
        &lt;div class=&quot;swe-bar-label&quot;&gt;MiniMax-M2.5&lt;/div&gt;
        &lt;div class=&quot;swe-bar-track&quot;&gt;&lt;div class=&quot;swe-bar-fill swe-low&quot; style=&quot;width:22%&quot;&gt;&lt;/div&gt;&lt;/div&gt;
        &lt;div class=&quot;swe-bar-value&quot;&gt;0.20&lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;swe-bar-row&quot;&gt;
        &lt;div class=&quot;swe-bar-label&quot;&gt;MiniMax-M2.1&lt;/div&gt;
        &lt;div class=&quot;swe-bar-track&quot;&gt;&lt;div class=&quot;swe-bar-fill swe-low&quot; style=&quot;width:17%&quot;&gt;&lt;/div&gt;&lt;/div&gt;
        &lt;div class=&quot;swe-bar-value&quot;&gt;0.15&lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;swe-bar-row&quot;&gt;
        &lt;div class=&quot;swe-bar-label&quot;&gt;Kimi-K2-Thinking&lt;/div&gt;
        &lt;div class=&quot;swe-bar-track&quot;&gt;&lt;div class=&quot;swe-bar-fill swe-low&quot; style=&quot;width:17%&quot;&gt;&lt;/div&gt;&lt;/div&gt;
        &lt;div class=&quot;swe-bar-value&quot;&gt;0.15&lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;swe-bar-row&quot;&gt;
        &lt;div class=&quot;swe-bar-label&quot;&gt;GLM-4.7 / GLM-4.6&lt;/div&gt;
        &lt;div class=&quot;swe-bar-track&quot;&gt;&lt;div class=&quot;swe-bar-fill swe-low&quot; style=&quot;width:16%&quot;&gt;&lt;/div&gt;&lt;/div&gt;
        &lt;div class=&quot;swe-bar-value&quot;&gt;0.14&lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;swe-bar-row&quot;&gt;
        &lt;div class=&quot;swe-bar-label&quot;&gt;Kimi-K2-instruct&lt;/div&gt;
        &lt;div class=&quot;swe-bar-track&quot;&gt;&lt;div class=&quot;swe-bar-fill swe-low&quot; style=&quot;width:13%&quot;&gt;&lt;/div&gt;&lt;/div&gt;
        &lt;div class=&quot;swe-bar-value&quot;&gt;0.12&lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;swe-bar-row&quot;&gt;
        &lt;div class=&quot;swe-bar-label&quot;&gt;Qwen3-coder-plus&lt;/div&gt;
        &lt;div class=&quot;swe-bar-track&quot;&gt;&lt;div class=&quot;swe-bar-fill swe-low&quot; style=&quot;width:11%&quot;&gt;&lt;/div&gt;&lt;/div&gt;
        &lt;div class=&quot;swe-bar-value&quot;&gt;0.10&lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;swe-bar-row&quot;&gt;
        &lt;div class=&quot;swe-bar-label&quot;&gt;Doubao / Qwen3-Max&lt;/div&gt;
        &lt;div class=&quot;swe-bar-track&quot;&gt;&lt;div class=&quot;swe-bar-fill swe-low&quot; style=&quot;width:9%&quot;&gt;&lt;/div&gt;&lt;/div&gt;
        &lt;div class=&quot;swe-bar-value&quot;&gt;0.08–0.09&lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;swe-chart-caption&quot;&gt;Proportion of trials in which no regression was introduced throughout maintenance. Most models stay below 0.25. Only two models exceed 0.5. Source: SWE-CI, Figure 6.&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;In concrete terms: if you ask most current LLMs to maintain a project over time, in more than 75% of cases, they will break something that was working. Not intentionally. Not through negligence. Through lack of a view of the whole — exactly like a junior developer who fixes a bug without reading the rest of the code.&lt;/p&gt;

&lt;div class=&quot;swe-note&quot;&gt;
  &lt;strong&gt;Reading note.&lt;/strong&gt; These figures evaluate agents in &lt;em&gt;autonomous&lt;/em&gt; mode, without human review between iterations. In practice, an experienced developer supervising AI suggestions will catch these regressions before they accumulate. The paper measures the intrinsic capability of models — not their usefulness in pair programming, which remains very real.
&lt;/div&gt;

&lt;h2 id=&quot;what-this-clarifies-for-me&quot;&gt;What this clarifies for me&lt;/h2&gt;

&lt;p&gt;When I started building &lt;a href=&quot;https://github.com/phpmetrics/PhpMetrics&quot;&gt;phpmetrics&lt;/a&gt;, the central question was: &lt;em&gt;how do you know, objectively, whether a PHP project is healthy?&lt;/em&gt; Not whether it compiles. Not whether it passes tests. But whether the internal structure of the code will allow working with it six months from now without suffering.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Cyclomatic_complexity&quot;&gt;Cyclomatic complexity&lt;/a&gt;. Coupling between modules. Class cohesion. &lt;a href=&quot;https://en.wikipedia.org/wiki/Software_package_metrics&quot;&gt;Component instability&lt;/a&gt;. These metrics aren’t glamorous. They don’t answer the question “does it work?” — they answer the question “will it hold?”&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/Halleck45/ast-metrics&quot;&gt;ast-metrics&lt;/a&gt; extends this logic by going deeper into the syntactic structure of code, independent of language. The idea remains the same: give a picture of maintainability, not just functionality.&lt;/p&gt;

&lt;p&gt;What SWE-CI has just formalized for AI agents is exactly this distinction. And it struck me reading the paper: the researchers built, to evaluate LLMs, the same type of reasoning that has guided these tools from the beginning.&lt;/p&gt;

&lt;blockquote class=&quot;swe-pullquote&quot;&gt;Making it work is necessary. Making it last is different. The two are not measured the same way.&lt;/blockquote&gt;

&lt;p&gt;LLMs excel today at making things work. They are progressing, fast, on the question of making things last. But they’re not there yet — with one exception. And this exception is not trivial: Claude Opus 4.6 reaches a zero-regression rate of 0.76. That’s remarkable. It’s also proof that it’s possible, and that the rest of the market will follow.&lt;/p&gt;

&lt;h2 id=&quot;what-this-means-in-practice&quot;&gt;What this means in practice&lt;/h2&gt;

&lt;p&gt;For me, the practical lesson is twofold.&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;maintainability metrics are not a luxury&lt;/strong&gt;. They may have been when code was entirely human and teams naturally had a memory of the project. They become essential when code is generated at industrial speed, with tools that have no memory between sessions and no vision of the global architecture. Without external measurement, you’re flying blind.&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;AI doesn’t replace architecture — it needs it all the more&lt;/strong&gt;. An LLM generating a function does so in a local context, without seeing adjacent modules, without understanding the constraints that guided past decisions. The more we delegate code production to these tools, the more important it becomes for someone (a human) to maintain the overall vision, set the invariants, define the contracts.&lt;/p&gt;

&lt;p&gt;This isn’t a criticism of AI. It’s a description of what it is today: an extraordinarily powerful production tool that needs a framework so its power doesn’t turn against itself.&lt;/p&gt;

&lt;p&gt;Thirty years of code have taught me that the truly costly problems are almost never bugs. They’re architectural errors discovered too late, poorly thought-out dependencies, abstractions that don’t hold up over time. LLMs haven’t solved that yet. And that’s precisely why tools like &lt;a href=&quot;https://github.com/phpmetrics/PhpMetrics&quot;&gt;phpmetrics&lt;/a&gt; or &lt;a href=&quot;https://github.com/Halleck45/ast-metrics&quot;&gt;ast-metrics&lt;/a&gt; remain useful — not as a bulwark against AI, but as a necessary complement.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;The SWE-CI paper is available on arXiv: &lt;a href=&quot;https://arxiv.org/abs/2603.03823&quot;&gt;arxiv.org/abs/2603.03823&lt;/a&gt;. It’s accessible, well-written, and its data is public on &lt;a href=&quot;https://huggingface.co/datasets/alingua/SWE-CI&quot;&gt;Hugging Face&lt;/a&gt;. If you work with AI agents on real projects, it’s worth a read.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Les LLMs savent coder. Mais savent-ils maintenir ?</title>
   <link href="https://blog.lepine.pro/les-llms-savent-coder-mais-savent-ils-maintenir"/>
   <updated>2026-03-10T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/les-llms-savent-coder-mais-savent-ils-maintenir</id>
   <content type="html">&lt;style&gt;
/* Diagram comparatif */
.swe-diagram {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  gap: 0;
  align-items: stretch;
  margin: 2.5rem 0;
  border: 2px solid #e5e7eb;
  border-radius: 10px;
  overflow: hidden;
  background: white;
  box-shadow: 0 4px 16px rgba(0,0,0,0.06);
}
.swe-diagram-panel {
  padding: 1.6rem 1.4rem;
}
.swe-diagram-panel h4 {
  font-family: &apos;Poppins&apos;, sans-serif;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  margin-bottom: 0.7rem;
  font-weight: 700;
}
.swe-diagram-left h4 {
  color: #6b7280;
}
.swe-diagram-right h4 {
  color: #c0392b;
}
.swe-diagram-panel p {
  font-size: 0.9rem;
  line-height: 1.55;
  color: #374151;
  margin: 0;
  text-align: left;
}
.swe-diagram-left {
  background: #f9fafb;
  border-right: 1px solid #e5e7eb;
}
.swe-diagram-right {
  background: #fef2f2;
  border-left: 3px solid #c0392b;
}
.swe-diagram-arrow {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 1rem;
  background: white;
  font-size: 1.6rem;
  color: #c0392b;
  font-weight: bold;
}
@media (max-width: 560px) {
  .swe-diagram { grid-template-columns: 1fr; }
  .swe-diagram-arrow { padding: 0.5rem 0; font-size: 1.2rem; }
  .swe-diagram-left { border-right: none; border-bottom: 1px solid #e5e7eb; }
  .swe-diagram-right { border-left: none; border-top: 3px solid #c0392b; }
}

/* Charts */
.swe-chart {
  margin: 2.5rem auto;
  max-width: 660px;
  background: white;
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  overflow: hidden;
}
.swe-chart-title {
  font-family: &apos;Poppins&apos;, sans-serif;
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: #6b7280;
  padding: 0.9rem 1.2rem 0.6rem;
  border-bottom: 1px solid #e5e7eb;
}
.swe-chart-inner {
  padding: 1.2rem 1rem 0.8rem;
}
.swe-chart-caption {
  font-size: 0.75rem;
  color: #9ca3af;
  padding: 0.6rem 1.2rem 1rem;
  font-style: italic;
  border-top: 1px solid #f3f4f6;
  line-height: 1.5;
  text-align: left;
}

/* Bar chart */
.swe-bars { display: flex; flex-direction: column; gap: 5px; }
.swe-bar-row {
  display: grid;
  grid-template-columns: 130px 1fr 52px;
  align-items: center;
  gap: 8px;
  font-family: &apos;Poppins&apos;, sans-serif;
  font-size: 0.72rem;
}
.swe-bar-label {
  color: #6b7280;
  text-align: right;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.swe-bar-track {
  background: #f3f4f6;
  border-radius: 3px;
  height: 20px;
  position: relative;
  overflow: hidden;
}
.swe-bar-fill {
  height: 100%;
  border-radius: 3px;
  transition: width 0.8s ease;
}
.swe-bar-fill.swe-low  { background: #d1d5db; }
.swe-bar-fill.swe-mid  { background: #86efac; }
.swe-bar-fill.swe-top  { background: #c0392b; }
.swe-bar-value {
  color: #111827;
  font-weight: 600;
  font-size: 0.72rem;
}
@media (max-width: 560px) {
  .swe-bar-row { grid-template-columns: 90px 1fr 44px; }
}

/* Pull quotes */
#post .swe-pullquote {
  border-left: 3px solid #c0392b;
  padding: 1rem 0 1rem 1.5rem;
  margin: 2rem 0;
  font-size: 1.15rem;
  font-style: italic;
  color: #111827;
  line-height: 1.6;
  background: none;
  border-radius: 0;
}
#post .swe-pullquote p {
  margin: 0;
  color: #111827;
  font-size: 1.15rem;
  line-height: 1.6;
}

/* Note box */
.swe-note {
  background: #fef2f2;
  border-left: 3px solid #c0392b;
  padding: 1rem 1.2rem;
  margin: 2rem 0;
  font-size: 0.88rem;
  line-height: 1.6;
  color: #374151;
  border-radius: 0 6px 6px 0;
}
.swe-note strong {
  color: #c0392b;
}
&lt;/style&gt;

&lt;p&gt;Je code depuis près de trente ans. Vingt ans à titre professionnel. Et je vais dire quelque chose qui aurait semblé absurde il y a encore quatre ans : les intelligences artificielles me surpassent largement en termes de production de code. En vitesse, en volume, souvent en couverture de cas limites.&lt;/p&gt;

&lt;p&gt;Ce n’est pas une capitulation. C’est un constat honnête, et je le vis bien. Ces outils m’ont rendu plus efficace que je ne l’ai jamais été. Copilot, Claude, GPT - selon les contextes, ils m’épatent régulièrement. Pour implémenter un algorithme connu, câbler une API, écrire des tests unitaires ou refactoriser une fonction, leur puissance est réelle et désormais indiscutable.&lt;/p&gt;

&lt;p&gt;Mais depuis un moment, quelque chose me tracassait. Une intuition que je n’arrivais pas tout à fait à formuler. Ce papier l’a formulée pour moi.&lt;/p&gt;

&lt;p&gt;Il s’intitule &lt;strong&gt;&lt;a href=&quot;https://arxiv.org/abs/2603.03823&quot;&gt;SWE-CI: Evaluating Agent Capabilities in Maintaining Codebases via Continuous Integration&lt;/a&gt;&lt;/strong&gt;, publié début mars 2026 sur arXiv par des chercheurs de Sun Yat-sen University et Alibaba Group. Il pose une question simple et dérangeante : &lt;em&gt;on sait que les LLMs écrivent du code - mais est-ce qu’ils écrivent du code qui tient dans le temps ?&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;le-problème-quon-ne-mesure-pas&quot;&gt;Le problème qu’on ne mesure pas&lt;/h2&gt;

&lt;p&gt;Pour comprendre l’apport de ce travail, il faut comprendre comment on évalue aujourd’hui les LLMs sur le code. Les benchmarks classiques (&lt;a href=&quot;https://github.com/openai/human-eval&quot;&gt;HumanEval&lt;/a&gt;, &lt;a href=&quot;https://www.swebench.com/&quot;&gt;SWE-bench&lt;/a&gt;, &lt;a href=&quot;https://livecodebench.github.io/&quot;&gt;LiveCodeBench&lt;/a&gt;) posent tous la même question fondamentale : &lt;em&gt;l’agent reçoit un problème, produit une solution, est-ce que ça passe les tests ?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;C’est ce que les chercheurs appellent l’évaluation « snapshot » : une photo à un instant T. Le modèle corrige un bug, génère une fonction, propose un patch. On vérifie. Ça marche ou pas.&lt;/p&gt;

&lt;div class=&quot;swe-diagram&quot;&gt;
  &lt;div class=&quot;swe-diagram-panel swe-diagram-left&quot;&gt;
    &lt;h4&gt;Évaluation classique (snapshot)&lt;/h4&gt;
    &lt;p&gt;Un problème → une solution → les tests passent. L&apos;agent est évalué sur un seul acte de production. Ce qui précède et ce qui suit n&apos;existe pas.&lt;/p&gt;
  &lt;/div&gt;
  &lt;div class=&quot;swe-diagram-arrow&quot;&gt;→&lt;/div&gt;
  &lt;div class=&quot;swe-diagram-panel swe-diagram-right&quot;&gt;
    &lt;h4&gt;Ce que SWE-CI mesure&lt;/h4&gt;
    &lt;p&gt;Partir d&apos;une base de code réelle, faire évoluer le projet sur des dizaines d&apos;itérations successives, et mesurer si le code &lt;em&gt;reste maintenable&lt;/em&gt; au fil du temps.&lt;/p&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Le problème ? Dans la vraie vie, un logiciel ne naît pas en une nuit et ne meurt pas après son premier déploiement. Il vit, mute, vieillit. Des fonctionnalités s’ajoutent, des interfaces changent, des collègues (ou des agents) reprennent ce qu’on a écrit. Ce qui compte alors, ce n’est pas seulement qu’un patch fonctionnel ait été produit - c’est que ce patch n’ait pas hypothéqué les cinquante suivants.&lt;/p&gt;

&lt;blockquote class=&quot;swe-pullquote&quot;&gt;Un agent qui hard-code une rustine fragile et un agent qui écrit du code propre et extensible peuvent tous les deux passer les mêmes tests. Leur différence n&apos;est visible qu&apos;au troisième ou quatrième changement.&lt;/blockquote&gt;

&lt;p&gt;C’est précisément ce que les &lt;a href=&quot;https://en.wikipedia.org/wiki/Lehman%27s_laws_of_software_evolution&quot;&gt;lois de Lehman sur l’évolution logicielle&lt;/a&gt; théorisaient dès les années 1970 : la qualité d’un logiciel se dégrade naturellement à mesure qu’il évolue. Et la littérature classique estime que la maintenance représente entre 60 et 80 % du coût total du cycle de vie d’un logiciel. La maintenance, pas le développement initial.&lt;/p&gt;

&lt;h2 id=&quot;comment-swe-ci-fonctionne&quot;&gt;Comment SWE-CI fonctionne&lt;/h2&gt;

&lt;p&gt;Le benchmark est construit avec soin. Les chercheurs ont parcouru GitHub à la recherche de projets Python sérieux : au moins trois ans de maintenance active, au moins 500 étoiles, une vraie suite de tests, une licence permissive. Sur 4 923 projets filtrés, ils ont finalement retenu &lt;strong&gt;100 cas issus de 68 dépôts distincts&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Pour chaque cas, ils choisissent deux commits sur la branche principale : un commit de départ (la « base ») et un commit cible (l’« oracle »), séparés en moyenne par &lt;strong&gt;233 jours et 71 commits&lt;/strong&gt; de vrai historique de développement. Entre les deux, au moins 500 lignes de code source ont changé.&lt;/p&gt;

&lt;p&gt;L’agent doit faire évoluer la base vers l’oracle, mais pas en une seule fois. Il procède par itérations successives, comme une équipe le ferait en intégration continue. À chaque tour :&lt;/p&gt;

&lt;p&gt;Un agent « architecte » analyse les tests qui échouent, identifie les causes racines dans le code, et produit un document de requirements en langage naturel - pas plus de cinq exigences prioritaires, formulées en termes de comportement attendu, sans prescrire l’implémentation.&lt;/p&gt;

&lt;p&gt;Un agent « développeur » lit ce document, comprend les contrats comportementaux, planifie ses modifications, et écrit le code. Sans exécuter les tests lui-même - c’est le système externe qui le fait.&lt;/p&gt;

&lt;p&gt;Ce double protocole reproduit ce qui se passe dans une vraie équipe. L’architecte ne code pas. Le développeur ne sur-conçoit pas. Et c’est le résultat cumulé sur toute la séquence qui est mesuré.&lt;/p&gt;

&lt;h3 id=&quot;comment-mesurer-la-maintenabilité&quot;&gt;Comment mesurer la maintenabilité&lt;/h3&gt;

&lt;p&gt;Les chercheurs introduisent deux métriques originales. La première, le &lt;em&gt;normalized change&lt;/em&gt;, mesure à chaque itération combien de tests supplémentaires passent par rapport à la base - avec une pénalité symétrique si des tests qui passaient sont cassés (ce qu’on appelle une régression).&lt;/p&gt;

&lt;p&gt;La seconde, l’&lt;strong&gt;EvoScore&lt;/strong&gt;, agrège ces mesures sur toute la séquence avec un poids croissant vers les dernières itérations. L’idée est simple et juste : un code vraiment maintenable est un code qui reste &lt;em&gt;facile à modifier&lt;/em&gt; quand l’évolution avance. Un agent qui réussit les premières itérations en accumulant de la dette technique, puis s’effondre ensuite, sera pénalisé. Un agent qui progresse régulièrement, même lentement, sera récompensé.&lt;/p&gt;

&lt;h2 id=&quot;ce-que-les-résultats-montrent&quot;&gt;Ce que les résultats montrent&lt;/h2&gt;

&lt;p&gt;Les chercheurs ont évalué &lt;strong&gt;18 modèles de 8 fournisseurs différents&lt;/strong&gt;, en dépensant plus de 10 milliards de tokens au total. Trois observations majeures ressortent.&lt;/p&gt;

&lt;h3 id=&quot;1-les-llms-progressent---vite&quot;&gt;1. Les LLMs progressent - vite&lt;/h3&gt;

&lt;p&gt;Dans toutes les familles de modèles, les versions récentes surpassent systématiquement les précédentes. Et les modèles sortis après début 2026 affichent des gains particulièrement marqués. Ce n’est pas une progression linéaire : c’est une accélération. Ce qui était difficile il y a un an commence à être résolu.&lt;/p&gt;

&lt;p&gt;Sur toute la période d’observation, la série Claude Opus se distingue nettement en tête, avec GLM-5 comme autre performeur remarquable.&lt;/p&gt;

&lt;div class=&quot;swe-chart&quot;&gt;
  &lt;div class=&quot;swe-chart-title&quot;&gt;EvoScore par famille de modèles — tendance générale&lt;/div&gt;
  &lt;div class=&quot;swe-chart-inner&quot;&gt;
    &lt;svg viewBox=&quot;0 0 600 200&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; style=&quot;font-family:&apos;Poppins&apos;,sans-serif;&quot;&gt;
      &lt;line x1=&quot;60&quot; y1=&quot;10&quot; x2=&quot;60&quot; y2=&quot;165&quot; stroke=&quot;#e5e7eb&quot; stroke-width=&quot;1&quot; /&gt;
      &lt;line x1=&quot;60&quot; y1=&quot;165&quot; x2=&quot;580&quot; y2=&quot;165&quot; stroke=&quot;#e5e7eb&quot; stroke-width=&quot;1&quot; /&gt;
      &lt;text x=&quot;52&quot; y=&quot;168&quot; text-anchor=&quot;end&quot; font-size=&quot;9&quot; fill=&quot;#9ca3af&quot;&gt;0.2&lt;/text&gt;
      &lt;text x=&quot;52&quot; y=&quot;130&quot; text-anchor=&quot;end&quot; font-size=&quot;9&quot; fill=&quot;#9ca3af&quot;&gt;0.4&lt;/text&gt;
      &lt;text x=&quot;52&quot; y=&quot;92&quot; text-anchor=&quot;end&quot; font-size=&quot;9&quot; fill=&quot;#9ca3af&quot;&gt;0.6&lt;/text&gt;
      &lt;text x=&quot;52&quot; y=&quot;54&quot; text-anchor=&quot;end&quot; font-size=&quot;9&quot; fill=&quot;#9ca3af&quot;&gt;0.8&lt;/text&gt;
      &lt;text x=&quot;52&quot; y=&quot;16&quot; text-anchor=&quot;end&quot; font-size=&quot;9&quot; fill=&quot;#9ca3af&quot;&gt;1.0&lt;/text&gt;
      &lt;line x1=&quot;60&quot; y1=&quot;130&quot; x2=&quot;580&quot; y2=&quot;130&quot; stroke=&quot;#f3f4f6&quot; stroke-width=&quot;1&quot; /&gt;
      &lt;line x1=&quot;60&quot; y1=&quot;92&quot; x2=&quot;580&quot; y2=&quot;92&quot; stroke=&quot;#f3f4f6&quot; stroke-width=&quot;1&quot; /&gt;
      &lt;line x1=&quot;60&quot; y1=&quot;54&quot; x2=&quot;580&quot; y2=&quot;54&quot; stroke=&quot;#f3f4f6&quot; stroke-width=&quot;1&quot; /&gt;
      &lt;text x=&quot;80&quot; y=&quot;178&quot; text-anchor=&quot;middle&quot; font-size=&quot;9&quot; fill=&quot;#9ca3af&quot;&gt;2025-08&lt;/text&gt;
      &lt;text x=&quot;170&quot; y=&quot;178&quot; text-anchor=&quot;middle&quot; font-size=&quot;9&quot; fill=&quot;#9ca3af&quot;&gt;2025-10&lt;/text&gt;
      &lt;text x=&quot;260&quot; y=&quot;178&quot; text-anchor=&quot;middle&quot; font-size=&quot;9&quot; fill=&quot;#9ca3af&quot;&gt;2025-12&lt;/text&gt;
      &lt;text x=&quot;350&quot; y=&quot;178&quot; text-anchor=&quot;middle&quot; font-size=&quot;9&quot; fill=&quot;#9ca3af&quot;&gt;2026-01&lt;/text&gt;
      &lt;text x=&quot;500&quot; y=&quot;178&quot; text-anchor=&quot;middle&quot; font-size=&quot;9&quot; fill=&quot;#9ca3af&quot;&gt;2026-02&lt;/text&gt;
      &lt;!-- Claude line --&gt;
      &lt;polyline points=&quot;80,155 170,140 260,110 350,95 500,28&quot; fill=&quot;none&quot; stroke=&quot;#c0392b&quot; stroke-width=&quot;2.5&quot; stroke-linejoin=&quot;round&quot; /&gt;
      &lt;circle cx=&quot;80&quot; cy=&quot;155&quot; r=&quot;3.5&quot; fill=&quot;#c0392b&quot; /&gt;
      &lt;circle cx=&quot;170&quot; cy=&quot;140&quot; r=&quot;3.5&quot; fill=&quot;#c0392b&quot; /&gt;
      &lt;circle cx=&quot;260&quot; cy=&quot;110&quot; r=&quot;3.5&quot; fill=&quot;#c0392b&quot; /&gt;
      &lt;circle cx=&quot;350&quot; cy=&quot;95&quot; r=&quot;3.5&quot; fill=&quot;#c0392b&quot; /&gt;
      &lt;circle cx=&quot;500&quot; cy=&quot;28&quot; r=&quot;3.5&quot; fill=&quot;#c0392b&quot; /&gt;
      &lt;text x=&quot;508&quot; y=&quot;24&quot; font-size=&quot;9&quot; fill=&quot;#c0392b&quot; font-weight=&quot;600&quot;&gt;Claude Opus&lt;/text&gt;
      &lt;!-- GLM line --&gt;
      &lt;polyline points=&quot;80,158 170,148 260,122 350,108 500,42&quot; fill=&quot;none&quot; stroke=&quot;#5a8a60&quot; stroke-width=&quot;1.8&quot; stroke-linejoin=&quot;round&quot; stroke-dasharray=&quot;4,2&quot; /&gt;
      &lt;circle cx=&quot;500&quot; cy=&quot;42&quot; r=&quot;3&quot; fill=&quot;#5a8a60&quot; /&gt;
      &lt;text x=&quot;508&quot; y=&quot;46&quot; font-size=&quot;9&quot; fill=&quot;#5a8a60&quot;&gt;GLM-5&lt;/text&gt;
      &lt;!-- Autres --&gt;
      &lt;polyline points=&quot;80,162 170,158 260,148 350,138 500,118&quot; fill=&quot;none&quot; stroke=&quot;#d1d5db&quot; stroke-width=&quot;1.5&quot; stroke-linejoin=&quot;round&quot; /&gt;
      &lt;text x=&quot;508&quot; y=&quot;122&quot; font-size=&quot;9&quot; fill=&quot;#9ca3af&quot;&gt;Autres modèles&lt;/text&gt;
    &lt;/svg&gt;
  &lt;/div&gt;
  &lt;div class=&quot;swe-chart-caption&quot;&gt;Représentation schématique de la progression de l&apos;EvoScore (γ=1) selon la date de sortie des modèles. Les modèles post-2026 montrent des gains nettement plus marqués. Source : SWE-CI, Figure 4.&lt;/div&gt;
&lt;/div&gt;

&lt;h3 id=&quot;2-les-fournisseurs-ont-des-priorités-différentes&quot;&gt;2. Les fournisseurs ont des priorités différentes&lt;/h3&gt;

&lt;p&gt;Le paramètre γ de l’EvoScore permet de faire varier le poids donné aux premières versus aux dernières itérations. Quand on fait monter γ, on favorise les modèles qui maintiennent la qualité sur le long terme. Quand on le fait baisser, on récompense les gains immédiats.&lt;/p&gt;

&lt;p&gt;Ce que les chercheurs observent est révélateur : les classements changent selon γ. MiniMax, DeepSeek et GPT favorisent les gains à long terme. Kimi et GLM privilégient les retours rapides. Qwen, Doubao et Claude restent relativement stables quelle que soit la pondération. Les auteurs interprètent ça comme un reflet des choix de formation - chaque fournisseur oriente ses modèles différemment, et ça se voit.&lt;/p&gt;

&lt;h3 id=&quot;3-la-régression-reste-le-grand-problème-non-résolu&quot;&gt;3. La régression reste le grand problème non résolu&lt;/h3&gt;

&lt;p&gt;C’est l’observation la plus parlante, et la plus directement utile pour quiconque utilise l’IA dans ses projets.&lt;/p&gt;

&lt;p&gt;Une régression, en développement, c’est quand une modification casse quelque chose qui fonctionnait avant. C’est le cauchemar de tout développeur expérimenté. Et c’est précisément là que les LLMs actuels peinent le plus.&lt;/p&gt;

&lt;div class=&quot;swe-chart&quot;&gt;
  &lt;div class=&quot;swe-chart-title&quot;&gt;Taux &quot;zéro régression&quot; — proportion d&apos;essais sans aucune régression introduite&lt;/div&gt;
  &lt;div class=&quot;swe-chart-inner&quot;&gt;
    &lt;div class=&quot;swe-bars&quot;&gt;
      &lt;div class=&quot;swe-bar-row&quot;&gt;
        &lt;div class=&quot;swe-bar-label&quot;&gt;Claude Opus 4.6&lt;/div&gt;
        &lt;div class=&quot;swe-bar-track&quot;&gt;&lt;div class=&quot;swe-bar-fill swe-top&quot; style=&quot;width:84%&quot;&gt;&lt;/div&gt;&lt;/div&gt;
        &lt;div class=&quot;swe-bar-value&quot;&gt;0.76&lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;swe-bar-row&quot;&gt;
        &lt;div class=&quot;swe-bar-label&quot;&gt;Claude Opus 4.5&lt;/div&gt;
        &lt;div class=&quot;swe-bar-track&quot;&gt;&lt;div class=&quot;swe-bar-fill swe-top&quot; style=&quot;width:57%&quot;&gt;&lt;/div&gt;&lt;/div&gt;
        &lt;div class=&quot;swe-bar-value&quot;&gt;0.51&lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;swe-bar-row&quot;&gt;
        &lt;div class=&quot;swe-bar-label&quot;&gt;Kimi-K2.5&lt;/div&gt;
        &lt;div class=&quot;swe-bar-track&quot;&gt;&lt;div class=&quot;swe-bar-fill swe-mid&quot; style=&quot;width:41%&quot;&gt;&lt;/div&gt;&lt;/div&gt;
        &lt;div class=&quot;swe-bar-value&quot;&gt;0.37&lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;swe-bar-row&quot;&gt;
        &lt;div class=&quot;swe-bar-label&quot;&gt;GLM-5&lt;/div&gt;
        &lt;div class=&quot;swe-bar-track&quot;&gt;&lt;div class=&quot;swe-bar-fill swe-mid&quot; style=&quot;width:40%&quot;&gt;&lt;/div&gt;&lt;/div&gt;
        &lt;div class=&quot;swe-bar-value&quot;&gt;0.36&lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;swe-bar-row&quot;&gt;
        &lt;div class=&quot;swe-bar-label&quot;&gt;GPT-5.2&lt;/div&gt;
        &lt;div class=&quot;swe-bar-track&quot;&gt;&lt;div class=&quot;swe-bar-fill swe-low&quot; style=&quot;width:26%&quot;&gt;&lt;/div&gt;&lt;/div&gt;
        &lt;div class=&quot;swe-bar-value&quot;&gt;0.23&lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;swe-bar-row&quot;&gt;
        &lt;div class=&quot;swe-bar-label&quot;&gt;Qwen3.5-plus&lt;/div&gt;
        &lt;div class=&quot;swe-bar-track&quot;&gt;&lt;div class=&quot;swe-bar-fill swe-low&quot; style=&quot;width:22%&quot;&gt;&lt;/div&gt;&lt;/div&gt;
        &lt;div class=&quot;swe-bar-value&quot;&gt;0.20&lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;swe-bar-row&quot;&gt;
        &lt;div class=&quot;swe-bar-label&quot;&gt;DeepSeek-V3.2&lt;/div&gt;
        &lt;div class=&quot;swe-bar-track&quot;&gt;&lt;div class=&quot;swe-bar-fill swe-low&quot; style=&quot;width:22%&quot;&gt;&lt;/div&gt;&lt;/div&gt;
        &lt;div class=&quot;swe-bar-value&quot;&gt;0.20&lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;swe-bar-row&quot;&gt;
        &lt;div class=&quot;swe-bar-label&quot;&gt;MiniMax-M2.5&lt;/div&gt;
        &lt;div class=&quot;swe-bar-track&quot;&gt;&lt;div class=&quot;swe-bar-fill swe-low&quot; style=&quot;width:22%&quot;&gt;&lt;/div&gt;&lt;/div&gt;
        &lt;div class=&quot;swe-bar-value&quot;&gt;0.20&lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;swe-bar-row&quot;&gt;
        &lt;div class=&quot;swe-bar-label&quot;&gt;MiniMax-M2.1&lt;/div&gt;
        &lt;div class=&quot;swe-bar-track&quot;&gt;&lt;div class=&quot;swe-bar-fill swe-low&quot; style=&quot;width:17%&quot;&gt;&lt;/div&gt;&lt;/div&gt;
        &lt;div class=&quot;swe-bar-value&quot;&gt;0.15&lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;swe-bar-row&quot;&gt;
        &lt;div class=&quot;swe-bar-label&quot;&gt;Kimi-K2-Thinking&lt;/div&gt;
        &lt;div class=&quot;swe-bar-track&quot;&gt;&lt;div class=&quot;swe-bar-fill swe-low&quot; style=&quot;width:17%&quot;&gt;&lt;/div&gt;&lt;/div&gt;
        &lt;div class=&quot;swe-bar-value&quot;&gt;0.15&lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;swe-bar-row&quot;&gt;
        &lt;div class=&quot;swe-bar-label&quot;&gt;GLM-4.7 / GLM-4.6&lt;/div&gt;
        &lt;div class=&quot;swe-bar-track&quot;&gt;&lt;div class=&quot;swe-bar-fill swe-low&quot; style=&quot;width:16%&quot;&gt;&lt;/div&gt;&lt;/div&gt;
        &lt;div class=&quot;swe-bar-value&quot;&gt;0.14&lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;swe-bar-row&quot;&gt;
        &lt;div class=&quot;swe-bar-label&quot;&gt;Kimi-K2-instruct&lt;/div&gt;
        &lt;div class=&quot;swe-bar-track&quot;&gt;&lt;div class=&quot;swe-bar-fill swe-low&quot; style=&quot;width:13%&quot;&gt;&lt;/div&gt;&lt;/div&gt;
        &lt;div class=&quot;swe-bar-value&quot;&gt;0.12&lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;swe-bar-row&quot;&gt;
        &lt;div class=&quot;swe-bar-label&quot;&gt;Qwen3-coder-plus&lt;/div&gt;
        &lt;div class=&quot;swe-bar-track&quot;&gt;&lt;div class=&quot;swe-bar-fill swe-low&quot; style=&quot;width:11%&quot;&gt;&lt;/div&gt;&lt;/div&gt;
        &lt;div class=&quot;swe-bar-value&quot;&gt;0.10&lt;/div&gt;
      &lt;/div&gt;
      &lt;div class=&quot;swe-bar-row&quot;&gt;
        &lt;div class=&quot;swe-bar-label&quot;&gt;Doubao / Qwen3-Max&lt;/div&gt;
        &lt;div class=&quot;swe-bar-track&quot;&gt;&lt;div class=&quot;swe-bar-fill swe-low&quot; style=&quot;width:9%&quot;&gt;&lt;/div&gt;&lt;/div&gt;
        &lt;div class=&quot;swe-bar-value&quot;&gt;0.08–0.09&lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;swe-chart-caption&quot;&gt;Proportion des essais dans lesquels aucune régression n&apos;a été introduite tout au long de la maintenance. La plupart des modèles restent sous 0.25. Seuls deux modèles dépassent 0.5. Source : SWE-CI, Figure 6.&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Traduction concrète : si vous demandez à la plupart des LLMs actuels d’entretenir un projet sur la durée, dans plus de 75% des cas, ils vont casser quelque chose qui marchait. Pas intentionnellement. Pas par négligence. Par manque de vision de l’ensemble - exactement comme un développeur junior qui règle un bug sans lire le reste du code.&lt;/p&gt;

&lt;div class=&quot;swe-note&quot;&gt;
  &lt;strong&gt;Note de lecture.&lt;/strong&gt; Ces chiffres évaluent des agents en mode &lt;em&gt;autonome&lt;/em&gt;, sans revue humaine entre les itérations. Dans la pratique, un développeur expérimenté qui supervise les suggestions de l&apos;IA attrapera ces régressions avant qu&apos;elles s&apos;accumulent. Le paper mesure la capacité intrinsèque des modèles - pas leur utilité en pair programming, qui reste très réelle.
&lt;/div&gt;

&lt;h2 id=&quot;ce-que-ça-éclaire-pour-moi&quot;&gt;Ce que ça éclaire pour moi&lt;/h2&gt;

&lt;p&gt;Quand j’ai commencé à construire &lt;a href=&quot;https://github.com/phpmetrics/PhpMetrics&quot;&gt;phpmetrics&lt;/a&gt;, la question centrale était : &lt;em&gt;comment savoir, objectivement, si un projet PHP est en bonne santé ?&lt;/em&gt; Pas si ça compile. Pas si ça passe les tests. Mais si la structure interne du code va permettre qu’on y travaille encore dans six mois sans souffrir.&lt;/p&gt;

&lt;p&gt;La &lt;a href=&quot;https://en.wikipedia.org/wiki/Cyclomatic_complexity&quot;&gt;complexité cyclomatique&lt;/a&gt;. Le couplage entre modules. La cohésion des classes. L’&lt;a href=&quot;https://en.wikipedia.org/wiki/Software_package_metrics&quot;&gt;instabilité des composants&lt;/a&gt;. Ces métriques n’ont rien de glamour. Elles ne répondent pas à la question « ça marche ? » - elles répondent à la question « ça va tenir ? »&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/Halleck45/ast-metrics&quot;&gt;ast-metrics&lt;/a&gt; prolonge cette logique en allant plus profond dans la structure syntaxique du code, indépendamment du langage. L’idée reste la même : donner une image de la maintenabilité, pas seulement de la fonctionnalité.&lt;/p&gt;

&lt;p&gt;Ce que SWE-CI vient de formaliser pour les agents IA, c’est exactement cette distinction. Et ça m’a frappé en lisant le paper : les chercheurs ont construit, pour évaluer les LLMs, le même type de raisonnement que celui qui a guidé ces outils depuis le début.&lt;/p&gt;

&lt;blockquote class=&quot;swe-pullquote&quot;&gt;Faire fonctionner, c&apos;est nécessaire. Faire durer, c&apos;est différent. Les deux ne se mesurent pas de la même façon.&lt;/blockquote&gt;

&lt;p&gt;Les LLMs excellent aujourd’hui à faire fonctionner. Ils progressent, vite, sur la question de faire durer. Mais ils n’y sont pas encore - sauf exception. Et cette exception n’est pas anodine : Claude Opus 4.6 atteint un taux sans régression de 0.76. C’est remarquable. C’est aussi la preuve que c’est possible, et que le reste du marché va suivre.&lt;/p&gt;

&lt;h2 id=&quot;ce-que-ça-implique-concrètement&quot;&gt;Ce que ça implique, concrètement&lt;/h2&gt;

&lt;p&gt;Pour moi, la leçon pratique est double.&lt;/p&gt;

&lt;p&gt;D’abord, &lt;strong&gt;les métriques de maintenabilité ne sont pas un luxe&lt;/strong&gt;. Elles l’étaient peut-être quand le code était entièrement humain et que les équipes avaient naturellement une mémoire du projet. Elles deviennent essentielles quand on génère du code à vitesse industrielle, avec des outils qui n’ont pas de mémoire entre les sessions et aucune vision de l’architecture globale. Sans mesure externe, on avance à l’aveugle.&lt;/p&gt;

&lt;p&gt;Ensuite, &lt;strong&gt;l’IA ne remplace pas l’architecture - elle en a d’autant plus besoin&lt;/strong&gt;. Un LLM qui génère une fonction le fait dans un contexte local, sans voir les modules adjacents, sans comprendre les contraintes qui ont guidé les décisions passées. Plus on délègue la production de code à ces outils, plus il devient important que quelqu’un (un humain) maintienne la vision d’ensemble, fixe les invariants, définit les contrats.&lt;/p&gt;

&lt;p&gt;Ce n’est pas une critique de l’IA. C’est une description de ce qu’elle est aujourd’hui : un outil de production extraordinairement puissant, qui a besoin d’un cadre pour que sa puissance ne se retourne pas contre elle-même.&lt;/p&gt;

&lt;p&gt;Trente ans de code m’ont appris que les problèmes vraiment coûteux ne sont presque jamais des bugs. Ce sont des erreurs d’architecture découvertes trop tard, des dépendances mal pensées, des abstractions qui ne tiennent pas à l’épreuve du temps. Les LLMs n’ont pas encore résolu ça. Et c’est précisément pour ça que des outils comme &lt;a href=&quot;https://github.com/phpmetrics/PhpMetrics&quot;&gt;phpmetrics&lt;/a&gt; ou &lt;a href=&quot;https://github.com/Halleck45/ast-metrics&quot;&gt;ast-metrics&lt;/a&gt; restent utiles - pas comme rempart contre l’IA, mais comme complément nécessaire.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Le paper SWE-CI est disponible sur arXiv : &lt;a href=&quot;https://arxiv.org/abs/2603.03823&quot;&gt;arxiv.org/abs/2603.03823&lt;/a&gt;. Il est accessible, bien écrit, et ses données sont publiques sur &lt;a href=&quot;https://huggingface.co/datasets/alingua/SWE-CI&quot;&gt;Hugging Face&lt;/a&gt;. Si vous travaillez avec des agents IA sur des projets réels, ça vaut le détour.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>How We Unblocked Our PR Flow in 4 Months</title>
   <link href="https://blog.lepine.pro/en/how-we-unblocked-our-pr-flow-in-4-months/"/>
   <updated>2025-11-16T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/en/how-we-unblocked-our-pr-flow-in-4-months</id>
   <content type="html">&lt;p&gt;Six months ago, I joined a &lt;strong&gt;talented&lt;/strong&gt;, &lt;strong&gt;technically strong&lt;/strong&gt; engineering team filled with developers who genuinely cared about quality. It wasn’t a team in trouble. Far from it. And yet, something was &lt;strong&gt;seriously blocking our ability to deliver&lt;/strong&gt;: Pull Requests were taking far too long to review and merge.&lt;/p&gt;

&lt;p&gt;Not a few hours. Not two or three days.&lt;br /&gt;
Sometimes &lt;strong&gt;close to three weeks&lt;/strong&gt; between opening and merging a PR.&lt;/p&gt;

&lt;p&gt;That’s what pushed me to write this article. Not to present a magic method or a heroic fix, but simply to share &lt;strong&gt;a gradual cultural shift&lt;/strong&gt; applied to a problem many teams face without naming it.&lt;/p&gt;

&lt;h2 id=&quot;when-a-skilled-team-gets-slowed-down-by-its-own-prs&quot;&gt;When a skilled team gets slowed down by its own PRs&lt;/h2&gt;

&lt;p&gt;The first thing I noticed was simple: &lt;strong&gt;our PRs were too big&lt;/strong&gt;. They often represented an entire sprint’s worth of work (sometimes more). No one can review that “between meetings”. You need the right moment. And that right moment never comes.&lt;/p&gt;

&lt;p&gt;On top of that, an implicit habit had formed over the years: &lt;strong&gt;leads review PRs&lt;/strong&gt;. It wasn’t written anywhere, but it had become the rule. This created an &lt;strong&gt;automatic bottleneck&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We also lacked visibility into the flow. Nobody really knew which PR was waiting on whom, or for how long. There was no lack of goodwill, just a &lt;strong&gt;structural invisibility&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When I started looking at the data, one number stood out above all others: &lt;strong&gt;19 days on average between opening a PR and merging it&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;the-initial-cultural-shock&quot;&gt;The initial cultural shock&lt;/h2&gt;

&lt;p&gt;When I started talking about changing some habits, I could feel a &lt;strong&gt;quiet apprehension&lt;/strong&gt;. Nothing confrontational. Just that silent, universal developer question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is this going to make our work harder?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Touching the way a team creates, reviews and merges PRs is always a small cultural shock. These are deeply anchored gestures. They shape the daily routine. Changing them feels like poking at the team’s internal gravity.&lt;/p&gt;

&lt;p&gt;But the pushback I expected never came.&lt;br /&gt;
No resistance.&lt;br /&gt;
No tension.&lt;/p&gt;

&lt;p&gt;Just a &lt;strong&gt;cautious curiosity&lt;/strong&gt;, and above all, a willingness to try.&lt;/p&gt;

&lt;h2 id=&quot;the-psychological-mechanisms-slowing-everything-down&quot;&gt;The psychological mechanisms slowing everything down&lt;/h2&gt;

&lt;p&gt;Oversized PRs aren’t only a technical issue. They’re a &lt;strong&gt;cognitive&lt;/strong&gt; issue.&lt;/p&gt;

&lt;p&gt;A big PR is intimidating. It triggers fear of missing something, fear of not understanding fully, fear of making a wrong call. It activates a defensive procrastination mechanism: “I’ll review it when I have a real chunk of time.” That time does not exist in a normal workday.&lt;/p&gt;

&lt;p&gt;Coming back to a PR that has been open for five days requires considerable mental effort. Reloading the context, reinterpreting the intent, rebuilding the mental stack. It’s tiring. And that cognitive cost makes you hesitate even more.&lt;/p&gt;

&lt;p&gt;This wasn’t a matter of motivation. It was a matter of &lt;strong&gt;cognitive load&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;what-we-changed-and-how-it-happened&quot;&gt;What we changed and how it happened&lt;/h2&gt;

&lt;p&gt;Our first step was to introduce a simple goal: &lt;strong&gt;small PRs&lt;/strong&gt;. Not as a rule. Not as a mandate. Just &lt;strong&gt;a shared intention: open PRs more frequently so they become smaller and easier to review&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Very quickly, PRs became easier to write, easier to review, and easier to maintain. A 150 line PR doesn’t trigger any mental resistance. You review it almost without thinking.&lt;/p&gt;

&lt;p&gt;The second step was to &lt;strong&gt;share the reviewing work&lt;/strong&gt;. Not to help the leads, but simply because small PRs invite participation. Reviewing becomes lighter. Everyone starts contributing. This change also happened naturally.&lt;/p&gt;

&lt;p&gt;The third step was to &lt;strong&gt;make the flow visible&lt;/strong&gt;. We used &lt;a href=&quot;https://app.octofirst.com/&quot;&gt;OctoFirst&lt;/a&gt;, the tool I’ve been building for the past two years. At first, it was just a personal dashboard to understand how my teams worked. Then it became an analysis tool. Then a cultural support. Then a way to help teams steadily change their habits.&lt;/p&gt;

&lt;p&gt;And that’s when things accelerated.&lt;/p&gt;

&lt;p&gt;When the team could &lt;strong&gt;see&lt;/strong&gt; which PRs were stuck, &lt;strong&gt;see&lt;/strong&gt; the interactions, &lt;strong&gt;see&lt;/strong&gt; the progress, the shift no longer depended on explanations. It became intuitive.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2025-11-octofirst-lead-time.png&quot; alt=&quot;Lead time on Octofirst&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;the-results-after-4-months&quot;&gt;The results after 4 months&lt;/h2&gt;

&lt;p&gt;Four months later, the metrics looked nothing like before.&lt;/p&gt;

&lt;p&gt;The average time went from &lt;strong&gt;19 days&lt;/strong&gt; to &lt;strong&gt;3 calendar days&lt;/strong&gt;.&lt;br /&gt;
The first review arrived in about ten hours.&lt;br /&gt;
The review ping pong took roughly twenty hours.&lt;br /&gt;
The merge followed soon after.&lt;/p&gt;

&lt;p&gt;We moved from a stalled flow to a fluid one.&lt;br /&gt;
It wasn’t magic.&lt;br /&gt;
It wasn’t overwork.&lt;br /&gt;
It wasn’t even a new process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It was a cultural shift.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;PRs naturally became smaller. Reviews became a reflex. &lt;strong&gt;Collaboration increased dramatically.&lt;/strong&gt;&lt;br /&gt;
The interaction graph had nothing to do with what it looked like before.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2025-11-octofirst-collaboration.png&quot; alt=&quot;Collaboration on Octofirst&quot; /&gt;&lt;/p&gt;
&lt;div class=&quot;caption&quot;&gt;
&lt;div&gt;The team’s interaction graph in OctoFirst, after 4 months of new practices.&lt;/div&gt;
&lt;div&gt;The team collaborates better, and leads are no longer the central bottleneck.&lt;/div&gt;
&lt;/div&gt;

&lt;h2 id=&quot;today-a-fully-embedded-culture&quot;&gt;Today: a fully embedded culture&lt;/h2&gt;

&lt;p&gt;Six months after this shift, the flow is simpler, lighter and clearer.&lt;br /&gt;
What strikes me the most is not the reduction of merge times, nor the charts, nor the metrics.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It’s that everything has become normal.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And that’s what defines a culture: the moment when no one thinks about “the old way” anymore, because the new way makes so much sense that it takes over without effort.&lt;/p&gt;

&lt;p&gt;Which brings me to OctoFirst.&lt;/p&gt;

&lt;p&gt;For more than fifteen years, I’ve been building open source tools (a lot of them). I’ve created tools to measure, analyze, understand. For a long time, this work lived in the shadows: useful for me, useful for a few teams around me, but nothing more. I never thought of “building a product”. I just wanted to understand what teams really experience, beyond impressions.&lt;/p&gt;

&lt;p&gt;Then, slowly, OctoFirst took a shape I didn’t fully expect: a tool that doesn’t just show statistics, but helps teams see what they usually can’t see. To understand their internal dynamics. &lt;strong&gt;To change habits gently. To regain fluidity.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And honestly, it’s the first time I’ve thought: “Maybe what I’m building could really help more people.”&lt;/p&gt;

&lt;p&gt;Not because it’s revolutionary, or “AI powered”. But because I see the concrete, measurable, human effect it can have on a team.&lt;/p&gt;

&lt;p&gt;Today, OctoFirst is still in beta. It’s moving quickly, mostly thanks to the feedback I receive. &lt;strong&gt;And I need that feedback.&lt;/strong&gt;&lt;br /&gt;
If your team struggles with PR latency, if you want to understand your internal interactions, or if you’re simply curious, I’d love for you to try it and tell me what works, what doesn’t, and what should be improved.&lt;/p&gt;

&lt;p&gt;There’s no hidden strategy. I’m just trying to improve a tool I’ve been building for a long time, and which is finally taking the shape of a real product.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you want to take a look or help me improve it: ➡️ &lt;a href=&quot;https://app.octofirst.com/&quot;&gt;app.octofirst.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And if you’d like to discuss, compare practices, or share your experience, reach out.&lt;br /&gt;
These conversations are precisely what helps this tool grow.&lt;/p&gt;

&lt;p&gt;Thank you.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Comment nous avons débloqué notre flux de PRs en 4 mois</title>
   <link href="https://blog.lepine.pro/retex-debloquer-flux-de-pr-en-4-mois"/>
   <updated>2025-11-15T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/retex-debloquer-flux-de-pr-en-4-mois</id>
   <content type="html">&lt;p&gt;Il y a six mois, je suis arrivé dans une équipe &lt;strong&gt;talentueuse&lt;/strong&gt;, &lt;strong&gt;solide techniquement&lt;/strong&gt;, avec des développeurs investis et attentifs à la qualité. Ce n’était pas une équipe en difficulté, loin de là. Pourtant, quelque chose &lt;strong&gt;bloquait fortement notre capacité à livrer&lt;/strong&gt; : les Pull Requests mettaient beaucoup trop de temps à être relues et mergées.&lt;/p&gt;

&lt;p&gt;Pas quelques heures, ou deux ou trois jours. Parfois &lt;strong&gt;près de trois semaines&lt;/strong&gt; entre l’ouverture et le merge.&lt;/p&gt;

&lt;p&gt;Et c’est ce qui m’a poussé à documenter ce qui suit. Pas une méthode magique ou un geste héroïque, juste &lt;strong&gt;un changement culturel progressif&lt;/strong&gt;, appliqué à un problème que beaucoup d’équipes rencontrent sans jamais vraiment le nommer.&lt;/p&gt;

&lt;h2 id=&quot;quand-une-équipe-compétente-se-retrouve-ralentie-par-ses-propres-prs&quot;&gt;Quand une équipe compétente se retrouve ralentie par ses propres PRs&lt;/h2&gt;

&lt;p&gt;Le premier constat que j’ai fait est simple : &lt;strong&gt;les PRs étaient trop grosses&lt;/strong&gt;. Elles représentaient souvent un livrable complet de sprint (voire parfois plusieurs), ce qui les rendait naturellement difficiles à relire. Le reviewer devait tout recharger en tête : le contexte, les intentions, les impacts. On n’aborde pas ce genre de PR « entre deux réunions » ; on attend d’avoir le bon moment. Et ce moment n’arrive jamais.&lt;/p&gt;

&lt;p&gt;D’autant qu’un mécanisme implicite s’était installé avec les années : &lt;strong&gt;les leads relisent&lt;/strong&gt;. Ce n’était écrit nulle part, c’était juste « comme ça ». Résultat : une concentration involontaire des relectures, et donc un &lt;strong&gt;goulot d’étranglement mécanique&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Enfin, nous manquions de visibilité sur le flux. Personne ne savait exactement quelles PRs attendaient quoi, ni depuis combien de temps. Il n’y avait pas d’intention, pas de mauvaise volonté, juste une &lt;strong&gt;invisibilité structurelle&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Quand j’ai commencé à regarder les chiffres, le plus froid de tous était celui-ci : &lt;strong&gt;19 jours en moyenne entre l’ouverture d’une PR et son merge&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;le-choc-culturel-du-début&quot;&gt;Le choc culturel du début&lt;/h2&gt;

&lt;p&gt;Quand j’ai commencé à évoquer l’idée de changer certaines habitudes, j’ai senti une &lt;strong&gt;appréhension silencieuse&lt;/strong&gt;. Rien de frontal. Juste cette question que tout développeur se pose, même inconsciemment :&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Est ce que ça va compliquer notre travail ?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Toucher à la façon de créer, relire et merger des PRs est toujours un petit choc culturel. Ce sont des gestes ancrés, routiniers, presque invisibles. Quand on y touche, on touche à la structure même du quotidien.&lt;/p&gt;

&lt;p&gt;Mais ce que je redoutais ne s’est pas produit. Il n’y a pas eu de forte résistance, ni de crispation.&lt;/p&gt;

&lt;p&gt;Juste une &lt;strong&gt;curiosité prudente&lt;/strong&gt;, et surtout une volonté d’essayer.&lt;/p&gt;

&lt;h2 id=&quot;les-mécanismes-psychologiques-qui-ralentissaient-tout&quot;&gt;Les mécanismes psychologiques qui ralentissaient tout&lt;/h2&gt;

&lt;p&gt;Les grosses PRs ne posent pas seulement un problème technique. Elles posent un problème &lt;strong&gt;cognitif&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Une PR massive intimide. Elle crée de la peur de mal faire, de passer à côté, de ne pas comprendre. Elle déclenche une forme de procrastination défensive : « Je la lirai quand j’aurai un vrai moment ». Mais ce moment n’existe pas dans une journée normale.&lt;/p&gt;

&lt;p&gt;Revenir sur une PR vieille de cinq jours demande un effort mental considérable. On doit tout recharger, tout réinterpréter, retrouver le fil. C’est épuisant. Et ce coût mental nourrit encore plus l’hésitation à se lancer.&lt;/p&gt;

&lt;p&gt;Ce n’était pas une question de volonté. C’était une question de &lt;strong&gt;charge cognitive&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;ce-que-nous-avons-changé-et-comment&quot;&gt;Ce que nous avons changé, et comment&lt;/h2&gt;

&lt;p&gt;La première étape a été d’introduire un objectif simple : &lt;strong&gt;des PRs petites&lt;/strong&gt;. Pas par injonction. Pas avec une règle stricte. Juste &lt;strong&gt;une intention commune : ouvrir des PR plus souvent, pour réduire la taille, donc pour réduire l’effort&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Très vite, les PRs ont commencé à devenir plus faciles à écrire, plus faciles à relire, plus faciles à maintenir. Une PR de 150 lignes ne déclenche plus aucun mécanisme de défense. On la lit presque naturellement.&lt;/p&gt;

&lt;p&gt;La deuxième étape a été de &lt;strong&gt;distribuer la relecture&lt;/strong&gt;. Pas pour faire plaisir aux leads. Pour que chacun contribue à la fluidité du flux. Et là aussi, le changement s’est fait naturellement : une PR petite n’intimide personne, donc tout le monde ose relire.&lt;/p&gt;

&lt;p&gt;Enfin, la troisième étape a été de &lt;strong&gt;rendre le flux visible&lt;/strong&gt;. Nous avons utilisé &lt;a href=&quot;https://app.octofirst.com/&quot;&gt;OctoFirst&lt;/a&gt;, l’outil que je construis depuis deux ans, déjà. Au départ, c’était juste un tableau de métriques pour comprendre le fonctionnement de mes équipes. Puis c’est devenu un outil d’analyse. Puis un support de changement culturel. Puis un moyen d’accompagner les équipes dans leurs habitudes.&lt;/p&gt;

&lt;p&gt;Et c’est là que tout s’est accéléré.&lt;/p&gt;

&lt;p&gt;Lorsque l’équipe a pu &lt;strong&gt;voir&lt;/strong&gt; les PRs bloquées, &lt;strong&gt;voir&lt;/strong&gt; les interactions, &lt;strong&gt;voir&lt;/strong&gt; les progrès, la transformation ne dépendait plus d’explications. Elle devenait naturelle.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2025-11-octofirst-lead-time.png&quot; alt=&quot;Lead time on Octofirst&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;le-résultat-après-4-mois&quot;&gt;Le résultat après 4 mois&lt;/h2&gt;

&lt;p&gt;4 mois plus tard, les métriques étaient méconnaissables.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Le temps moyen était passé de &lt;strong&gt;19 jours&lt;/strong&gt; à &lt;strong&gt;3 jours calendaires&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;La première review arrivait en une dizaine d’heures.&lt;/li&gt;
  &lt;li&gt;Le ping pong de commentaires prenait une vingtaine d’heures.&lt;/li&gt;
  &lt;li&gt;Le merge se faisait dans la foulée.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nous étions passés d’un flux immobile à un flux fluide.&lt;br /&gt;
Ce n’était pas de la magie, ni même du sur effort. Ce n’était même pas un nouveau process. &lt;strong&gt;C’était juste une culture différente.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Les PRs étaient devenues naturellement petites. Les relectures étaient devenues un réflexe. &lt;strong&gt;La collaboration avait explosé.&lt;/strong&gt;&lt;br /&gt;
Le graphe des interactions ne ressemblait plus du tout à ce qu’il était.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2025-11-octofirst-collaboration.png&quot; alt=&quot;Collaboration on octofirst&quot; /&gt;&lt;/p&gt;
&lt;div class=&quot;caption&quot;&gt;
&lt;div&gt;Le graphe des interactions de l&apos;équipe sur Octofirst, après 4 mois de changement de pratiques.&lt;/div&gt; 
&lt;div&gt;Les équipes collaborent bien, et les lead ne centralisent plus tout&lt;/div&gt;
&lt;/div&gt;

&lt;h2 id=&quot;aujourdhui--une-culture-installée&quot;&gt;Aujourd’hui : une culture installée&lt;/h2&gt;

&lt;p&gt;Six mois après cette transformation, notre flux est devenu plus simple, plus léger, plus lisible. Et ce qui me frappe le plus, ce n’est pas la baisse du temps de merge, ni les graphiques, ni les chiffres.
&lt;strong&gt;C’est que tout cela est devenu normal.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Je crois que &lt;strong&gt;c’est ce qui définit vraiment une culture : le moment où plus personne ne réfléchit “à l’ancienne manière”, parce que la nouvelle façon de faire a tellement de sens qu’elle s’impose d’elle-même.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ce qui m’amène à OctoFirst. Depuis plus de quinze ans, je fais de l’open source (beaucoup !). 
J’ai construit des outils pour mesurer, analyser, comprendre. Pendant longtemps, ce travail est resté dans l’ombre : utile pour moi, utile pour quelques équipes 
autour de moi, mais rien de plus. Je n’ai jamais pensé “faire un produit”. Je voulais juste comprendre ce que les équipes vivent réellement, au-delà des impressions.&lt;/p&gt;

&lt;p&gt;Et puis, petit à petit, OctoFirst a pris une forme qui me dépasse un peu : un outil qui n’est pas seulement là pour afficher des statistiques, 
mais pour aider les équipes à voir ce qu’elles ne voient pas d’habitude. À comprendre leurs dynamiques internes. &lt;strong&gt;À changer leurs habitudes en douceur.
À retrouver de la fluidité.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Pour être honnête, c’est la première fois que je me dis : « Peut-être que ce que je construis peut aider vraiment davantage de monde. »&lt;/p&gt;

&lt;p&gt;Pas parce que c’est “révolutionnaire”, ou que c’est “IA-powered”. Juste parce que je vois l’effet concret, mesurable, humain que ça peut avoir sur une équipe.&lt;/p&gt;

&lt;p&gt;Aujourd’hui, OctoFirst est encore en bêta. Il avance vite, mais surtout grâce aux retours que je reçois. &lt;strong&gt;Et j’ai besoin de ce feedback.&lt;/strong&gt;
Si votre équipe rencontre des latences dans les PRs, si vous voulez mieux comprendre vos interactions internes, si vous êtes simplement curieux, alors 
j’aimerais beaucoup que vous l’essayiez et que vous me disiez ce qui fonctionne, ce qui manque, ce qui pourrait être mieux.&lt;/p&gt;

&lt;p&gt;Il n’y a pas de stratégie cachée. Je cherche juste à améliorer un outil que je construis depuis longtemps, et qui commence 
enfin à prendre la forme d’un produit réel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Si vous voulez jeter un œil ou m’aider à l’améliorer : ➡️ &lt;a href=&quot;https://app.octofirst.com/&quot;&gt;app.octofirst.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Et si vous avez envie d’en discuter, de comparer vos pratiques, ou de partager votre expérience, écrivez-moi. 
C’est exactement grâce à ces échanges que cet outil progresse.&lt;/p&gt;

&lt;p&gt;Merci !&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>How to Audit Go Code with Static Analysis: A Deep Dive into ast-metrics</title>
   <link href="https://blog.lepine.pro/en/how-to-audit-go-code-with-static-analysis-a-deep-dive-into-ast-metrics/"/>
   <updated>2025-09-18T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/en/how-to-audit-go-code-with-static-analysis-a-deep-dive-into-ast-metrics</id>
   <content type="html">&lt;p&gt;Static code analysis is one of the most powerful techniques for maintaining code quality at scale. When it comes to Go, there are several excellent tools available, each with their own strengths and trade-offs. In this post, I’ll walk you through the landscape of Go static analysis tools, explain why I built ast-metrics, and show you how to use it effectively in your projects.&lt;/p&gt;

&lt;h2 id=&quot;understanding-static-analysis-and-ast&quot;&gt;Understanding Static Analysis and AST&lt;/h2&gt;

&lt;p&gt;Before diving into tools, let’s understand what static analysis actually means and how it works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Static analysis&lt;/strong&gt; examines source code without executing it, looking for patterns that might indicate bugs, security vulnerabilities, or code quality issues. It’s like having a very thorough code reviewer that never gets tired and can process thousands of lines in seconds.&lt;/p&gt;

&lt;p&gt;The foundation of static analysis is the &lt;strong&gt;Abstract Syntax Tree (AST)&lt;/strong&gt;. When you write Go code like this:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func calculateTotal(items []Item) float64 {
    total := 0.0
    for _, item := range items {
        total += item.Price
    }
    return total
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The Go compiler first parses this into an AST - a tree structure where each node represents a construct in your code. The &lt;code&gt;func&lt;/code&gt; becomes a function node, the &lt;code&gt;for&lt;/code&gt; loop becomes a loop node, and so on. Static analyzers traverse this tree, looking for specific patterns and relationships.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2025-09-function-ast.png&quot; alt=&quot;example of function AST&quot; class=&quot;m-auto max-w-full max-h-128 rounded &quot; /&gt;&lt;/p&gt;

&lt;p&gt;This is powerful because it means we can detect not just syntax errors, but semantic issues like:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Functions that are too complex&lt;/li&gt;
  &lt;li&gt;Dependencies that violate architectural rules&lt;/li&gt;
  &lt;li&gt;Code that might panic at runtime&lt;/li&gt;
  &lt;li&gt;Security vulnerabilities&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Analyzing the AST is also a way to get insights into the architecture of the code.&lt;/p&gt;

&lt;h2 id=&quot;the-go-static-analysis-landscape&quot;&gt;The Go Static Analysis Landscape&lt;/h2&gt;

&lt;p&gt;Go has a rich ecosystem of static analysis tools, each serving different needs:&lt;/p&gt;

&lt;h3 id=&quot;golangci-lint&quot;&gt;golangci-lint&lt;/h3&gt;
&lt;p&gt;The most popular choice, golangci-lint is actually a meta-linter that runs multiple linters in parallel. It’s fast, configurable, and catches a wide range of issues.&lt;/p&gt;

&lt;p&gt;To start using it, you just need to install it and run it:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.4.0
golangci-lint run
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then you get a report like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2025-09-golangci-lint.png&quot; alt=&quot;example of golangci-lint report&quot; class=&quot;m-auto max-w-full max-h-128 rounded &quot; /&gt;&lt;/p&gt;

&lt;p&gt;This tool is really useful for catching bugs and security vulnerabilities. But it doesn’t provide any architectural insights.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strengths:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Comprehensive coverage with 50+ linters&lt;/li&gt;
  &lt;li&gt;Excellent performance through parallel execution&lt;/li&gt;
  &lt;li&gt;Great CI integration&lt;/li&gt;
  &lt;li&gt;Active community and regular updates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Limitations:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Some linters produce overlapping warnings&lt;/li&gt;
  &lt;li&gt;Configuration can be complex for large teams&lt;/li&gt;
  &lt;li&gt;Focuses mainly on code style and basic quality issues&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;go-vet&quot;&gt;go vet&lt;/h3&gt;
&lt;p&gt;Built into the Go toolchain, &lt;code&gt;go vet&lt;/code&gt; catches common mistakes that the compiler doesn’t catch.&lt;/p&gt;

&lt;p&gt;You have nothing to do to use it, it’s built into the Go toolchain. Just run it:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;go vet ./...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Gives something like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2025-09-govet.png&quot; alt=&quot;example of go vet report&quot; class=&quot;m-auto max-w-full max-h-128 rounded &quot; /&gt;&lt;/p&gt;

&lt;p&gt;Having a tool built into the language is a big plus. It’s really something super in Golang. But it’s still very basic (useful!), and very focused on instructions, not on architecture.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strengths:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Always up-to-date with Go releases&lt;/li&gt;
  &lt;li&gt;Zero configuration required&lt;/li&gt;
  &lt;li&gt;Very fast execution&lt;/li&gt;
  &lt;li&gt;Catches subtle bugs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Limitations:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Limited scope compared to third-party tools&lt;/li&gt;
  &lt;li&gt;No architectural analysis&lt;/li&gt;
  &lt;li&gt;No complexity metrics&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;staticcheck&quot;&gt;staticcheck&lt;/h3&gt;
&lt;p&gt;A sophisticated linter that goes beyond basic checks to find bugs and performance issues.&lt;/p&gt;

&lt;p&gt;It detects dead code, uninitialized variables, and more.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;staticcheck ./...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;After running it, you get a report like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2025-09-staticcheck.png&quot; alt=&quot;example of staticcheck report&quot; class=&quot;m-auto max-w-full max-h-128   rounded&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This tool is really useful for finding bugs and performance issues. But it doesn’t provide any architectural insights. But, just like the others, it also doesn’t help you understand the architecture of your codebase.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strengths:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Excellent at finding real bugs&lt;/li&gt;
  &lt;li&gt;Good performance&lt;/li&gt;
  &lt;li&gt;Clear, actionable error messages&lt;/li&gt;
  &lt;li&gt;Catches issues other tools miss&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Limitations:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Focuses on correctness rather than architecture&lt;/li&gt;
  &lt;li&gt;No complexity or maintainability metrics&lt;/li&gt;
  &lt;li&gt;Limited architectural analysis&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;sonarqube&quot;&gt;SonarQube&lt;/h3&gt;

&lt;p&gt;SonarQube is more a debt detection tool than a static analysis tool, even if it can be used for static analysis.&lt;/p&gt;

&lt;p&gt;It’s very useful for detecting debt and code smells, and give insights into the codebase.&lt;/p&gt;

&lt;p&gt;But it’s very heavy and resource-intensive, and requires a significant infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I’m not the best person to talk about it, as I haven’t used it in years.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strengths:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Very comprehensive analysis&lt;/li&gt;
  &lt;li&gt;Great reporting and dashboards&lt;/li&gt;
  &lt;li&gt;Supports many languages&lt;/li&gt;
  &lt;li&gt;Good for enterprise environments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Limitations:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Heavy and resource-intensive&lt;/li&gt;
  &lt;li&gt;Complex setup and maintenance&lt;/li&gt;
  &lt;li&gt;Can be overkill for smaller projects&lt;/li&gt;
  &lt;li&gt;Requires significant infrastructure&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;why-i-built-ast-metrics&quot;&gt;Why I Built ast-metrics&lt;/h2&gt;

&lt;p&gt;I code since more than 20 years, and I noticed that static analysis tools were often limited. In general, I notice that:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Architectural analysis was limited&lt;/strong&gt;: most tools focus on individual files or functions, but don’t help you understand how your code is structured at a higher level.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Complexity metrics were scattered&lt;/strong&gt;: you could get cyclomatic complexity from one tool, coupling from another, but there was no unified view.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;CI integration was cumbersome&lt;/strong&gt;: setting up comprehensive analysis in CI often meant running multiple tools and combining their outputs.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Reports were not actionable&lt;/strong&gt;: many tools produce lists of issues, but don’t help you understand the bigger picture or prioritize fixes.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I have a good experience with static analysis tools. In 2013, I created &lt;a href=&quot;https://github.com/Halleck45/PhpMetrics&quot;&gt;PhpMetrics&lt;/a&gt;, a static analysis tool for PHP. I immediately imagined something very different, &lt;strong&gt;not focused on syntax or language sugar, but rather on architecture and code quality in general&lt;/strong&gt;. A tool to give feedback, and not to decide for the developer.&lt;/p&gt;

&lt;p&gt;It’s a tool that is quite unique in its approach, and I haven’t found anything like it in static analysis tools for other languages.&lt;/p&gt;

&lt;p&gt;Since then, I have had the opportunity to work on more and more complex projects, with more and more teams. I noticed that static analysis tools were often limited, and that teams didn’t know how to use them. It’s necessary to configure them for each language, integrate them into the CI, etc.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I have therefore decided to create ast-metrics, a static analysis tool agnostic of the programming language, and above all which gives a view of the code and its architecture.&lt;/strong&gt; Not necessarily a tool to check such or such instruction, but rather something that tells you if your packages are too coupled, if your methods are too complex, if your data structures are too large, etc.&lt;/p&gt;

&lt;p&gt;ast-metrics was born from the need for a tool that provides architectural insights, complexity metrics, and activity analysis in a single, fast binary that’s easy to integrate into any workflow, for any language.&lt;/p&gt;

&lt;h2 id=&quot;key-features-of-ast-metrics&quot;&gt;Key Features of ast-metrics&lt;/h2&gt;

&lt;h3 id=&quot;multi-language-support&quot;&gt;Multi-language Support&lt;/h3&gt;
&lt;p&gt;While built in Go, ast-metrics supports multiple languages (Go, Python, Rust, PHP) with more coming. This makes it valuable for polyglot projects.&lt;/p&gt;

&lt;h3 id=&quot;architectural-analysis&quot;&gt;Architectural Analysis&lt;/h3&gt;
&lt;p&gt;The tool’s most distinctive feature is its ability to detect “communities” - clusters of tightly coupled components in your codebase. This helps you understand your architecture and identify refactoring opportunities.&lt;/p&gt;

&lt;h3 id=&quot;comprehensive-metrics&quot;&gt;Comprehensive Metrics&lt;/h3&gt;
&lt;p&gt;ast-metrics calculates over 20 different metrics including:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Cyclomatic complexity&lt;/li&gt;
  &lt;li&gt;Maintainability index, Bugs probability&lt;/li&gt;
  &lt;li&gt;Coupling (afferent and efferent)&lt;/li&gt;
  &lt;li&gt;Lines of code (physical and logical)&lt;/li&gt;
  &lt;li&gt;Activity metrics (commits, bus factor)&lt;/li&gt;
  &lt;li&gt;Lack of cohesion of methods (LCOM)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and more.&lt;/p&gt;

&lt;h3 id=&quot;fast-and-dependency-free&quot;&gt;Fast and Dependency-free&lt;/h3&gt;
&lt;p&gt;A single binary with no external dependencies, making it perfect for CI/CD pipelines.&lt;/p&gt;

&lt;p&gt;I think we can make it much more performant, but it’s a project that is in constant development, and is constantly evolving.&lt;/p&gt;

&lt;h3 id=&quot;a-cli-app-with-html-reports&quot;&gt;A CLI app, with HTML reports&lt;/h3&gt;

&lt;p&gt;It’s not just a tool that throws a bunch of numbers at you. You can actually navigate through your code metrics right in your terminal (thanks to &lt;a href=&quot;https://github.com/charmbracelet/lipgloss&quot;&gt;lipgloss&lt;/a&gt; and &lt;a href=&quot;https://github.com/charmbracelet/bubbletea&quot;&gt;bubbletea&lt;/a&gt;!)&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2025-09-ast-metrics-cli-app.png&quot; alt=&quot;example of CLI report&quot; class=&quot;m-auto max-w-full max-h-128   rounded&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;getting-started-with-ast-metrics&quot;&gt;Getting Started with ast-metrics&lt;/h2&gt;

&lt;h3 id=&quot;installation&quot;&gt;Installation&lt;/h3&gt;

&lt;p&gt;The easiest way to install ast-metrics is using the provided script:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl -s https://raw.githubusercontent.com/Halleck45/ast-metrics/main/scripts/download.sh | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This downloads the appropriate binary for your platform. For manual installation, check the &lt;a href=&quot;https://halleck45.github.io/ast-metrics/getting-started/install/&quot;&gt;official documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;basic-analysis&quot;&gt;Basic Analysis&lt;/h3&gt;

&lt;p&gt;To analyze your Go project:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ast-metrics analyze --report-html=./report /path/to/your/code
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This generates an HTML report in the &lt;code&gt;./report&lt;/code&gt; directory. The report includes multiple views:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Overview&lt;/strong&gt;: High-level metrics and trends&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Risks&lt;/strong&gt;: Potential issues and code smells&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Explorer&lt;/strong&gt;: Detailed file-by-file analysis&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Architecture&lt;/strong&gt;: Architectural insights&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Compare&lt;/strong&gt;: Branch comparison (if using Git, and if you used the &lt;code&gt;--compare-with&lt;/code&gt; option)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;## Exploring the report&lt;/p&gt;

&lt;p&gt;I invite you to explore the Architecture page, which gives you an overview of the architecture of your code.&lt;/p&gt;

&lt;p&gt;For example, here is the architecture report for the &lt;a href=&quot;https://github.com/Halleck45/ast-metrics&quot;&gt;ast-metrics&lt;/a&gt; project:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2025-09-ast-metrics-architecture.png&quot; alt=&quot;example of architecture report&quot; class=&quot;m-auto max-w-full max-h-128   rounded&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In the top of the chart, you have the entrypoint of your code (&lt;strong&gt;the “highest layers”&lt;/strong&gt;). And &lt;strong&gt;at the bottom, you have the “lowest layers” of the project (the package and components used by the entrypoints).&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can also &lt;strong&gt;explore the “purity” of your packages&lt;/strong&gt;. A package is pure if it does not depend on any other package.&lt;/p&gt;

&lt;p&gt;I’m currently refining the algorithms, but I already find the results quite promising. The next step for me is to keep making things as clear as possible. But it’s a good start!&lt;/p&gt;

&lt;h3 id=&quot;configuration-with-rulesets&quot;&gt;Configuration with Rulesets&lt;/h3&gt;

&lt;p&gt;Even if it’s not the main objective of ast-metrics, you can use it as a linter for yours projects.&lt;/p&gt;

&lt;p&gt;ast-metrics uses a ruleset-based configuration system. Start by initializing a configuration file:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ast-metrics init
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This creates a &lt;code&gt;.ast-metrics.yaml&lt;/code&gt; file. Then add rulesets based on your needs:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# List available rulesets
ast-metrics ruleset list

# Add specific rulesets
ast-metrics ruleset add architecture
ast-metrics ruleset add volume
ast-metrics ruleset add complexity
ast-metrics ruleset add golang
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Each ruleset contains related rules. For example, the &lt;code&gt;architecture&lt;/code&gt; ruleset includes:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Coupling constraints&lt;/li&gt;
  &lt;li&gt;Circular dependency detection&lt;/li&gt;
  &lt;li&gt;Maintainability thresholds&lt;/li&gt;
  &lt;li&gt;God class detection&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;understanding-rulesets&quot;&gt;Understanding Rulesets&lt;/h3&gt;

&lt;p&gt;Let’s look at what each ruleset provides:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architecture Ruleset&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;requirements:
  rules:
    architecture:
      coupling:
        forbidden:
          - from: Controller
            to: Repository
          - from: Repository
            to: Service
      max_afferent_coupling: 10
      max_efferent_coupling: 10
      min_maintainability: 70
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This enforces architectural constraints like preventing controllers from directly accessing repositories.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Volume Ruleset&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;    volume:
      max_loc: 1000
      max_logical_loc: 600
      max_loc_by_method: 30
      max_logical_loc_by_method: 20
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Controls file and method size limits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Golang Ruleset&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;    golang:
      no_package_name_in_method: true
      max_nesting: 4
      max_file_size: 1000
      max_files_per_package: 50
      slice_prealloc: true
      context_missing: true
      context_ignored: true
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Enforces Go-specific best practices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Future Rulesets&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I try to add new ruleset, based on my experience and my ideas.&lt;/p&gt;

&lt;p&gt;Feel &lt;a href=&quot;https://github.com/Halleck45/ast-metrics/blob/main/.github/CONTRIBUTING.md&quot;&gt;free to contribute&lt;/a&gt; if you have ideas ❤️!&lt;/p&gt;

&lt;h3 id=&quot;linting-your-code&quot;&gt;Linting Your Code&lt;/h3&gt;

&lt;p&gt;Once you have rules configured, you can lint your code:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ast-metrics lint
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This will check your code against all enabled rules and report violations. The output is designed to be actionable, with clear explanations of what’s wrong and how to fix it.&lt;/p&gt;

&lt;h3 id=&quot;ci-integration&quot;&gt;CI Integration&lt;/h3&gt;

&lt;p&gt;ast-metrics provides a dedicated CI command that runs linting and generates all reports:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ast-metrics ci
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This command:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Runs the linter first&lt;/li&gt;
  &lt;li&gt;Generates HTML, Markdown, JSON, OpenMetrics, and &lt;a href=&quot;https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning/sarif-support-for-code-scanning&quot;&gt;SARIF&lt;/a&gt; reports&lt;/li&gt;
  &lt;li&gt;Exits with non-zero status if violations are found&lt;/li&gt;
  &lt;li&gt;Still produces reports even if violations exist&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;github-actions-integration&quot;&gt;GitHub Actions Integration&lt;/h3&gt;

&lt;p&gt;**For GitHub Actions, there’s a &lt;a href=&quot;https://github.com/halleck45/action-ast-metrics&quot;&gt;dedicated action&lt;/a&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;name: &quot;AST Metrics&quot;
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
        - uses: halleck45/action-ast-metrics@v1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This automatically analyzes your code on every push and can be configured to fail the build on violations.&lt;/p&gt;

&lt;h2 id=&quot;exploring-architecture-with-communities&quot;&gt;Exploring Architecture with Communities&lt;/h2&gt;

&lt;p&gt;One of ast-metrics’ most powerful features is its community detection algorithm. This analyzes your code’s dependency graph to identify clusters of tightly coupled components.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2025-09-ast-metrics-purity-communities.png&quot; alt=&quot;example of purity report&quot; class=&quot;m-auto max-w-full &quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;how-community-detection-works&quot;&gt;How Community Detection Works&lt;/h3&gt;

&lt;p&gt;The algorithm uses label propagation to group related components. It starts by assigning each component to its own community, then iteratively moves components to the community that most of their neighbors belong to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This reveals the natural boundaries in your codebase - areas that are tightly coupled internally but loosely coupled to the rest of the system.&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;reading-the-communities-report&quot;&gt;Reading the Communities Report&lt;/h3&gt;

&lt;p&gt;The communities page in the HTML report shows:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Community graph&lt;/strong&gt;: The relations between your packages.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Community Details&lt;/strong&gt;: Size, coupling ratios, and key components for each community&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Architectural Suggestions&lt;/strong&gt;: Recommendations for refactoring based on the analysis&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For example, if you see a community with high outbound coupling (&amp;gt;0.7), the tool might suggest introducing a facade pattern. If a community is very large (&amp;gt;50 components) with low purity (&amp;lt;0.6), it might suggest splitting the module.&lt;/p&gt;

&lt;h3 id=&quot;using-communities-for-refactoring&quot;&gt;Using Communities for Refactoring&lt;/h3&gt;

&lt;p&gt;Communities help you answer questions like:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Which parts of my code are too tightly coupled?&lt;/li&gt;
  &lt;li&gt;Where should I introduce interfaces to reduce coupling?&lt;/li&gt;
  &lt;li&gt;What are the natural boundaries for microservices?&lt;/li&gt;
  &lt;li&gt;Which components are architectural bottlenecks?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;advanced-usage&quot;&gt;Advanced Usage&lt;/h2&gt;

&lt;h3 id=&quot;branch-comparison&quot;&gt;Branch Comparison&lt;/h3&gt;

&lt;p&gt;Compare your current branch with another branch or commit:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ast-metrics analyze --compare-with=main .
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This shows how metrics have changed between branches, helping you understand the impact of recent changes.&lt;/p&gt;

&lt;h3 id=&quot;custom-exclusions&quot;&gt;Custom Exclusions&lt;/h3&gt;

&lt;p&gt;Exclude files or directories from analysis:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ast-metrics analyze --exclude=&quot;.*_test.go&quot; --exclude=&quot;vendor/&quot; .
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&quot;multiple-report-formats&quot;&gt;Multiple Report Formats&lt;/h3&gt;

&lt;p&gt;Generate reports in different formats:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ast-metrics analyze \
  --report-html=./html-report \
  --report-json=./metrics.json \
  --report-sarif=./results.sarif \
  --report-openmetrics=./metrics.txt \
  .
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This is particularly useful for CI pipelines where different tools consume different formats. If you
don’t know what is the correct format for you, use the &lt;code&gt;--ci&lt;/code&gt; option: it will generate all the reports once.&lt;/p&gt;

&lt;h2 id=&quot;current-limitations-and-future-directions&quot;&gt;Current Limitations and Future Directions&lt;/h2&gt;

&lt;p&gt;ast-metrics is actively developed, but there are some current limitations:&lt;/p&gt;

&lt;h3 id=&quot;language-support&quot;&gt;Language Support&lt;/h3&gt;
&lt;p&gt;While supporting Go, Python, Rust, and PHP, some languages have more complete support than others. TypeScript, Java, and C++ support is planned, and I hope to support more languages in the future.&lt;/p&gt;

&lt;h3 id=&quot;rule-customization&quot;&gt;Rule Customization&lt;/h3&gt;
&lt;p&gt;While the ruleset system is flexible, some advanced customization scenarios might require code changes rather than configuration.&lt;/p&gt;

&lt;h3 id=&quot;ide-integration&quot;&gt;IDE Integration&lt;/h3&gt;
&lt;p&gt;Currently, ast-metrics is primarily a command-line tool. IDE integration would make it more accessible for day-to-day development. I think that a VsCode extension would be a good idea.&lt;/p&gt;

&lt;h3 id=&quot;performance-on-very-large-codebases&quot;&gt;Performance on Very Large Codebases&lt;/h3&gt;
&lt;p&gt;While fast, very large codebases (millions of lines) might require optimization for memory usage.
For example, analyzing 2 millions of lines of code takes a around 30 seconds on my computer. It can be improved.&lt;/p&gt;

&lt;h2 id=&quot;best-practices-for-go-static-analysis&quot;&gt;Best Practices for Go Static Analysis&lt;/h2&gt;

&lt;p&gt;Based on my experience with ast-metrics and other tools, here are some recommendations:&lt;/p&gt;

&lt;h3 id=&quot;start-simple&quot;&gt;Start Simple&lt;/h3&gt;
&lt;p&gt;Begin with basic rules and gradually add more sophisticated ones as your team gets comfortable.&lt;/p&gt;

&lt;h3 id=&quot;focus-on-architecture&quot;&gt;Focus on Architecture&lt;/h3&gt;
&lt;p&gt;Don’t just look at individual files - understand how your components interact. This is where ast-metrics really shines.&lt;/p&gt;

&lt;h3 id=&quot;use-multiple-tools&quot;&gt;Use Multiple Tools&lt;/h3&gt;
&lt;p&gt;ast-metrics complements rather than replaces other tools. Use golangci-lint for style issues and ast-metrics for architectural analysis.&lt;/p&gt;

&lt;h3 id=&quot;integrate-early&quot;&gt;Integrate Early&lt;/h3&gt;
&lt;p&gt;Set up static analysis in your CI pipeline from day one. It’s much easier than retrofitting it later.&lt;/p&gt;

&lt;h3 id=&quot;review-regularly&quot;&gt;Review Regularly&lt;/h3&gt;
&lt;p&gt;Static analysis is not a silver bullet. Regular code reviews and architectural discussions are still essential.&lt;/p&gt;

&lt;h2 id=&quot;my-final-thoughts&quot;&gt;My final thoughts&lt;/h2&gt;

&lt;p&gt;Static analysis is a crucial part of maintaining code quality, especially as your codebase grows. While there are many excellent tools available, ast-metrics fills an important gap by providing architectural insights and comprehensive metrics in a single, easy-to-use tool.&lt;/p&gt;

&lt;p&gt;The key is to start with basic analysis and gradually incorporate more sophisticated rules as your team’s needs evolve. The communities feature alone can provide valuable insights into your code’s structure and help guide refactoring efforts.&lt;/p&gt;

&lt;p&gt;Whether you’re working on a small Go service or a large polyglot system, static analysis tools like ast-metrics can help you write better, more maintainable code. The investment in setting up these tools pays dividends in reduced bugs, easier refactoring, and better architectural decisions.&lt;/p&gt;

&lt;p&gt;Give ast-metrics a try on your next project, and let me know what you think. The project is open source and welcomes contributions - after all, the best tools are built by the community that uses them.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;ast-metrics is available on &lt;a href=&quot;https://github.com/Halleck45/ast-metrics&quot;&gt;GitHub&lt;/a&gt; and &lt;a href=&quot;https://halleck45.github.io/ast-metrics/&quot;&gt;documented&lt;/a&gt;. For questions or contributions, feel free to open an issue or start a discussion.&lt;/em&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Speech Vectorization Explained: Building a Local AI for Pronunciation Detection</title>
   <link href="https://blog.lepine.pro/en/ai-wav2vec-pronunciation-vectorization/"/>
   <updated>2025-09-11T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/en/ai-wav2vec-pronunciation-vectorization</id>
   <content type="html">&lt;p&gt;Reading or writing has never been much of a problem for me, but as soon as it comes to &lt;strong&gt;speaking&lt;/strong&gt;, I realize my pronunciation is not always clear.&lt;/p&gt;

&lt;p&gt;ChatGPT is great for text, but current generative models are &lt;strong&gt;not designed to evaluate pronunciation&lt;/strong&gt;.&lt;br /&gt;
So I asked myself: &lt;strong&gt;what if I built my own pronunciation coach?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A tool that:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;listens to me,&lt;/li&gt;
  &lt;li&gt;compares what I say to a reference,&lt;/li&gt;
  &lt;li&gt;and gives me clear and visual feedback.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s the project I’ll detail here, while also breaking down the AI building blocks: &lt;strong&gt;embeddings, distances, DTW, phonemes, visemes&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;why-is-comparing-two-sounds-so-hard&quot;&gt;Why is comparing two sounds so hard?&lt;/h2&gt;

&lt;p&gt;A spoken word is not a sequence of letters, but a &lt;strong&gt;sound wave that varies over time&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;the air vibrates,&lt;/li&gt;
  &lt;li&gt;the voice rises and falls,&lt;/li&gt;
  &lt;li&gt;some parts are long, others very short.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even two people pronouncing &lt;em&gt;exactly&lt;/em&gt; the same word will never have identical curves.&lt;/p&gt;

&lt;p&gt;The central question becomes: &lt;strong&gt;how do you compare two audios that do not align perfectly?&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;the-chosen-approach&quot;&gt;The chosen approach&lt;/h2&gt;

&lt;p&gt;Here’s an overview of the architecture I set up:&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
    &lt;img src=&quot;/images/2025-08-audio-embedding.png&quot; alt=&quot;Embedding architecture&quot; width=&quot;600px&quot; /&gt;
&lt;/p&gt;

&lt;p&gt;Don’t worry(we’ll break down all these concepts step by step.&lt;/p&gt;

&lt;h2 id=&quot;turning-sound-into-numbers-embeddings&quot;&gt;Turning sound into numbers: embeddings&lt;/h2&gt;

&lt;p&gt;A computer does not understand sound. It only manipulates &lt;strong&gt;vectors of numbers&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A &lt;strong&gt;vector&lt;/strong&gt; = a list of numbers, e.g. &lt;code&gt;[0.2, -0.7, 1.1]&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;An &lt;strong&gt;audio embedding&lt;/strong&gt; = a condensed representation of a short audio segment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With &lt;strong&gt;Wav2Vec2&lt;/strong&gt;, every few milliseconds of audio are encoded into &lt;strong&gt;768 numbers&lt;/strong&gt; describing timbre, energy, articulation, etc.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;processor = Wav2Vec2Processor.from_pretrained(&quot;facebook/wav2vec2-large-960h&quot;)

def extract_embeddings(audio_waveform, sampling_rate=16000):
    inputs = processor(audio_waveform, sampling_rate=sampling_rate,
                       return_tensors=&quot;pt&quot;, padding=True)
    input_values = inputs.input_values.squeeze(0)
    with torch.no_grad():
        features = model(input_values).last_hidden_state
    return features.squeeze(0).numpy()
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In short:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;similar sounds → close vectors&lt;/strong&gt;,&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;different sounds → distant vectors&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;measuring-similarity-distances&quot;&gt;Measuring similarity: distances&lt;/h2&gt;

&lt;p&gt;Once two vectors are extracted, you need to measure their &lt;strong&gt;proximity&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The basic tool: &lt;strong&gt;Euclidean distance&lt;/strong&gt;.&lt;br /&gt;
Simple example between &lt;code&gt;[1,2]&lt;/code&gt; and &lt;code&gt;[4,6]&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;√((4-1)² + (6-2)²) = 5
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;With audio embeddings, it’s the same principle, but in a 768-dimensional space.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from fastdtw import fastdtw
from scipy.spatial.distance import euclidean

def compare_pronunciation(expected, actual):
    expected_seq = get_phoneme_embeddings(expected)
    actual_seq = get_phoneme_embeddings(actual)
    distance, _ = fastdtw(expected_seq, actual_seq, dist=euclidean)
    return distance
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;dtw-when-speech-is-not-at-the-same-speed&quot;&gt;DTW: when speech is not at the same speed&lt;/h2&gt;

&lt;p&gt;Problem: a word can last &lt;strong&gt;0.5 seconds&lt;/strong&gt; for me and &lt;strong&gt;0.8 seconds&lt;/strong&gt; in the reference.&lt;br /&gt;
If you compare naively, it fails.&lt;/p&gt;

&lt;p&gt;The solution: &lt;strong&gt;Dynamic Time Warping (DTW)&lt;/strong&gt;.&lt;br /&gt;
This algorithm aligns two sequences of different speeds by “stretching” or “compressing” time to match similar parts.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import numpy as np

def align_sequences_dtw(seq1, seq2):
    distance, path = fastdtw(seq1, seq2, dist=euclidean)
    aligned1, aligned2 = [], []
    for i, j in path:
        aligned1.append(seq1[i][0])
        aligned2.append(seq2[j][0])
    return np.array(aligned1), np.array(aligned2)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Result: a robust comparison, even when pacing differs.&lt;/p&gt;

&lt;h2 id=&quot;phonemes-and-visemes-hear-and-see&quot;&gt;Phonemes and visemes: hear AND see&lt;/h2&gt;

&lt;p&gt;Some sounds are hard to distinguish by ear.&lt;br /&gt;
Example: &lt;em&gt;“think”&lt;/em&gt; vs &lt;em&gt;“sink”&lt;/em&gt; (subtle difference between /θ/ and /s/).&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A &lt;strong&gt;phoneme&lt;/strong&gt; = the smallest distinctive sound (e.g. /p/, /a/, /t/).&lt;/li&gt;
  &lt;li&gt;A &lt;strong&gt;viseme&lt;/strong&gt; = the visual mouth shape that produces that sound.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In my prototype, when a word is mispronounced, I can click on it and see a &lt;strong&gt;mouth animation&lt;/strong&gt; showing the correct articulation.&lt;/p&gt;

&lt;p&gt;👉 Learning becomes more concrete: I both &lt;strong&gt;hear and see&lt;/strong&gt; what to correct.&lt;br /&gt;
(&lt;a href=&quot;https://learn.microsoft.com/fr-fr/azure/ai-services/speech-service/how-to-speech-synthesis-viseme?tabs=visemeid&amp;amp;pivots=programming-language-csharp&quot;&gt;Microsoft documentation on visemes&lt;/a&gt;)&lt;/p&gt;

&lt;h2 id=&quot;the-complete-pipeline&quot;&gt;The complete pipeline&lt;/h2&gt;

&lt;p align=&quot;center&quot;&gt;
    &lt;img src=&quot;/images/2025-08-audio-analysis.png&quot; alt=&quot;pronunciation pipeline&quot; width=&quot;600px&quot; /&gt;
&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;I write a text: &lt;em&gt;“Hello, how are you?”&lt;/em&gt;.&lt;/li&gt;
  &lt;li&gt;The system generates a &lt;strong&gt;reference voice&lt;/strong&gt; (e.g. gTTS).&lt;/li&gt;
  &lt;li&gt;I record my voice.&lt;/li&gt;
  &lt;li&gt;Extract &lt;strong&gt;Wav2Vec2 embeddings&lt;/strong&gt; for both audios.&lt;/li&gt;
  &lt;li&gt;Align them with &lt;strong&gt;DTW&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;Compare &lt;strong&gt;phoneme by phoneme&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;Feedback returned:
    &lt;ul&gt;
      &lt;li&gt;a &lt;strong&gt;global score&lt;/strong&gt; (out of 100),&lt;/li&gt;
      &lt;li&gt;a list of mispronounced words,&lt;/li&gt;
      &lt;li&gt;clear feedback: “❌ You should pronounce better: &lt;em&gt;Hello, you&lt;/em&gt;”,&lt;/li&gt;
      &lt;li&gt;and the corresponding visemes.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;limitations-of-the-approach&quot;&gt;Limitations of the approach&lt;/h2&gt;

&lt;p&gt;Let’s be honest:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Wav2Vec2 was not trained to score pronunciation&lt;/strong&gt; → approximation.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Synthetic voices are imperfect&lt;/strong&gt; → sometimes artificial accent.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Simplified visemes&lt;/strong&gt; → animations too discrete compared to a real mouth.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Subjectivity remains&lt;/strong&gt; → a native listener is still the ultimate reference.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;what-it-still-brings&quot;&gt;What it still brings&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;I can &lt;strong&gt;listen to myself objectively&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;I can &lt;strong&gt;spot my mistakes&lt;/strong&gt; without a teacher.&lt;/li&gt;
  &lt;li&gt;I get &lt;strong&gt;visual feedback&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;And most importantly: I stay &lt;strong&gt;motivated&lt;/strong&gt; to practice.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;ways-forward&quot;&gt;Ways forward&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Integrate specialized &lt;strong&gt;pronunciation scoring&lt;/strong&gt; models (commercial APIs).&lt;/li&gt;
  &lt;li&gt;Improve visemes with &lt;strong&gt;smooth 3D animation&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;Add &lt;strong&gt;prosody&lt;/strong&gt; (intonation, stress, rhythm).&lt;/li&gt;
  &lt;li&gt;Extend to other languages.&lt;/li&gt;
  &lt;li&gt;Evaluate longer sentences (reading, dialogue).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;I haven’t reinvented Duolingo. But I have built a &lt;strong&gt;home-made coach&lt;/strong&gt; that helps me improve my speaking.&lt;/p&gt;

&lt;p&gt;The strength comes from the combination of:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;AI&lt;/strong&gt; (Wav2Vec2) to turn voice into vectors,&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;classic algorithms&lt;/strong&gt; (DTW) to compare,&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;pedagogical visuals&lt;/strong&gt; (visemes) to correct.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A mix of machine learning, math, and pedagogy, all serving a very concrete goal: &lt;strong&gt;speaking English better&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;👉 Source code is available on &lt;a href=&quot;https://github.com/Halleck45/OpenPronounce&quot;&gt;GitHub&lt;/a&gt;.&lt;br /&gt;
Feedback or contributions are welcome.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Speech Embeddings et Pronunciation Detection : construire un pipeline IA local avec Wav2Vec2</title>
   <link href="https://blog.lepine.pro/ai-wav2vec-prononciation"/>
   <updated>2025-09-11T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/ai-wav2vec-prononciation</id>
   <content type="html">&lt;p&gt;Lire ou écrire ne me pose pas trop de problèmes, mais dès qu’il s’agit de &lt;strong&gt;parler&lt;/strong&gt;, je me rends compte que ma prononciation n’est pas toujours claire.&lt;/p&gt;

&lt;p&gt;ChatGPT est super pour le texte, mais les modèles génératifs actuels ne savent &lt;strong&gt;pas évaluer la prononciation&lt;/strong&gt;.&lt;br /&gt;
Alors je me suis demandé : &lt;strong&gt;et si je construisais moi-même un petit coach ?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Un outil qui :&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;m’écoute,&lt;/li&gt;
  &lt;li&gt;compare ce que je dis à une référence,&lt;/li&gt;
  &lt;li&gt;et me renvoie un retour clair et visuel.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;C’est ce que je vais détailler ici, tout en expliquant simplement les briques IA derrière : &lt;strong&gt;embeddings, distances, DTW, phonèmes, visèmes&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;pourquoi-comparer-deux-sons-est-si-difficile-&quot;&gt;Pourquoi comparer deux sons est si difficile ?&lt;/h2&gt;

&lt;p&gt;Un mot parlé n’est pas une suite de lettres, mais une &lt;strong&gt;onde sonore qui varie dans le temps&lt;/strong&gt; :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;l’air vibre,&lt;/li&gt;
  &lt;li&gt;la voix monte et descend,&lt;/li&gt;
  &lt;li&gt;certaines parties sont longues, d’autres très courtes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Même deux personnes qui prononcent &lt;em&gt;exactement&lt;/em&gt; le même mot n’auront jamais deux courbes identiques.&lt;br /&gt;
La question centrale devient donc : &lt;strong&gt;comment comparer deux audios qui ne s’alignent pas parfaitement ?&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;lapproche-choisie&quot;&gt;L’approche choisie&lt;/h2&gt;

&lt;p&gt;Voici un aperçu de l’architecture que j’ai mise en place :&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
    &lt;img src=&quot;/images/2025-08-audio-embedding.png&quot; alt=&quot;Embedding architecture&quot; width=&quot;600px&quot; /&gt;
&lt;/p&gt;

&lt;p&gt;N’ayez pas peur, on va expliquer tous ces concepts pas à pas.&lt;/p&gt;

&lt;h2 id=&quot;transformer-le-son-en-nombres--les-embeddings&quot;&gt;Transformer le son en nombres : les embeddings&lt;/h2&gt;

&lt;p&gt;Un ordinateur ne comprend pas les sons. Il ne manipule que des &lt;strong&gt;vecteurs de nombres&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Un &lt;strong&gt;vecteur&lt;/strong&gt; = une liste de nombres, par ex. &lt;code&gt;[0.2, -0.7, 1.1]&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Un &lt;strong&gt;embedding audio&lt;/strong&gt; = une représentation condensée d’un petit morceau de son.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Avec &lt;strong&gt;Wav2Vec2&lt;/strong&gt;, chaque tranche d’audio de quelques millisecondes est encodée en &lt;strong&gt;768 nombres&lt;/strong&gt; décrivant le timbre, l’énergie, l’articulation, etc.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;processor = Wav2Vec2Processor.from_pretrained(&quot;facebook/wav2vec2-large-960h&quot;)

def extract_embeddings(audio_waveform, sampling_rate=16000):
    inputs = processor(audio_waveform, sampling_rate=sampling_rate,
                       return_tensors=&quot;pt&quot;, padding=True)
    input_values = inputs.input_values.squeeze(0)
    with torch.no_grad():
        features = model(input_values).last_hidden_state
    return features.squeeze(0).numpy()
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;En résumé :&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;sons proches → vecteurs proches&lt;/strong&gt;,&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;sons différents → vecteurs éloignés&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;mesurer-la-ressemblance--les-distances&quot;&gt;Mesurer la ressemblance : les distances&lt;/h2&gt;

&lt;p&gt;Une fois deux vecteurs extraits, il faut mesurer leur &lt;strong&gt;proximité&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;L’outil de base : la &lt;strong&gt;distance euclidienne&lt;/strong&gt;.&lt;br /&gt;
Exemple simple entre &lt;code&gt;[1,2]&lt;/code&gt; et &lt;code&gt;[4,6]&lt;/code&gt; :&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;√((4-1)² + (6-2)²) = 5
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Avec les embeddings audio, c’est le même principe mais dans un espace à 768 dimensions.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from fastdtw import fastdtw
from scipy.spatial.distance import euclidean

def compare_pronunciation(expected, actual):
    expected_seq = get_phoneme_embeddings(expected)
    actual_seq = get_phoneme_embeddings(actual)
    distance, _ = fastdtw(expected_seq, actual_seq, dist=euclidean)
    return distance
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;dtw--quand-on-ne-parle-pas-à-la-même-vitesse&quot;&gt;DTW : quand on ne parle pas à la même vitesse&lt;/h2&gt;

&lt;p&gt;Problème : un mot peut durer &lt;strong&gt;0,5 seconde&lt;/strong&gt; chez moi et &lt;strong&gt;0,8 seconde&lt;/strong&gt; dans la référence.&lt;br /&gt;
Si on compare naïvement, ça échoue.&lt;/p&gt;

&lt;p&gt;La solution : &lt;strong&gt;Dynamic Time Warping (DTW)&lt;/strong&gt;.&lt;br /&gt;
Cet algorithme aligne deux séquences de vitesses différentes en “étirant” ou “compressant” le temps pour faire correspondre les parties similaires.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import numpy as np

def align_sequences_dtw(seq1, seq2):
    distance, path = fastdtw(seq1, seq2, dist=euclidean)
    aligned1, aligned2 = [], []
    for i, j in path:
        aligned1.append(seq1[i][0])
        aligned2.append(seq2[j][0])
    return np.array(aligned1), np.array(aligned2)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Résultat : une comparaison robuste, même si le rythme diffère.&lt;/p&gt;

&lt;h2 id=&quot;phonèmes-et-visèmes--entendre-et-voir&quot;&gt;Phonèmes et visèmes : entendre ET voir&lt;/h2&gt;

&lt;p&gt;Certains sons sont difficiles à distinguer à l’oreille.&lt;br /&gt;
Exemple : &lt;em&gt;“think”&lt;/em&gt; vs &lt;em&gt;“sink”&lt;/em&gt; (différence subtile entre /θ/ et /s/).&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Un &lt;strong&gt;phonème&lt;/strong&gt; = le plus petit son distinctif (ex. /p/, /a/, /t/).&lt;/li&gt;
  &lt;li&gt;Un &lt;strong&gt;visème&lt;/strong&gt; = l’image de la bouche qui produit ce son.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Dans mon prototype, quand un mot est mal prononcé, je peux cliquer dessus et voir une &lt;strong&gt;animation de bouche&lt;/strong&gt; qui montre la bonne articulation.&lt;/p&gt;

&lt;p&gt;👉 Apprentissage plus concret : j’entends &lt;strong&gt;et je vois&lt;/strong&gt; ce qu’il faut corriger.&lt;br /&gt;
(&lt;a href=&quot;https://learn.microsoft.com/fr-fr/azure/ai-services/speech-service/how-to-speech-synthesis-viseme?tabs=visemeid&amp;amp;pivots=programming-language-csharp&quot;&gt;Documentation Microsoft sur les visèmes&lt;/a&gt;)&lt;/p&gt;

&lt;h2 id=&quot;le-pipeline-complet&quot;&gt;Le pipeline complet&lt;/h2&gt;

&lt;p align=&quot;center&quot;&gt;
    &lt;img src=&quot;/images/2025-08-audio-analysis.png&quot; alt=&quot;pronunciation pipeline&quot; width=&quot;600px&quot; /&gt;
&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;J’écris un texte : &lt;em&gt;“Hello, how are you?”&lt;/em&gt;.&lt;/li&gt;
  &lt;li&gt;Le système génère une &lt;strong&gt;voix de référence&lt;/strong&gt; (ex. gTTS).&lt;/li&gt;
  &lt;li&gt;J’enregistre ma voix.&lt;/li&gt;
  &lt;li&gt;Extraction des &lt;strong&gt;embeddings Wav2Vec2&lt;/strong&gt; pour les deux audios.&lt;/li&gt;
  &lt;li&gt;Alignement avec &lt;strong&gt;DTW&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;Comparaison &lt;strong&gt;phonème par phonème&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;Feedback renvoyé :
    &lt;ul&gt;
      &lt;li&gt;un &lt;strong&gt;score global&lt;/strong&gt; (sur 100),&lt;/li&gt;
      &lt;li&gt;une liste de mots mal prononcés,&lt;/li&gt;
      &lt;li&gt;un feedback clair : “❌ Tu dois mieux prononcer : &lt;em&gt;Hello, you&lt;/em&gt;”,&lt;/li&gt;
      &lt;li&gt;et les visèmes correspondants.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;limites-de-lapproche&quot;&gt;Limites de l’approche&lt;/h2&gt;

&lt;p&gt;Soyons honnêtes :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Wav2Vec2 n’est pas entraîné pour noter la prononciation&lt;/strong&gt; → approximation.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Voix de synthèse imparfaite&lt;/strong&gt; → accent parfois artificiel.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Visèmes simplifiés&lt;/strong&gt; → animations trop discrètes comparées à une bouche réelle.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Subjectivité persistante&lt;/strong&gt; → la compréhension d’un natif reste la référence ultime.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;ce-que-ça-apporte-malgré-tout&quot;&gt;Ce que ça apporte malgré tout&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Je peux &lt;strong&gt;m’écouter objectivement&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;J’identifie &lt;strong&gt;mes erreurs&lt;/strong&gt; sans prof.&lt;/li&gt;
  &lt;li&gt;Je dispose d’un &lt;strong&gt;feedback visuel&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;Et surtout : je garde la &lt;strong&gt;motivation&lt;/strong&gt; à pratiquer.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;pistes-pour-aller-plus-loin&quot;&gt;Pistes pour aller plus loin&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Intégrer des modèles spécialisés de &lt;strong&gt;pronunciation scoring&lt;/strong&gt; (API commerciales).&lt;/li&gt;
  &lt;li&gt;Améliorer les visèmes avec de la &lt;strong&gt;3D fluide&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;Ajouter la &lt;strong&gt;prosodie&lt;/strong&gt; (intonation, accentuation, rythme).&lt;/li&gt;
  &lt;li&gt;Étendre à d’autres langues.&lt;/li&gt;
  &lt;li&gt;Évaluer des phrases longues (lecture, dialogue).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Je n’ai pas réinventé Duolingo. Mais j’ai construit un &lt;strong&gt;coach maison&lt;/strong&gt; qui m’aide à progresser à l’oral.&lt;/p&gt;

&lt;p&gt;La force vient de la combinaison :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;IA&lt;/strong&gt; (Wav2Vec2) pour transformer la voix en vecteurs,&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;algorithmes classiques&lt;/strong&gt; (DTW) pour comparer,&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;visuels pédagogiques&lt;/strong&gt; (visèmes) pour corriger.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Un mélange de machine learning, de maths et de pédagogie, au service d’un objectif très concret : &lt;strong&gt;mieux parler anglais&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;👉 Le code source est disponible sur &lt;a href=&quot;https://github.com/Halleck45/OpenPronounce&quot;&gt;GitHub&lt;/a&gt;.&lt;br /&gt;
Vos retours ou contributions sont bienvenus.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Parsing PHP code, without depending on PHP</title>
   <link href="https://blog.lepine.pro/en/parsing-php-code-ast-go/"/>
   <updated>2025-08-26T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/en/parsing-php-code-ast-go</id>
   <content type="html">&lt;p&gt;For the past few months, I’ve been working on &lt;a href=&quot;https://github.com/Halleck45/ast-metrics&quot;&gt;&lt;strong&gt;AstMetrics&lt;/strong&gt;&lt;/a&gt;, a tool for
analyzing source code of software projects at scale, regardless of the programming language.&lt;/p&gt;

&lt;p&gt;The idea is simple: instead of limiting analysis to line counts or superficial static rules, AstMetrics relies directly
on the &lt;a href=&quot;https://en.wikipedia.org/wiki/Abstract_syntax_tree&quot;&gt;&lt;strong&gt;AST&lt;/strong&gt;&lt;/a&gt; (Abstract Syntax Tree), i.e. the structured
representation of code as understood by the compiler.&lt;/p&gt;

&lt;p&gt;With an AST, you can measure much more than surface-level metrics: complexity, nesting depth, number of branches,
dependencies between logical units, and so on. You can also compare metrics between project versions and detect trends.&lt;/p&gt;

&lt;p&gt;From the beginning, AstMetrics was designed as &lt;strong&gt;language-agnostic&lt;/strong&gt;. Nothing prevents analyzing PHP, JavaScript,
Python, or Go: as long as I can obtain an AST in a stable format (JSON, for instance), I can build metrics on top of it.
This is one of the reasons I started AstMetrics compared to
&lt;a href=&quot;https://github.com/phpmetrics/PhpMetrics&quot;&gt;PhpMetrics&lt;/a&gt;, which is PHP-only.&lt;/p&gt;

&lt;p&gt;It’s in this context that &lt;a href=&quot;https://github.com/Halleck45/go-php-parser&quot;&gt;&lt;strong&gt;Go-PHP-Parser&lt;/strong&gt;&lt;/a&gt; was born.&lt;/p&gt;

&lt;h2 id=&quot;the-problem-parsing-php&quot;&gt;The problem: parsing PHP&lt;/h2&gt;

&lt;p&gt;To extract the AST of a language, there are two main approaches:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Write your own parser&lt;/strong&gt;: starting from the grammar of the language, rebuild a lexical and syntax analyzer.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Reuse the official parser&lt;/strong&gt;: embed it or call it directly to get the AST it produces.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At first, I explored the first option, which seemed more interesting.&lt;/p&gt;

&lt;h2 id=&quot;attempt-1-lex-and-yacc&quot;&gt;Attempt 1: Lex and Yacc&lt;/h2&gt;

&lt;h3 id=&quot;what-are-lexyacc&quot;&gt;What are Lex/Yacc?&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Lex_(software)&quot;&gt;&lt;strong&gt;Lex&lt;/strong&gt;&lt;/a&gt; is a lexical analyzer generator. You describe the &lt;em&gt;tokens&lt;/em&gt; of
a language (keywords, operators, strings, etc.) using regular expressions. Lex generates C code that can split a
source file into a stream of tokens.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Yacc&quot;&gt;&lt;strong&gt;Yacc&lt;/strong&gt;&lt;/a&gt; (Yet Another Compiler Compiler) is a parser generator. You describe the
grammar of a language in terms of production rules (e.g., an &lt;em&gt;expression&lt;/em&gt; is either a number or the addition of two
expressions). Yacc generates a parser that builds a syntax tree from the tokens produced by Lex.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Lex+Yacc combo is classic: it was used to build parsers for many languages in the 80s–90s. There are modern
equivalents in Go, like &lt;code&gt;goyacc&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;These are fundamental tools, used as compilation engines for many programming languages.&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;trying-to-parse-php-with-lexyacc&quot;&gt;Trying to parse PHP with Lex/Yacc&lt;/h3&gt;

&lt;p&gt;So I started writing a PHP grammar for Yacc in Go. Very quickly, I hit the limits:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The PHP grammar is huge, full of edge cases and historical quirks.&lt;/li&gt;
  &lt;li&gt;Each version of the language adds new constructs (e.g., &lt;em&gt;match expressions&lt;/em&gt; in PHP 8).&lt;/li&gt;
  &lt;li&gt;Keeping this grammar up to date would have required enormous and constant effort.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I tried to automate part of the process with AI to generate the rules. It turned out too complex for the AI. Maybe in a
few months it’ll be worth trying again… I spent hours on it, but for now I’m dropping that path.&lt;/p&gt;

&lt;p&gt;By the way, a project like &lt;a href=&quot;https://github.com/z7zmey/php-parser&quot;&gt;z7zmey/php-parser&lt;/a&gt; followed that approach. It’s a
native PHP parser in Go based on a hand-written grammar. But it’s not fully up to date (PHP 8.2), and you can see why:
maintaining a manual PHP grammar in another language is a never-ending job.&lt;/p&gt;

&lt;p&gt;Result: I learned a lot, but abandoned the idea.&lt;/p&gt;

&lt;p&gt;If you’re interested in the subject, &lt;strong&gt;I recommend reading
&lt;a href=&quot;https://www.oreilly.com/library/view/lex-yacc/9781565920002/ch01.html&quot;&gt;Lex &amp;amp; Yacc&lt;/a&gt;&lt;/strong&gt;, by John Levine, Doug Brown, Tony
Mason. It’s dense but really useful, especially if you like regular expressions!&lt;/p&gt;

&lt;h2 id=&quot;attempt-2-reusing-the-official-parser&quot;&gt;Attempt 2: reusing the official parser&lt;/h2&gt;

&lt;p&gt;The second option is to avoid reinventing the wheel.&lt;/p&gt;

&lt;p&gt;PHP already has its official parser, maintained by the language team. There’s even an extension,
&lt;a href=&quot;https://github.com/nikic/php-ast&quot;&gt;ext-ast&lt;/a&gt;, which exposes PHP’s AST internally in a stable and versioned form (thanks
to &lt;a href=&quot;https://github.com/nikic&quot;&gt;Nikita Popov&lt;/a&gt; 🙏).&lt;/p&gt;

&lt;p&gt;The problem: to use it, you must have &lt;strong&gt;PHP installed&lt;/strong&gt; in the correct version, and you also need the &lt;code&gt;ext-ast&lt;/code&gt;
extension enabled.&lt;/p&gt;

&lt;p&gt;This works locally, but not for a generic tool like AstMetrics, which must run on any machine without dependencies.&lt;/p&gt;

&lt;p&gt;I tried building a standalone PHP to parse code. It worked, but the performance was awful, and CPU usage was huge.&lt;/p&gt;

&lt;p&gt;The most logical solution (not necessarily the simplest, I admit): switch to &lt;code&gt;C&lt;/code&gt; and use the &lt;code&gt;SAPI Embed&lt;/code&gt; to call the
official parser.&lt;/p&gt;

&lt;h2 id=&quot;go-php-parser-embedding-php-in-go&quot;&gt;Go-PHP-Parser: embedding PHP in Go&lt;/h2&gt;

&lt;p&gt;The chosen solution was to &lt;strong&gt;embed the PHP engine directly as a C library&lt;/strong&gt; thanks to the &lt;strong&gt;SAPI Embed&lt;/strong&gt;.&lt;/p&gt;

&lt;h3 id=&quot;sapi-embed&quot;&gt;SAPI Embed&lt;/h3&gt;

&lt;p&gt;PHP offers several SAPIs (Server APIs). The most well-known is &lt;strong&gt;&lt;code&gt;SAPI FPM&lt;/code&gt;&lt;/strong&gt; for running PHP behind a web server.&lt;br /&gt;
The Embed SAPI is an interface that allows you to use the PHP engine &lt;strong&gt;as a library&lt;/strong&gt; inside another &lt;code&gt;C&lt;/code&gt; program.&lt;/p&gt;

&lt;p&gt;You can initialize the engine, feed it some code, and get the result back.&lt;/p&gt;

&lt;p&gt;This SAPI is &lt;a href=&quot;https://github.com/php/php-src/tree/master/sapi/embed&quot;&gt;available in the PHP GitHub repository&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;ext-ast&quot;&gt;ext-ast&lt;/h3&gt;

&lt;p&gt;By enabling &lt;code&gt;ext-ast&lt;/code&gt;, I can ask PHP not for the execution result, but directly for the &lt;code&gt;AST&lt;/code&gt; of the code.&lt;/p&gt;

&lt;p&gt;This AST is identical to the one PHP uses internally, so it is always up to date with the language.&lt;/p&gt;

&lt;p&gt;An AST is simply a tree representation of your source code. For example, the code:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;while b ≠ 0:
if a &amp;gt; b:
a := a - b
else:
b := b - a
return a
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;is represented by this tree (&lt;em&gt;Wikipedia illustration)&lt;/em&gt;:&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://upload.wikimedia.org/wikipedia/commons/thumb/c/c7/Abstract_syntax_tree_for_Euclidean_algorithm.svg/500px-Abstract_syntax_tree_for_Euclidean_algorithm.svg.png&quot; alt=&quot;AST&quot; width=&quot;600px&quot; /&gt;
&lt;/p&gt;

&lt;h3 id=&quot;c--go-bridge&quot;&gt;C ↔ Go Bridge&lt;/h3&gt;

&lt;p&gt;I wrote a small bridge in &lt;code&gt;C&lt;/code&gt; that:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Initializes the embedded PHP engine.&lt;/li&gt;
  &lt;li&gt;Passes the PHP source code to &lt;code&gt;ext-ast&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Serializes the AST to JSON.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This bridge is exposed to Go via &lt;strong&gt;cgo&lt;/strong&gt;. In practice, from Go I can simply call:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;ast, err := parser.Parse(&quot;&amp;lt;?php echo 1 + 2;&quot;)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;and I get a JSON structure describing the AST.&lt;/p&gt;

&lt;h3 id=&quot;simplified-distribution&quot;&gt;Simplified distribution&lt;/h3&gt;

&lt;p&gt;To avoid forcing the user to compile PHP embed themselves, the project relies on
&lt;a href=&quot;https://github.com/crazywhalecc/static-php-cli&quot;&gt;static-php-cli&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Precompiled binaries of PHP + ext-ast are provided.&lt;/li&gt;
  &lt;li&gt;On first use, the binary for the current platform is automatically downloaded.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Result: the Go user has nothing to install. Just run:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;go get github.com/Halleck45/go-php-parser
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In the future, I might drop &lt;code&gt;static-php-cli&lt;/code&gt; if I see the project isn’t maintained anymore. It’s possible, even though
&lt;code&gt;static-php-cli&lt;/code&gt; saves a lot of time when compiling PHP.&lt;/p&gt;

&lt;h2 id=&quot;architecture&quot;&gt;Architecture&lt;/h2&gt;

&lt;p&gt;Here’s an overview of the overall architecture of Go-PHP-Parser:&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
    &lt;img src=&quot;/images/2025-08-08-archi-go-php-parser.png&quot; alt=&quot;Architecture diagram&quot; width=&quot;600px&quot; /&gt;
&lt;/p&gt;

&lt;h2 id=&quot;why-go&quot;&gt;Why Go?&lt;/h2&gt;

&lt;p&gt;Two main reasons:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Performance&lt;/strong&gt;: Go compiles to native binaries without a heavy runtime. It’s fast for handling &lt;code&gt;C&lt;/code&gt; calls via &lt;code&gt;cgo&lt;/code&gt;
and efficient at processing large volumes of files in parallel thanks to goroutines. Perfect for scanning entire
repositories.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Interoperability&lt;/strong&gt;: Go is a good language for writing easy-to-use libraries. By providing a Go API, I make
integration into AstMetrics trivial.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;approach-comparison&quot;&gt;Approach comparison&lt;/h2&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Approach&lt;/th&gt;
      &lt;th&gt;Advantage&lt;/th&gt;
      &lt;th&gt;Drawback&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Custom parser (Lex/Yacc)&lt;/td&gt;
      &lt;td&gt;Independent, full control&lt;/td&gt;
      &lt;td&gt;Huge maintenance, slow to keep up to date&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Project like z7zmey&lt;/td&gt;
      &lt;td&gt;Native Go, fast&lt;/td&gt;
      &lt;td&gt;Not up to date, costly to maintain&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Calling a PHP binary&lt;/td&gt;
      &lt;td&gt;Simple to implement&lt;/td&gt;
      &lt;td&gt;External process, I/O overhead, requires installation&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Embed + ext-ast (current)&lt;/td&gt;
      &lt;td&gt;Fast, always up to date, reduced maintenance&lt;/td&gt;
      &lt;td&gt;Requires a C bridge and embedded binaries. More complex&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;performance&quot;&gt;Performance&lt;/h2&gt;

&lt;p&gt;Preliminary benchmarks show that:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Parsing a PHP file is in the same ballpark as using &lt;code&gt;php-ast&lt;/code&gt; natively (4,000 to 8,000 files per second on my 16-core
32 GB RAM PC).&lt;/li&gt;
  &lt;li&gt;Embedding avoids the cost of launching a &lt;code&gt;php&lt;/code&gt; process for each file.&lt;/li&gt;
  &lt;li&gt;For large-scale scans, the real bottleneck is disk I/O, not parsing itself.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;potential-and-use-cases&quot;&gt;Potential and use cases&lt;/h2&gt;

&lt;p&gt;Go-PHP-Parser was born to serve &lt;strong&gt;AstMetrics&lt;/strong&gt;, but it can be useful for much more:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Running PHP code from Go (I think there’s real potential there).&lt;/li&gt;
  &lt;li&gt;Automated refactoring tools.&lt;/li&gt;
  &lt;li&gt;Static analysis integrated into CI/CD.&lt;/li&gt;
  &lt;li&gt;Code indexing for search engines or big-code tools.&lt;/li&gt;
  &lt;li&gt;Assisting migration between PHP versions.&lt;/li&gt;
  &lt;li&gt;Documentation generation from code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Basically, anything that needs fast and reliable access to the PHP AST.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Go-PHP-Parser&lt;/strong&gt; is not a parser written from scratch, and that’s intentional.&lt;/p&gt;

&lt;p&gt;Instead of maintaining a parallel PHP grammar, I chose to rely on the official parser of the language, via the Embed SAPI
and &lt;code&gt;ext-ast&lt;/code&gt;. This ensures staying up to date while benefiting from native performance and the simplicity of Go.&lt;/p&gt;

&lt;p&gt;Next steps for me: using it in AstMetrics! It’s a lot of work, but little by little it’s moving forward, among my many
other projects…&lt;/p&gt;

&lt;p&gt;I hope the project will be useful to others! If you’d like to test or contribute, the project is available here:
&lt;a href=&quot;https://github.com/Halleck45/go-php-parser&quot;&gt;https://github.com/Halleck45/go-php-parser&lt;/a&gt;&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Parser du code PHP, sans dépendre de PHP</title>
   <link href="https://blog.lepine.pro/go-parser-php-sapi-embed-ast"/>
   <updated>2025-08-25T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/go-parser-php-sapi-embed-ast</id>
   <content type="html">&lt;p&gt;Depuis quelques mois, je travaille sur &lt;a href=&quot;https://github.com/Halleck45/ast-metrics&quot;&gt;&lt;strong&gt;AstMetrics&lt;/strong&gt;&lt;/a&gt;, un outil pour
analyser le code source de projets logiciels à grande échelle, quel que soit le langage de programmation.&lt;/p&gt;

&lt;p&gt;L’idée est simple : au lieu de se limiter à du comptage de lignes ou à des règles statiques superficielles, AstMetrics
s’appuie directement sur l’&lt;a href=&quot;https://en.wikipedia.org/wiki/Abstract_syntax_tree&quot;&gt;&lt;strong&gt;AST&lt;/strong&gt;&lt;/a&gt; (Abstract Syntax Tree),
c’est-à-dire la représentation structurée du code telle que le compilateur l’entend.&lt;/p&gt;

&lt;p&gt;Avec un AST, on peut mesurer bien plus que des métriques superficielles : complexité, profondeur de nesting, nombre de
branches, dépendances entre unités logiques, etc. On peut également comparer des métriques entre versions d’un projet et
détecter des tendances.&lt;/p&gt;

&lt;p&gt;Dès le départ, AstMetrics a été pensé comme &lt;strong&gt;agnostique du langage&lt;/strong&gt;. Rien n’empêche d’analyser du PHP, du JavaScript,
du Python ou du Go : tant que je peux obtenir un AST dans un format stable (JSON par exemple), je peux construire des
métriques dessus. C’est une des raisons qui m’ont poussées à démarrer AstMetrics par rapport
à &lt;a href=&quot;https://github.com/phpmetrics/PhpMetrics&quot;&gt;PhpMetrics&lt;/a&gt;, uniquement orienté PHP.&lt;/p&gt;

&lt;p&gt;C’est dans ce contexte qu’est né &lt;a href=&quot;https://github.com/Halleck45/go-php-parser&quot;&gt;&lt;strong&gt;Go-PHP-Parser&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;le-problème--parser-du-php&quot;&gt;Le problème : parser du PHP&lt;/h2&gt;

&lt;p&gt;Pour récupérer l’AST d’un langage, deux approches principales existent :&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Écrire son propre parser&lt;/strong&gt; : à partir de la grammaire du langage, reconstruire un analyseur lexical et syntaxique.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Réutiliser le parser officiel&lt;/strong&gt; : l’embarquer ou l’appeler pour récupérer directement l’AST produit.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Au départ, j’ai exploré la première voie, qui m’a parue plus intéressante.&lt;/p&gt;

&lt;h2 id=&quot;tentative-1--lex-et-yacc&quot;&gt;Tentative 1 : Lex et Yacc&lt;/h2&gt;

&lt;h3 id=&quot;quest-ce-que-lexyacc-&quot;&gt;Qu’est-ce que Lex/Yacc ?&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Lex_(software)&quot;&gt;&lt;strong&gt;Lex&lt;/strong&gt;&lt;/a&gt; est un générateur d’analyseur lexical. On décrit les &lt;em&gt;tokens&lt;/em&gt; d’un langage (mots-clés, opérateurs, chaînes de
caractères, etc.) sous forme d’expressions régulières. Lex génère du code C qui sait découper un fichier source en une
suite de tokens.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Yacc&quot;&gt;&lt;strong&gt;Yacc&lt;/strong&gt;&lt;/a&gt; (Yet Another Compiler Compiler) est un générateur d’analyseur syntaxique. On décrit la grammaire d’un langage
en termes de règles de production (par ex. une &lt;em&gt;expression&lt;/em&gt; est soit un nombre, soit une addition de deux
expressions). Yacc génère ensuite un parser qui construit un arbre syntaxique à partir des tokens produits par Lex.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La combinaison Lex+Yacc est classique : c’est ce qui a servi à écrire des parseurs pour de nombreux langages dans les
années 80-90. Il existe des équivalents modernes en Go, comme &lt;code&gt;goyacc&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ce sont des outils fondamentaux, utilisés comme moteur de compilation pour de nombreux langages de programmation.&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;essayer-de-parser-php-avec-lexyacc&quot;&gt;Essayer de parser PHP avec Lex/Yacc&lt;/h3&gt;

&lt;p&gt;J’ai donc commencé à écrire une grammaire PHP pour Yacc en Go. Très vite, j’ai vu les limites :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;La grammaire PHP est énorme, pleine de cas particuliers et d’ambiguïtés historiques.&lt;/li&gt;
  &lt;li&gt;Chaque version du langage ajoute de nouvelles constructions (par exemple, les &lt;em&gt;match expressions&lt;/em&gt; en PHP 8).&lt;/li&gt;
  &lt;li&gt;Maintenir cette grammaire à jour aurait demandé un travail colossal et constant.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;J’ai essayé d’automatiser une partie via des IA pour générer les règles. C’était visiblement trop complexe pour l’IA.
Peut-être que d’ici quelques mois, avec les progrès actuels, ça vaudra plus le coup… J’y ai passé des heures, pour le moment j’abandonne ce chemin.&lt;/p&gt;

&lt;p&gt;D’ailleurs, un projet comme &lt;a href=&quot;https://github.com/z7zmey/php-parser&quot;&gt;z7zmey/php-parser&lt;/a&gt; a pris cette voie.
C’est un parseur PHP natif en Go basé sur une grammaire écrite à la main.
Mais il n’est pas complètement à jour (PHP 8.2), et on comprend pourquoi : maintenir une grammaire manuelle de PHP dans
un langage tiers est une tâche sans fin.&lt;/p&gt;

&lt;p&gt;Résultat : j’ai beaucoup appris, mais j’ai abandonné l’idée.&lt;/p&gt;

&lt;p&gt;Si le sujet vous intéresse, &lt;strong&gt;je vous recommande de lire &lt;a href=&quot;https://www.oreilly.com/library/view/lex-yacc/9781565920002/ch01.html&quot;&gt;Lex &amp;amp; Yacc&lt;/a&gt;&lt;/strong&gt;,
de John Levine, Doug Brown, Tony Mason. C’est dense mais vraiment utile, surtout si vous aimez les expressions régulières !&lt;/p&gt;

&lt;h2 id=&quot;tentative-2--réutiliser-le-parser-officiel&quot;&gt;Tentative 2 : réutiliser le parser officiel&lt;/h2&gt;

&lt;p&gt;La deuxième voie consiste à ne pas réinventer la roue.&lt;/p&gt;

&lt;p&gt;PHP possède déjà son parser officiel, maintenu par l’équipe du langage. Il existe même une
extension, &lt;a href=&quot;https://github.com/nikic/php-ast&quot;&gt;ext-ast&lt;/a&gt;, qui expose l’AST PHP en interne, sous une forme stable et
versionnée (merci &lt;a href=&quot;https://github.com/nikic&quot;&gt;Nikita popov&lt;/a&gt; 🙏)&lt;/p&gt;

&lt;p&gt;Le problème : pour l’utiliser, il faut avoir &lt;strong&gt;PHP installé&lt;/strong&gt; dans la bonne version, et en plus avoir activé l’extension &lt;code&gt;ext-ast&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;C’est faisable en local, mais pas dans le cadre d’un outil générique comme AstMetrics qui doit tourner sur n’importe
quelle machine, sans dépendance.&lt;/p&gt;

&lt;p&gt;J’ai tout de même essayé de builder un standalone PHP pour parser du code. Ca marchait bien, mais les performances
étaient catastrophiques, ainsi que le CPU utilisé.&lt;/p&gt;

&lt;p&gt;Le plus logique (pas forcément le plus simple, je le reconnais) : passer sur sur &lt;code&gt;C&lt;/code&gt;, et utiliser la
&lt;code&gt;SAPI Embed&lt;/code&gt; pour appeler le parser officiel.&lt;/p&gt;

&lt;h2 id=&quot;go-php-parser--embarquer-php-dans-go&quot;&gt;Go-PHP-Parser : embarquer PHP dans Go&lt;/h2&gt;

&lt;p&gt;La solution retenue a été d’&lt;strong&gt;embarquer le moteur PHP directement comme une librairie C&lt;/strong&gt; grâce à la &lt;strong&gt;SAPI Embed&lt;/strong&gt;.&lt;/p&gt;

&lt;h3 id=&quot;sapi-embed&quot;&gt;SAPI Embed&lt;/h3&gt;

&lt;p&gt;PHP propose plusieurs SAPIs (Server API). La plus connue est le &lt;strong&gt;&lt;code&gt;SAPI FPM&lt;/code&gt;&lt;/strong&gt; pour exécuter PHP derrière un serveur
web.&lt;br /&gt;
La SAPI Embed est une interface qui permet d’utiliser le moteur PHP &lt;strong&gt;comme une bibliothèque&lt;/strong&gt; au sein d’un autre
programme &lt;code&gt;C&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;On peut ainsi initialiser le moteur, lui donner un bout de code, et récupérer le résultat.&lt;/p&gt;

&lt;p&gt;Cette SAPI est &lt;a href=&quot;https://github.com/php/php-src/tree/master/sapi/embed&quot;&gt;disponible sur le repository Github de PHP&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;ext-ast&quot;&gt;ext-ast&lt;/h3&gt;

&lt;p&gt;En activant &lt;code&gt;ext-ast&lt;/code&gt;, je peux demander à PHP de me renvoyer non pas le résultat d’exécution, mais directement l’&lt;code&gt;AST&lt;/code&gt;
du code.&lt;/p&gt;

&lt;p&gt;Cet AST est identique à celui que PHP utilise en interne, donc toujours à jour avec les évolutions du langage.&lt;/p&gt;

&lt;p&gt;Un AST est simplement une représentation en arbre de votre code source. Par exemple, le code:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;while b ≠ 0:
    if a &amp;gt; b:
        a := a - b
    else:
        b := b - a
return a
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Est représenté par cet arbre (&lt;em&gt;illustration wikipedia)&lt;/em&gt;:&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://upload.wikimedia.org/wikipedia/commons/thumb/c/c7/Abstract_syntax_tree_for_Euclidean_algorithm.svg/500px-Abstract_syntax_tree_for_Euclidean_algorithm.svg.png&quot; alt=&quot;AST&quot; width=&quot;600px&quot; /&gt;
&lt;/p&gt;

&lt;h3 id=&quot;bridge-c--go&quot;&gt;Bridge C ↔ Go&lt;/h3&gt;

&lt;p&gt;J’ai écrit un petit bridge en &lt;code&gt;C&lt;/code&gt; qui :&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Initialise le moteur PHP embed.&lt;/li&gt;
  &lt;li&gt;Passe le code source PHP à &lt;code&gt;ext-ast&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Sérialise l’AST en JSON.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ce bridge est exposé côté Go via &lt;strong&gt;cgo&lt;/strong&gt;. En pratique, dans Go j’appelle une fonction simple :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;ast, err := parser.Parse(&quot;&amp;lt;?php echo 1 + 2;&quot;)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;et je reçois une structure JSON décrivant l’AST.&lt;/p&gt;

&lt;h3 id=&quot;distribution-simplifiée&quot;&gt;Distribution simplifiée&lt;/h3&gt;

&lt;p&gt;Pour éviter à l’utilisateur de devoir compiler PHP embed lui-même, le projet s’appuie
sur &lt;a href=&quot;https://github.com/crazywhalecc/static-php-cli&quot;&gt;static-php-cli&lt;/a&gt; :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Des binaires précompilés de PHP + ext-ast sont fournis.&lt;/li&gt;
  &lt;li&gt;Lors de la première utilisation, le binaire adapté à la plateforme est téléchargé automatiquement.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Résultat : l’utilisateur Go n’a rien à installer. Un simple :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;go get github.com/Halleck45/go-php-parser
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;et tout fonctionne.&lt;/p&gt;

&lt;p&gt;A l’avenir, je me passerai peut-être de &lt;code&gt;static-php-cli&lt;/code&gt;, si je m’aperçois que le projet n’est plus maintenu. C’est
possible, même si &lt;code&gt;static-php-cli&lt;/code&gt; fait gagner beaucoup de temps pour la phase de compilation de PHP.&lt;/p&gt;

&lt;h2 id=&quot;architecture&quot;&gt;Architecture&lt;/h2&gt;

&lt;p&gt;Voici un aperçu de l’architecture générale de Go-PHP-Parser :&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
    &lt;img src=&quot;/images/2025-08-08-archi-go-php-parser.png&quot; alt=&quot;Diagramme d&apos;architecture&quot; width=&quot;600px&quot; /&gt;
&lt;/p&gt;

&lt;h2 id=&quot;pourquoi-go-&quot;&gt;Pourquoi Go ?&lt;/h2&gt;

&lt;p&gt;Deux raisons principales :&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Performance&lt;/strong&gt; : Go compile en binaire natif, sans runtime lourd. Il est rapide pour gérer des appels &lt;code&gt;C&lt;/code&gt; via &lt;code&gt;cgo&lt;/code&gt;,
et efficace pour traiter des gros volumes de fichiers en parallèle grâce aux goroutines. C’est parfait pour scanner
des dépôts entiers.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Interopérabilité&lt;/strong&gt; : Go est un bon langage pour écrire des bibliothèques simples à utiliser. En fournissant une API
Go, je rends l’intégration dans AstMetrics triviale.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;comparaison-des-approches&quot;&gt;Comparaison des approches&lt;/h2&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Approche&lt;/th&gt;
      &lt;th&gt;Avantage&lt;/th&gt;
      &lt;th&gt;Inconvénient&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Parser maison (Lex/Yacc)&lt;/td&gt;
      &lt;td&gt;Indépendant, contrôle total&lt;/td&gt;
      &lt;td&gt;Maintenance énorme, lent à mettre à jour&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Projet type z7zmey&lt;/td&gt;
      &lt;td&gt;Natif Go, rapide&lt;/td&gt;
      &lt;td&gt;Pas à jour, coûteux à maintenir&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Appel à un binaire PHP&lt;/td&gt;
      &lt;td&gt;Simple à écrire&lt;/td&gt;
      &lt;td&gt;Process externe, coût d’I/O, dépendance à l’installation&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Embed + ext-ast (choix actuel)&lt;/td&gt;
      &lt;td&gt;Rapide, toujours à jour, maintenance réduite&lt;/td&gt;
      &lt;td&gt;Nécessite un bridge C et des binaires embarqués. Complexe&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;performance&quot;&gt;Performance&lt;/h2&gt;

&lt;p&gt;Les benchmarks préliminaires montrent que :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Le parsing d’un fichier PHP est du même ordre de grandeur que via &lt;code&gt;php-ast&lt;/code&gt; en natif (4 000 à 8 000 fichiers par seconde sur mon PC de 16 coeurs et 32 Go de RAM).&lt;/li&gt;
  &lt;li&gt;L’embed évite le coût de lancer un process &lt;code&gt;php&lt;/code&gt; à chaque fichier.&lt;/li&gt;
  &lt;li&gt;Pour un scan massif, le vrai goulot reste les I/O disques, pas le parsing lui-même.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;potentiel-et-usages&quot;&gt;Potentiel et usages&lt;/h2&gt;

&lt;p&gt;Go-PHP-Parser est né pour servir &lt;strong&gt;AstMetrics&lt;/strong&gt;, mais il peut servir bien plus :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Exécution de code PHP depuis Go (je pense qu’il y a là un vrai potentiel)&lt;/li&gt;
  &lt;li&gt;Outils de refactoring automatique.&lt;/li&gt;
  &lt;li&gt;Analyse statique intégrée à la CI/CD.&lt;/li&gt;
  &lt;li&gt;Indexation de code pour moteurs de recherche ou big-code.&lt;/li&gt;
  &lt;li&gt;Aide à la migration entre versions de PHP.&lt;/li&gt;
  &lt;li&gt;Génération de documentation à partir du code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tout ce qui nécessite un accès fiable et rapide à l’AST PHP.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Go-PHP-Parser&lt;/strong&gt; n’est pas un parseur écrit from scratch, et c’est volontaire.&lt;/p&gt;

&lt;p&gt;Plutôt que de maintenir une grammaire PHP parallèle, j’ai préféré m’appuyer sur le parser officiel du langage, via la SAPI embed et &lt;code&gt;ext-ast&lt;/code&gt;.&lt;br /&gt;
Cela permet de rester toujours à jour, tout en bénéficiant des performances du natif et de la simplicité de Go.&lt;/p&gt;

&lt;p&gt;Prochaines étapes pour moi : l’utiliser dans AstMetrics ! C’est du boulot, mais petit à petit ça avance, parmi mes nombreux autres projets…&lt;/p&gt;

&lt;p&gt;J’espère que le projet servira à d’autres ! Si vous voulez tester ou contribuer, le projet est disponible
ici : &lt;a href=&quot;https://github.com/Halleck45/go-php-parser&quot;&gt;https://github.com/Halleck45/go-php-parser&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Monorepo : pourquoi (et comment) rapatrier vos dépôts existants avec git subtree</title>
   <link href="https://blog.lepine.pro/monorepo-git-subtree"/>
   <updated>2025-08-08T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/monorepo-git-subtree</id>
   <content type="html">&lt;blockquote&gt;
  &lt;p&gt;Vous entendez parler de &lt;em&gt;monorepo&lt;/em&gt; partout, ça a l’air ésotérique, mais en vrai c’est pas si compliqué.&lt;br /&gt;
Dans ce billet, on va voir à quoi ça sert, pourquoi c’est souvent mystifié, ce qu’est &lt;strong&gt;Git subtree&lt;/strong&gt; (et en quoi c’est différent de submodules), puis &lt;strong&gt;comment rapatrier proprement des dépôts existants dans un monorepo&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;tldr&quot;&gt;TL;DR&lt;/h2&gt;

&lt;p align=&quot;center&quot;&gt;
    &lt;img src=&quot;/images/2025-08-08-illustration-monorepo1.png&quot; alt=&quot;schema of monorepository&quot; width=&quot;600px&quot; /&gt;
&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Un &lt;strong&gt;monorepo&lt;/strong&gt; = un seul dépôt Git pour plusieurs projets, packages, librairies…&lt;/li&gt;
  &lt;li&gt;C’est utile pour &lt;strong&gt;synchroniser les versions&lt;/strong&gt;, &lt;strong&gt;factoriser les libs&lt;/strong&gt;, &lt;strong&gt;simplifier les PR&lt;/strong&gt; transverses et &lt;strong&gt;industrialiser la CI/CD&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;Pas besoin de Bazel ou d’outillage complexe : &lt;strong&gt;&lt;code&gt;git subtree&lt;/code&gt;&lt;/strong&gt; et &lt;strong&gt;&lt;code&gt;git filter-repo&lt;/code&gt;&lt;/strong&gt; suffisent dans la majorité des cas.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code&gt;subtree&lt;/code&gt;&lt;/strong&gt; garde une &lt;strong&gt;arborescence  propre&lt;/strong&gt; (un dossier par projet) et &lt;strong&gt;l’historique&lt;/strong&gt; de chaque dépôt importé. &lt;strong&gt;Pas de dépendance&lt;/strong&gt; comme avec les submodules.&lt;/li&gt;
  &lt;li&gt;Voici un &lt;a href=&quot;#le-script-pour-importer-un-dépôt-existant-dans-un-monorepo-avec-historique&quot;&gt;script ci-dessous&lt;/a&gt; pour &lt;strong&gt;rapatrier un dépôt existant dans un sous-dossier&lt;/strong&gt; de votre monorepo, en conservant son historique.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;pourquoi-adopter-un-monorepo-&quot;&gt;Pourquoi adopter un monorepo ?&lt;/h2&gt;

&lt;p&gt;Il y a vraiment de nombreux avantages à fusionner vos projets dans un seul dépôt Git :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Visibilité globale&lt;/strong&gt; : un seul endroit pour tout voir (services, libs, front, infra-as-code…).&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Refactorings atomiques&lt;/strong&gt; : une PR peut toucher plusieurs packages d’un coup.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Outillage unifié&lt;/strong&gt; : lints/formatters/tests/CI standardisés.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Gestion des versions&lt;/strong&gt; : stratégies de release cohérentes (versionning de libs internes, changelogs, etc.).&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Onboarding&lt;/strong&gt; : un clone, et les devs ont tout ce qu’il faut.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La simplicité de ne faire qu’une seule Pull Request à un seul endroit est vraiment un plaisir au quotidien.&lt;/p&gt;

&lt;p&gt;Par exemple : si vous travaillez sur un projet avec un dépôt pour le front et un autre pour le back. Si jusqu’ici vous 
deviez faire deux Pull Requests, attendre que la première soit mergée pour accepter la seconde… désormais vous pouvez faire une seule PR pour les deux.&lt;/p&gt;

&lt;h2 id=&quot;subtree-na-rien-de-mystique&quot;&gt;&lt;code&gt;subtree&lt;/code&gt; n’a rien de mystique&lt;/h2&gt;

&lt;p&gt;On confonds les deux, mais les  &lt;code&gt;subtree&lt;/code&gt; et les &lt;code&gt;submodule&lt;/code&gt; n’ont pas grand chose en commun.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Submodules&lt;/strong&gt; : pointent vers d’autres dépôts (pointeurs Git). Souvent pénibles à synchroniser, cassent le &lt;em&gt;dev workflow&lt;/em&gt; si tout le monde n’est pas à l’aise.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Subtree&lt;/strong&gt; : &lt;strong&gt;intègre&lt;/strong&gt; le contenu d’un dépôt &lt;strong&gt;dans un sous-dossier&lt;/strong&gt; de votre monorepo, &lt;strong&gt;en conservant l’historique&lt;/strong&gt;. Vous pouvez ensuite &lt;strong&gt;pull/push&lt;/strong&gt; des mises à jour entre le monorepo et le dépôt d’origine si vous le gardez vivant.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Si vous avez déjà essayé de faire un monorepo avec les &lt;code&gt;submodules&lt;/code&gt;, vous savez que c’est vraiment galère.&lt;/p&gt;

&lt;p&gt;Utiliser des &lt;code&gt;submodules&lt;/code&gt; revient à ajouter une dépendance externe dans un repo interne. On se tire rarement une balle dans le pied en 2025 avec des submodules si on peut l’éviter.&lt;/p&gt;

&lt;h2 id=&quot;est-ce-que-je-vais-perdre-mon-historique-&quot;&gt;Est-ce que je vais perdre mon historique ?&lt;/h2&gt;

&lt;p&gt;Non (sauf si vous choisissez délibérement de le faire avec l’option &lt;code&gt;--squash&lt;/code&gt;)&lt;/p&gt;

&lt;h2 id=&quot;deux-stratégies-pour-rapatrier-des-dépôts&quot;&gt;Deux stratégies pour rapatrier des dépôts&lt;/h2&gt;

&lt;h3 id=&quot;1-git-subtree-add&quot;&gt;1) &lt;strong&gt;&lt;code&gt;git subtree add&lt;/code&gt;&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;C’est rapide et rapide et natif :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git remote add my-origin git@github.com:acme/my-repository.git
git subtree add --prefix=packages/my-repository my-origin main --squash
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Ici:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;--prefix&lt;/code&gt; : le dossier cible dans votre monorepo.&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;--squash&lt;/code&gt; : optionnel, compresse l’historique si vous ne voulez pas tout garder (perso je préfère &lt;strong&gt;conserver l’historique&lt;/strong&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;2-git-filter-repo--merge-plus-flexible&quot;&gt;2) &lt;strong&gt;&lt;code&gt;git filter-repo&lt;/code&gt; + merge&lt;/strong&gt; (plus flexible)&lt;/h3&gt;

&lt;p&gt;Vous clonez le dépôt source, &lt;strong&gt;réécrivez son historique pour le placer sous un sous-dossier&lt;/strong&gt;, puis &lt;strong&gt;merge&lt;/strong&gt; dans le monorepo.&lt;/p&gt;

&lt;p&gt;Vous avez plus de contrôle sur le résultat final, vous pouvez renommer, nettoyer…&lt;/p&gt;

&lt;p&gt;C’est cette 2ᵉ approche que j’automatise ci-dessous.&lt;/p&gt;

&lt;h2 id=&quot;le-script-pour-importer-un-dépôt-existant-dans-un-monorepo-avec-historique&quot;&gt;Le script pour importer un dépôt existant dans un monorepo (avec historique)&lt;/h2&gt;

&lt;p&gt;En général, on place les repositories importés dans un dossier &lt;code&gt;packages&lt;/code&gt;. Dumoins, c’est ce que j’ai toujours rencontré, j’en déduis que c’est un standard.&lt;/p&gt;

&lt;p&gt;Pré-requis :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Git 2.30+&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/newren/git-filter-repo&quot;&gt;&lt;code&gt;git-filter-repo&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; installé (successeur moderne de &lt;code&gt;git filter-branch&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Usage :&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;./import-into-monorepo.sh &amp;lt;adresse-du-repository&amp;gt; [branche] &amp;lt;dossier-destination&amp;gt;

# exemple:
./import-into-monorepo.sh git@github.com:acme/my-repository.git main packages/my-repository
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Et le code de &lt;code&gt;import-into-monorepo.sh&lt;/code&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/usr/bin/env bash
set -euo pipefail

MONOREPO_DIR=&quot;$( cd &quot;$( dirname &quot;${BASH_SOURCE[0]}&quot; )&quot; &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 &amp;amp;&amp;amp; pwd )&quot;

REPOSITORY=&quot;${1:-}&quot;
BRANCH=&quot;${2:-main}&quot;
FOLDER=&quot;${3:-}&quot;

if [[ -z $REPOSITORY ]]; then
    echo &quot;Usage: $0 &amp;lt;repository&amp;gt; [branch] [folder]&quot;
    echo &quot;Example: $0 git@github.com:acme/my-repository.git main packages/my-repository&quot;
    exit 1
fi

if [[ -z ${FOLDER} ]]; then
    echo &quot;Error: &amp;lt;folder&amp;gt; (destination in monorepo) is required&quot;
    exit 1
fi

if ! command -v git-filter-repo &amp;gt;/dev/null 2&amp;gt;&amp;amp;1; then
    echo &quot;Error: git-filter-repo not found. Install it: https://github.com/newren/git-filter-repo&quot;
    exit 1
fi

if [[ -d $FOLDER ]]; then
    echo &quot;Folder $FOLDER already exists in the monorepo&quot;
    exit 1
fi

# Clean temp workdir
if [[ -d /tmp/workdir ]]; then
    rm -Rf /tmp/workdir
fi

# Clone source repo (single branch)
cd /tmp
git clone --branch &quot;$BRANCH&quot; --single-branch &quot;$REPOSITORY&quot; /tmp/workdir

# Rewrite history to move repo into $FOLDER
cd /tmp/workdir
git filter-repo --to-subdirectory-filter &quot;$FOLDER&quot;

# Merge rewritten history into monorepo
cd &quot;$MONOREPO_DIR&quot;
git remote -v | grep -q &quot;repo-to-import&quot; &amp;amp;&amp;amp; git remote remove repo-to-import || true
git remote add repo-to-import /tmp/workdir
git fetch repo-to-import

# If you want to allow unrelated histories (typical case)
git merge --allow-unrelated-histories &quot;repo-to-import/$BRANCH&quot; -m &quot;Import $REPOSITORY into $FOLDER&quot;

# Optional: cleanup remote
git remote remove repo-to-import || true

echo &quot;✅ Imported $REPOSITORY into monorepo at $FOLDER (branch: $BRANCH).&quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;bonus--workflow-github-actions-optionnel-pour-pousser-le-code-vers-lancien-repository&quot;&gt;Bonus : workflow GitHub Actions (optionnel) pour pousser le code vers l’ancien repository&lt;/h2&gt;

&lt;p&gt;Maintenant, vous pourriez avoir envie de garder l’ancien dépôt actif. Par exemple,
si vous avez des Github actions qui tournent dessus, ou si vous souhaitez garder des PR ouvertes…&lt;/p&gt;

&lt;p&gt;Vous pouvez automatiser ce processus avec GitHub Actions.&lt;/p&gt;

&lt;p&gt;Créer un fichier &lt;code&gt;.github/workflows/push-subtree.yml&lt;/code&gt; avec le contenu suivant :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;name: Push subtrees
on:
  push:
    branches: [ main ]
    paths:
      - &apos;packages/my-repository/**&apos;

jobs:
  push-subtree:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Push subtree to legacy repo
        run: |
          git remote add legacy git@github.com:acme/my-repository.git
          git subtree push --prefix=packages/my-repository legacy main
&lt;/code&gt;&lt;/pre&gt;

&lt;hr /&gt;

&lt;p&gt;Cette action va se déclencher à chaque push sur le branch &lt;code&gt;main&lt;/code&gt;, s’il y a eu des modificationsdans le dossier &lt;code&gt;packages/my-repository&lt;/code&gt;, et 
va renvoyer les dernières modifications sur le dépôt d’origine.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Comment améliorer votre CV (du point de vue d'un CTO) ?</title>
   <link href="https://blog.lepine.pro/retours-experience-cv"/>
   <updated>2025-05-08T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/retours-experience-cv</id>
   <content type="html">&lt;p&gt;Je ne suis pas recruteur, et je n’ai pas la prétention d’être expert en recrutement.&lt;/p&gt;

&lt;p&gt;Par contre, en tant que CTO, et leader technique depuis quelques années déjà, j’ai été amené à 
lire un bon paquet de CV. J’estime avoir mené environ 350 entretiens techniques. Je n’aurai pas la prétention de parler d’erreurs, mais je vois 
des choses qui dévalorisent parfois les CV que je lis, et &lt;strong&gt;je voudrais partager quelques conseils pour vous aider à mieux valoriser 
votre profil.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;C’est un billet que j’aurais aimé lire il y a quelques années, côté candidat, et qui j’espère pourra vous aider.&lt;/p&gt;

&lt;p&gt;Il existe des tonnes
de sites sur le sujet, mais curieusement peu me paraissent concrets, c’est souvent des sites qui semblent plus vouloir faire du trafic que de 
réellement partager des retours d’expérience réels ; J’espère combler un peu ce manque.&lt;/p&gt;

&lt;h2 id=&quot;comment-ça-se-passe-quand-un-recruteur-reçoit-un-cv-&quot;&gt;Comment ça se passe quand un recruteur reçoit un CV ?&lt;/h2&gt;

&lt;p&gt;L’habitude consiste à utiliser un ATS (Applicant Tracking System) pour trier les CV. &lt;strong&gt;C’est exactement 
comme l’outil de ticketing que vous avez l’habitude d’utiliser (comme Jira, Trello, Asana…), mais avec des CV&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Ca veut dire que, comme pour ces outils, c’est parfois un peu le bazar et fastidieux à utiliser.&lt;/p&gt;

&lt;p&gt;Voici par exemple une vue de l’ATS de WelcomeToTheJungle:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2025-05-capture-ats-wttj.png&quot; alt=&quot;ATS de WelcomeToTheJungle&quot; class=&quot;mx-auto my-8&quot; style=&quot;width: 800px; max-width:100%; height: auto;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Même si la capture est anonymisée, vous pouvez remarquer une chose : il y a beaucoup de candidats. Et, d’aussi loin que je 
recrute, c’est toujours le cas. L’exemple ci-dessus est une annonce pour un poste de développeur front-end, et il y a déjà 150 candidats.&lt;/p&gt;

&lt;p&gt;Vu le nombre de candidats, et même si c’est super important, &lt;strong&gt;il est difficile de passer plus de quelques secondes, ou dizaines de 
secondes, sur un CV&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;C’est la partie la plus difficile pour un candidat : il faut se démarquer, et faire en sorte que le·la recruteur·se ait envie
de passer plus de temps sur votre CV.&lt;/p&gt;

&lt;p&gt;D’un point de vue recruteur, c’est pas évident : l’enjeu est énorme, et refouler un CV est un geste humainement difficile, 
car on sait qu’un·e candidat·e a passé du temps à postuler, et attend beaucoup de cette candidature. Recruter a un un 
impact monumental sur la vie des gens, et c’est pas un geste anodin. Mais il faut bien faire le tri et rester pragmatique.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Plus vous aiderez le·la recruteur·se à comprendre votre parcours rapidement, plus il sera enclin à passer du temps sur votre CV.&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;comment-faire-pour-se-démarquer-&quot;&gt;Comment faire pour se démarquer ?&lt;/h2&gt;

&lt;h2 id=&quot;tldr--les-essentiels&quot;&gt;TL;DR : les essentiels&lt;/h2&gt;

&lt;p&gt;Parce qu’on n’a pas toujours le temps de lire un article entier, voici un résumé des conseils que je vais vous donner dans cet article :&lt;/p&gt;

&lt;div class=&quot;rounded-lg bg-gray-100 p-4 my-8&quot;&gt;
    &lt;div class=&quot;grid md:grid-cols-2 gap-4&quot;&gt;
        &lt;div&gt;
            &lt;ul&gt;
            &lt;li&gt;&lt;b&gt;Regardez &lt;a href=&quot;https://www.google.com/about/careers/applications/videos/google-resume-tips-and-advice/?hl=en_US&quot;&gt;cette vidéo de Google Careers.&lt;/a&gt;&lt;/b&gt;&lt;/li&gt;
            &lt;li&gt;Résumez votre parcours dès le début, et indiquez ce que vous recherchez.&lt;/li&gt;
            &lt;li&gt;Donnez des chiffres, des impacts, pas juste des mots.&lt;/li&gt;
            &lt;li&gt;Mettez le contexte de vos missions.&lt;/li&gt;
            &lt;li&gt;Ne mettez pas de lettre de motivation, sauf si elle est personnalisée.&lt;/li&gt;
            &lt;li&gt;Lisez &lt;a href=&quot;https://www.amazon.fr/LArt-lenchantement-Comment-influencer-esprits-ebook/dp/B082DL3LGT&quot;&gt;L&apos;Art de l&apos;enchantement&lt;/a&gt;.&lt;/li&gt;
            &lt;li&gt;Privilégiez la clarté à l’exhaustivité.&lt;/li&gt;
            &lt;li&gt;Faites les calculs de date vous-mêmes&lt;/li&gt;
            &lt;li&gt;Soyez aligné… visuellement aussi.&lt;/li&gt;
            &lt;li&gt;Précisez l’usage des outils plutôt qu’un inventaire&lt;/li&gt;
            &lt;li&gt;Ayez un CV clair et beau&lt;/li&gt;
            &lt;li&gt;N&apos;hésitez pas à relancer&lt;/li&gt;
            &lt;li&gt;Ne mettez pas d’étoiles ou de barres de progression&lt;/li&gt;
            &lt;/ul&gt;
        &lt;/div&gt;
        &lt;div&gt;
            &lt;iframe class=&quot;my-8 mx-auto&quot; width=&quot;560&quot; height=&quot;315&quot; style=&quot;max-width: 100%&quot; src=&quot;https://www.youtube.com/embed/S_Macvy5CQE?si=WXHVu_M3U-yyf8dx&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Vous pouvez vous arrêter là, ou continuer à lire pour avoir plus de détails sur chacun de ces conseils.&lt;/p&gt;

&lt;h2 id=&quot;parlez-de-votre-impact&quot;&gt;Parlez de votre impact&lt;/h2&gt;

&lt;p&gt;La première chose de mon point de vue consiste à afficher vos accomplissements.&lt;/p&gt;

&lt;p&gt;Par exemple, dans votre expérience précédente, plutôt que de mettre quelque chose comme:&lt;/p&gt;

&lt;div class=&quot;border bg-rose-50 border-rose-100 text-rose-800 px-4 py-3 rounded relative&quot;&gt;
&lt;div class=&quot;text-rose-800 text-center text-sm pb-4&quot;&gt;Version améliorable&lt;/div&gt;
&lt;b&gt;Développeur front-end chez MonEntreprise (2020 - 2023)&lt;/b&gt;
&lt;ul style=&quot;margin-bottom:0; &quot;&gt;
&lt;li&gt;Développement de l&apos;application web (React)&lt;/li&gt;
&lt;li&gt;Développement de l&apos;API (PHP)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;

&lt;p&gt;Trouvez un moyen de &lt;strong&gt;mettre en avant l’impact de votre travail&lt;/strong&gt;. Par exemple :&lt;/p&gt;

&lt;div class=&quot;border bg-lime-50 border-lime-100 text-lime-800 px-4 py-3 rounded relative&quot;&gt;
&lt;div class=&quot;text-lime-800 text-center text-sm pb-4&quot;&gt;Version améliorée&lt;/div&gt;
&lt;b&gt;Développeur front-end chez MonEntreprise (2020 - 2023)&lt;/b&gt;
&lt;ul style=&quot;margin-bottom:0; &quot;&gt;
&lt;li&gt;Développement de l&apos;application web (React) : 20 composants créés, accessibilité améliorée de 30% (audit Lighthouse)&lt;/li&gt;
&lt;li&gt;Développement de l&apos;API (PHP) : Temps de réponse moyen réduit de 20%&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;

&lt;p&gt;Je vous invite FORTEMENT à &lt;a href=&quot;https://buildyourfuture.withgoogle.com/resources&quot;&gt;lire la section Carrière de Google&lt;/a&gt;, 
et à &lt;a href=&quot;https://www.google.com/about/careers/applications/videos/google-resume-tips-and-advice/?hl=en_US&quot;&gt;visionner cette vidéo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;On me dit parfois que “&lt;strong&gt;oui mais moi ma boîte elle mesure rien, donc je peux pas mettre de chiffres&lt;/strong&gt;”. 
Je vous réponds que &lt;strong&gt;c’est faux&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Vous pouvez toujours trouver un moyen de quantifier votre impact. Posez-vous ces questions :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;est-ce que vous avez amélioré la productivité de l’équipe ?&lt;/li&gt;
  &lt;li&gt;Est-ce que vous avez réduit le temps de développement d’une fonctionnalité ?&lt;/li&gt;
  &lt;li&gt;Est-ce que vous avez amélioré la satisfaction des utilisateurs ?&lt;/li&gt;
  &lt;li&gt;Est-ce que vous avez réduit le nombre de bugs ?&lt;/li&gt;
  &lt;li&gt;Est-ce que vous avez amélioré la qualité du code ?&lt;/li&gt;
  &lt;li&gt;Est-ce que vous avez réduit le temps de réponse d’une API ?&lt;/li&gt;
  &lt;li&gt;Est-ce que vous avez amélioré l’accessibilité d’une application ?&lt;/li&gt;
  &lt;li&gt;Est-ce que vous avez amélioré la sécurité d’une application ?&lt;/li&gt;
  &lt;li&gt;Est-ce que vous avez amélioré la performance d’une application ?&lt;/li&gt;
  &lt;li&gt;Est-ce que vous avez amélioré l’expérience utilisateur ?&lt;/li&gt;
  &lt;li&gt;etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;faites-un-résumé-de-votre-expérience-et-de-ce-que-vous-recherchez&quot;&gt;Faites un résumé de votre expérience et de ce que vous recherchez&lt;/h2&gt;

&lt;p&gt;De ce que j’ai pu glaner comme informations, &lt;strong&gt;un recruteur se fait une idée de votre profil en lisant la première 
moitié de la première page de votre CV.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;C’est un peu comme la ligne de flottaison d’un site web : il faut que ça soit clair et concis.&lt;/p&gt;

&lt;p&gt;Je vous conseille d’ajouter une phrase ou deux en haut de votre CV, qui résume votre expérience et ce que vous recherchez. Par exemple :&lt;/p&gt;

&lt;div class=&quot;border bg-lime-50 border-lime-100 text-lime-800 px-4 py-3 rounded relative&quot;&gt;
&lt;div class=&quot;text-lime-800 text-center text-sm pb-4&quot;&gt;Version suggérée&lt;/div&gt;
&lt;b&gt;Développeuse front-end avec 5 ans d&apos;expérience spécialisée en Vue.js, je suis passionnée par l&apos;accessibilité et l&apos;optimisation des performances. 
Je recherche un poste dans une entreprise SaaS qui met l&apos;accent sur la qualité du code et l&apos;expérience utilisateur.&lt;/b&gt;
&lt;/div&gt;

&lt;p&gt;C’est un gain de temps pour le·la recruteur·se, et ça lui permet de savoir si vous correspondez à ce qu’il recherche. Et un 
bon moyen de vous démarquer.&lt;/p&gt;

&lt;h2 id=&quot;donnez-du-contexte&quot;&gt;Donnez du contexte&lt;/h2&gt;

&lt;p&gt;Un autre point important est de donner du contexte à vos missions. Par exemple, précisez combien vous
étiez dans l’équipe, quel était le produit, quel était le contexte de votre mission, etc.&lt;/p&gt;

&lt;p&gt;Par exemple, au lieu de mettre :&lt;/p&gt;

&lt;div class=&quot;border bg-rose-50 border-rose-100 text-rose-800 px-4 py-3 rounded relative&quot;&gt;
&lt;div class=&quot;text-rose-800 text-center text-sm pb-4&quot;&gt;Version améliorable&lt;/div&gt;
&lt;b&gt;Développeuse front-end chez MonEntreprise (3 ans)&lt;/b&gt;
&lt;ul style=&quot;margin-bottom:0; &quot;&gt;
&lt;li&gt;Développement de l&apos;application web (React)&lt;/li&gt;
&lt;li&gt;Développement de l&apos;API (PHP)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;

&lt;p&gt;Essayez de mettre :&lt;/p&gt;

&lt;div class=&quot;border bg-lime-50 border-lime-100 text-lime-800 px-4 py-3 rounded relative&quot;&gt;
&lt;div class=&quot;text-lime-800 text-center text-sm pb-4&quot;&gt;Version améliorée&lt;/div&gt;
&lt;div&gt;&lt;b&gt;Développeuse front-end chez MonEntreprise (3 ans)&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;Équipe de 5 personnes, produit SaaS B2C utilisé par 1000 utilisateurs actifs.&lt;/div&gt;
&lt;div&gt;Contexte : refonte de l&apos;application pour améliorer l&apos;expérience utilisateur et la performance&lt;/div&gt;
&lt;ul style=&quot;margin-bottom:0; &quot;&gt;
&lt;li class=&quot;mt-4&quot;&gt;Développement de l&apos;application web (React) : 20 composants créés, accessibilité améliorée de 30% (audit Lighthouse)&lt;/li&gt;
&lt;li&gt;Développement de l&apos;API (PHP) : Temps de réponse moyen réduit de 20%&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;

&lt;p&gt;Dans le contexte, parlez de l’équipe, du produit, de la taille de l’entreprise, du nombre d’utilisateurs, etc. Le but
est de donner de la perspective :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Type de boîte : ESN ? Produit SaaS ? Agence ?&lt;/li&gt;
  &lt;li&gt;Taille de l’équipe : 2 devs ? 20 ?&lt;/li&gt;
  &lt;li&gt;Mission principale : refonte ? support ? évolution ?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;précisez-lusage-des-outils-plutôt-quun-inventaire&quot;&gt;Précisez l’usage des outils plutôt qu’un inventaire&lt;/h2&gt;

&lt;p&gt;Pas la peine de coller une liste de 30 technos. Dites plutôt :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Docker : optimisation des images et des temps de build&lt;/li&gt;
  &lt;li&gt;PostgreSQL : optimisation de requêtes lentes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mentionnez aussi les “évidences” comme Git, bash, etc. quand vous avez un profil junior ; ça peut rassurer.&lt;/p&gt;

&lt;p&gt;Par exemple, au lieu de mettre :&lt;/p&gt;

&lt;div class=&quot;border bg-rose-50 border-rose-100 text-rose-800 px-4 py-3 rounded relative&quot;&gt;
&lt;div class=&quot;text-rose-800 text-center text-sm pb-4&quot;&gt;Version améliorable&lt;/div&gt;
&lt;div&gt;&lt;b&gt;Développeuse front-end chez MonEntreprise (3 ans)&lt;/b&gt;&lt;/div&gt;
(...)
&lt;div&gt;
Technos : Docker, PostgreSQL, React, PHP, Git, bash
&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Essayez de mettre :&lt;/p&gt;

&lt;div class=&quot;border bg-lime-50 border-lime-100 text-lime-800 px-4 py-3 rounded relative&quot;&gt;
&lt;div class=&quot;text-lime-800 text-center text-sm pb-4&quot;&gt;Version améliorée&lt;/div&gt;
&lt;div&gt;&lt;b&gt;Développeuse front-end chez MonEntreprise (3 ans)&lt;/b&gt;&lt;/div&gt;
(...)
&lt;div&gt;
Technos : 
&lt;b&gt;Docker&lt;/b&gt; (optimisation des images et des temps de build),
&lt;b&gt;PostgreSQL&lt;/b&gt; (optimisation de requêtes lentes), 
&lt;b&gt;React&lt;/b&gt; (20 composants créés, accessibilité améliorée de 30% (audit Lighthouse),
&lt;b&gt;PHP&lt;/b&gt; (développement de l&apos;API Laravel),
Git, Bash
&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Surtout, ne mettez pas de technologies qui seraient simplement présentes dans l’entreprise, mais que vous n’avez pas
utilisées.&lt;/p&gt;

&lt;p&gt;L’exemple typique est “Tests unitaires” : &lt;strong&gt;si vous n’avez pas écrit de tests unitaires, ne le mettez pas, même si votre
collègue José a bien écrit des tests unitaires, lui&lt;/strong&gt;. Ce serait le meilleur moyen de créer de la frustration lors de l’entretien
(et c’est vraiment hyper fréquent, je vous assure).&lt;/p&gt;

&lt;h2 id=&quot;ayez-un-cv-clair-et-beau&quot;&gt;Ayez un CV clair et beau&lt;/h2&gt;

&lt;p&gt;Si si, je vous le promet : &lt;strong&gt;le style ça compte&lt;/strong&gt;. Inconsciemment ou pas, un CV bien présenté, avec une belle mise en page,
donne envie de le lire.&lt;/p&gt;

&lt;p&gt;Je vous conseille de lire ce merveilleux livre &lt;a href=&quot;https://www.amazon.fr/LArt-lenchantement-Comment-influencer-esprits-ebook/dp/B082DL3LGT&quot;&gt;L’Art de l’enchantement - Comment influencer les coeurs, les esprits et les actes&lt;/a&gt;, 
ce sera sans doute l’un des meilleurs investissements de votre carrière.&lt;/p&gt;

&lt;p&gt;Je vous invite également à &lt;strong&gt;arrêter d’utiliser LaTeX pour faire votre CV&lt;/strong&gt;. Oui oui c’est cool vous être trop fort·e en LaTeX, mais
&lt;strong&gt;un CV c’est pas un article scientifique&lt;/strong&gt;. On en voit tellement, et ils sont tous tellement similaires, que ça n’offre
aucun point de différenciation ou d’ancrage mémoriel pour le·la recruteur·se.&lt;/p&gt;

&lt;p&gt;Prenez le temps de faire un CV beau, clair, et qui vous ressemble. Vous pouvez utiliser des outils comme &lt;a href=&quot;https://www.canva.com/fr_fr/&quot;&gt;Canva&lt;/a&gt;, 
&lt;a href=&quot;https://www.figma.com/&quot;&gt;Figma&lt;/a&gt; ou &lt;a href=&quot;https://docs.google.com/&quot;&gt;Google Docs&lt;/a&gt; pour faire un CV qui vous ressemble.&lt;/p&gt;

&lt;p&gt;Attention à bien garder une version “texte” de votre CV, car certains ATS font un premier tri sur le texte brut, et
peuvent ne pas lire les images ou les mises en page trop complexes.&lt;/p&gt;

&lt;p&gt;Si vous êtes, comme moi, nul·le en design, vous pouvez utiliser des modèles de CV sur Canva ou Figma, et les adapter à votre goût,
et même lire &lt;a href=&quot;https://refactoringui.com/&quot;&gt;Refactoring UI&lt;/a&gt; qui vous donnera de superbes astuces pour améliorer vos designs de manière générale.&lt;/p&gt;

&lt;div class=&quot;grid md:grid-cols-2 gap-4&quot;&gt;
&lt;div&gt;
&lt;a href=&quot;https://www.amazon.fr/LArt-lenchantement-Comment-influencer-esprits-ebook/dp/B082DL3LGT&quot;&gt;
&lt;img alt=&quot;L&apos;Art de l&apos;enchantement&quot; src=&quot;https://m.media-amazon.com/images/I/719U4rJnnqL._SY466_.jpg&quot; class=&quot;mx-auto my-8&quot; style=&quot;max-width: 100%; width: 400px; height: auto;&quot; /&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;a href=&quot;https://refactoringui.com/&quot;&gt;
&lt;img alt=&quot;Refactoring UI&quot; src=&quot;https://www.sarasoueidan.com/assets/images/refactoring-ui-book.png&quot; class=&quot;mx-auto my-8&quot; style=&quot;max-width: 100%; width: 400px;  height: auto;&quot; /&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;h2 id=&quot;nhésitez-pas-à-relancer&quot;&gt;N’hésitez pas à relancer&lt;/h2&gt;

&lt;p&gt;Si vous n’avez pas de nouvelles après une semaine, &lt;strong&gt;n’hésitez pas à relancer le·la recruteur·se.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Il est possible qu’il ait oublié de vous répondre, ou qu’il ait eu un contretemps, ou que votre CV soit passé à la trappe.&lt;/p&gt;

&lt;p&gt;Ce sera sans doute un bon moyen de vous démarquer, et de montrer que vous êtes motivé·e. C’est en général un excellent signal pour le·la recruteur·se.&lt;/p&gt;

&lt;p&gt;N’hésitez pas non plus (sans trop en faire) à &lt;strong&gt;échanger avec des personnes de l’entreprise sur LinkedIn&lt;/strong&gt;. C’est un bon moyen 
de vous forger une opinion sur l’entreprise, et de vous faire connaître.&lt;/p&gt;

&lt;h2 id=&quot;expérience-en-remote--dites-le&quot;&gt;Expérience en remote ? Dites-le&lt;/h2&gt;

&lt;p&gt;Si vous cherchez un poste en remote, n’hésitez pas à le préciser dans votre CV, et à bien 
mettre en avant vos expériences en remote.&lt;/p&gt;

&lt;p&gt;Ce sera un énorme levier plus éviter de perdre du temps à postuler pour des postes qui ne sont pas en remote, et 
surtout permettra au recruteur que vous êtes déjà à l’aise avec le travail à distance.&lt;/p&gt;

&lt;p&gt;Par exemple, vous pouvez mettre :&lt;/p&gt;

&lt;div class=&quot;border bg-lime-50 border-lime-100 text-lime-800 px-4 py-3 rounded relative&quot;&gt;
&lt;div class=&quot;text-lime-800 text-center text-sm pb-4&quot;&gt;Version suggérée&lt;/div&gt;
&lt;div&gt;&lt;b&gt;Développeuse front-end chez MonEntreprise (3 ans - 🏠 Full remote)&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;h2 id=&quot;ne-mettez-pas-détoiles-ou-de-barres-de-progression&quot;&gt;Ne mettez pas d’étoiles ou de barres de progression&lt;/h2&gt;

&lt;p&gt;Vous avez mis 4/5 étoiles en PHP ? Super !&lt;/p&gt;

&lt;p&gt;Ca veut donc dire que vous avez le même niveau que ce candidat qui, lui aussi, a mis 4/5 étoiles en PHP, mais fait du PHP depuis 20 ans 
dans des boîtes de la Silicon Valley ? Non ? Dans ce cas évitez de mettre des étoiles ou des barres de progression.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;C’est un peu comme si vous mettiez une note sur 20 à votre CV, mais sans connaître le barème de notation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Je vous conseille de mettre plutôt un petit paragraphe qui explique votre niveau, et ce que vous savez faire avec la techno.&lt;/p&gt;

&lt;p&gt;Par exemple, plutôt que de mettre :&lt;/p&gt;

&lt;div class=&quot;border bg-rose-50 border-rose-100 text-rose-800 px-4 py-3 rounded relative&quot;&gt;
&lt;div class=&quot;text-rose-800 text-center text-sm pb-4&quot;&gt;Version améliorable&lt;/div&gt;
&lt;div&gt;&lt;b&gt;PHP : ⭐⭐⭐⭐&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Essayez de mettre :&lt;/p&gt;

&lt;div class=&quot;border bg-lime-50 border-lime-100 text-lime-800 px-4 py-3 rounded relative&quot;&gt;
&lt;div class=&quot;text-lime-800 text-center text-sm pb-4&quot;&gt;Version suggérée&lt;/div&gt;
&lt;div&gt;&lt;b&gt;PHP&lt;/b&gt; : 4 ans d&apos;expérience, développement d&apos;API RESTful avec Symfony et Laravel, optimisation de requêtes SQL, tests unitaires et fonctionnels.&lt;/div&gt;
&lt;/div&gt;

&lt;h2 id=&quot;si-vous-pouvez-mentionnez-votre-compte-github&quot;&gt;Si vous pouvez, mentionnez votre compte GitHub&lt;/h2&gt;

&lt;p&gt;Alors non, je ne dis pas que vous devez avoir un compte GitHub et avoir fait des tonnes de contribution Open Source pour postuler.&lt;/p&gt;

&lt;p&gt;On a tous une vie, et j’espère que la majorité des recruteurs en sont conscients. Chacun contribue à son rythme, selon ses envies.&lt;/p&gt;

&lt;p&gt;Mais si vous avez un compte Github étoffé, c’est un vrai plus à mentionner sur votre CV. Vous pouvez tout à fait 
mettre une section complémentaire “Open Source” ou “Contributions” dans votre CV, et mettre le lien vers votre compte GitHub.&lt;/p&gt;

&lt;p&gt;Une grosse réserve toutefois : si votre compte ne contient que des exercices imposés lors de votre formation, ce 
n’est pas très intéressant pour le·la recruteur·se. Essayez de mettre en avant des projets personnels, ou des contributions à des projets Open Source.&lt;/p&gt;

&lt;h2 id=&quot;vous-venez-dun-bootcamp--ajoutez-un-portfolio&quot;&gt;vous venez d’un bootcamp ? Ajoutez un portfolio.&lt;/h2&gt;

&lt;p&gt;Les formations bootcamp (type OpenClassroom) sont légion. Pour ne pas dire hyper saturées.&lt;/p&gt;

&lt;p&gt;Si vous êtes junior·e, avec peu d’expérience, le seul moyen efficace de vous démarquer est de montrer vos projets. Je 
sais que c’est pénible, mais c’est un passage obligé dans votre cas, car le marché est saturé de candidats avec des formations similaires.&lt;/p&gt;

&lt;p&gt;N’hésitez pas à ajouter une ou deux pages à votre CV, avec un titre clair “Portfolio”, et à mettre en avant vos projets annexes.
Même si ces projets ne sont pas en ligne, ou n’ont pas d’utilisateurs, c’est pas grave. 
Le but est de montrer que vous êtes capable de mener un projet à terme, et que vous avez une certaine autonomie.&lt;/p&gt;

&lt;p&gt;Je fais un aparté un instant : le marché est, de mon point de vue, sursaturé de candidat·e·s issu·e·s de reconversions courtes. &lt;strong&gt;Des
écoles vous font croire qu’avec une formation de trois mois, vous
trouverez un CDI payé 50 K€. C’est faux, et ça le sera toujours.&lt;/strong&gt; Je ne dis pas que ces formations sont inutiles, mais elles ne
sont pas suffisantes pour vous faire décrocher un CDI.&lt;/p&gt;

&lt;h2 id=&quot;faites-les-calculs-vous-mêmes&quot;&gt;Faites les calculs vous-mêmes&lt;/h2&gt;

&lt;p&gt;Un truc un peu idiot mais impactant, c’est de faire les calculs vous-mêmes. Par exemple, indiquez
le nombre d’années passées dans une entreprise plutôt que simplement mettre les dates de début et de fin.&lt;/p&gt;

&lt;p&gt;Par exemple, au lieu de mettre :&lt;/p&gt;

&lt;div class=&quot;border bg-rose-50 border-rose-100 text-rose-800 px-4 py-3 rounded relative&quot;&gt;
&lt;div class=&quot;text-rose-800 text-center text-sm pb-4&quot;&gt;Version améliorable&lt;/div&gt;
&lt;b&gt;Développeur front-end chez MonEntreprise (2020 - 2023)&lt;/b&gt;
&lt;/div&gt;

&lt;p&gt;Essayez de mettre :&lt;/p&gt;

&lt;div class=&quot;border bg-lime-50 border-lime-100 text-lime-800 px-4 py-3 rounded relative&quot;&gt;
&lt;div class=&quot;text-lime-800 text-center text-sm pb-4&quot;&gt;Version améliorée&lt;/div&gt;
&lt;b&gt;Développeur front-end chez MonEntreprise (3 ans)&lt;/b&gt;
&lt;/div&gt;

&lt;p&gt;Excepté si les dates sont importantes, évitez d’ajouter trop de charge mentale au recruteur, qui, je le rappelle, ne
passera pas plus de quelques secondes sur votre CV.&lt;/p&gt;

&lt;h2 id=&quot;ne-mettez-pas-de-lettre-de-motivation-sauf-si&quot;&gt;Ne mettez pas de lettre de motivation, sauf si&lt;/h2&gt;

&lt;p&gt;Que pensez-vous de cette lettre de motivation ?&lt;/p&gt;

&lt;div class=&quot;border bg-rose-50 border-rose-100 text-rose-800 px-4 py-3 rounded relative&quot;&gt;
&lt;div class=&quot;text-rose-800 text-center text-sm pb-4&quot;&gt;Version améliorable&lt;/div&gt;
&lt;div&gt;&lt;b&gt;Lettre de motivation&lt;/b&gt;&lt;/div&gt;
&lt;p&gt;Bonjour,&lt;/p&gt;
&lt;p&gt;Je suis très intéressé·e par le poste de développeur·se front-end chez MonEntreprise. Je serai ravie de mettre mes compétences au service de votre entreprise.&lt;/p&gt;
&lt;p&gt;Forte de mes 5 ans d&apos;expérience en développement front-end, je suis convaincue que je pourrai apporter une réelle valeur ajoutée à votre équipe.&lt;/p&gt;
&lt;p&gt;Je vous remercie de l&apos;attention que vous porterez à ma candidature.&lt;/p&gt;
&lt;p&gt;Cordialement,&lt;/p&gt;
&lt;p&gt;Jeanne Dupont&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;C’est une perte de temps&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A force de lire des lettres de motivation, on finit par se rendre compte que c’est un simple copier-coller de la même lettre. &lt;strong&gt;Je pense
que la très large majorité des candidats·es copie-colle le premier exemple de lettre trouvé sur Google, et le personnalise un peu.&lt;/strong&gt; Autant vous dire que ça ne sert à rien.&lt;/p&gt;

&lt;p&gt;Par contre, j’ai souvent bien accroché avec des lettres de motivation qui étaient bien écrites, et qui montraient que la personne avait pris le temps de se renseigner sur l’entreprise, et sur le poste.&lt;/p&gt;

&lt;p&gt;A l’heure de ChatGPT, je pense que ça vaut le coup de prendre 5 minutes pour coller l’annonce dans chatGPT, coller votre CV, et lui demander de vous faire une lettre de motivation “personnalisée” pour le poste.&lt;/p&gt;

&lt;p&gt;Si votre lettre de motivation n’est pas personnalisée, ne mettez pas de lettre de motivation, et mettez plutôt un lien vers votre profil LinkedIn, ou vers votre portfolio.&lt;/p&gt;

&lt;h2 id=&quot;personnalisez-votre-cv&quot;&gt;Personnalisez votre CV&lt;/h2&gt;

&lt;p&gt;C’est la partie pénible, mais c’est un passage obligé. &lt;strong&gt;Ne postulez pas avec le même CV pour toutes les annonces.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;C’est hyper long, hyper fastidieux, et je sais que c’est pas évident. Mais c’est un passage obligé si vous voulez vous démarquer.&lt;/p&gt;

&lt;p&gt;Parfois, il suffit de changer le résumé en haut de votre CV, ou de mettre en avant certaines expériences plutôt que d’autres.&lt;/p&gt;

&lt;p&gt;Par exemple, pour une annonce “Développeur·se front-end React”, mettez en avant la compétence React, et mettez le en avant dans votre résumé de haut de page.&lt;/p&gt;

&lt;div class=&quot;border bg-lime-50 border-lime-100 text-lime-800 px-4 py-3 rounded relative&quot;&gt;
&lt;div class=&quot;text-lime-800 text-center text-sm pb-4&quot;&gt;Version suggérée&lt;/div&gt;
&lt;div&gt;&lt;b&gt;Développeuse front-end avec 5 ans d&apos;expérience, ayant une forte appétence en React, je suis passionnée par l&apos;accessibilité et l&apos;optimisation des performances.
Je recherche un poste dans une entreprise SaaS qui met l&apos;accent sur la qualité du code et l&apos;expérience utilisateur.&lt;/b&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Ne mentez pas bien sûr, mais essayez de mettre en avant les compétences qui sont demandées dans l’annonce.&lt;/p&gt;

&lt;h2 id=&quot;ensuite-&quot;&gt;Ensuite ?&lt;/h2&gt;

&lt;p&gt;Je pourrai probablement continuer, mais je crois qu’en respectant déjà ces quelques conseils, vous aurez un CV qui se démarquera plus facilement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Je n’ai pas tellement l’habitude d’aborder ce genre de problématiques sur mon blog, alors 
dites moi si ce genre de sujets vous intéresse&lt;/strong&gt;. Je pourrai également, par exemple, faire un retour sur les entretiens, 
si jamais ça peut intéresser du monde.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;N’hésitez pas également à partager vos propres conseils ici, je suis sûr que ça peut aider du monde.&lt;/strong&gt;&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>PHP Ecosystem Deep Dive: The Code Quality Landscape</title>
   <link href="https://blog.lepine.pro/en/php-ecosystem-deep-dive-code-quality-landscape/"/>
   <updated>2025-03-05T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/en/php-ecosystem-deep-dive-code-quality-landscape</id>
   <content type="html"> &lt;!-- Chart.js --&gt;
 &lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.min.js&quot;&gt;&lt;/script&gt;
 &lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/heatmap.js/2.0.2/heatmap.min.js&quot;&gt;&lt;/script&gt;


 &lt;div class=&quot;rounded-lg mb-8 mt-8&quot;&gt;
    &lt;h3 class=&quot;text-xl font-bold mb-3&quot;&gt;A quick word about the approach&lt;/h3&gt;
    
    &lt;div class=&quot;flex justify-between align-middle gap-8&quot;&gt;
      &lt;div&gt;
        &lt;p class=&quot;mb-3&quot;&gt;
          Hi! I&apos;m Jean-François Lépine, and I&apos;ve been interested in software quality for quite a few years now. I created PhpMetrics, then Ast-Metrics, and have helped many companies in industrializing their development processes.
        &lt;/p&gt;
        &lt;p class=&quot;mb-3&quot;&gt;I wanted to see the big picture of PHP code in the wild, so I went all in. 
          
          I downloaded the entire PHP ecosystem—a whopping &lt;span class=&quot;font-bold text-indigo-600&quot;&gt;236 GB &lt;/span&gt;of Git repositories containing a total of &lt;span class=&quot;font-bold text-indigo-600&quot;&gt;2.3 billion lines of code&lt;/span&gt;.
    
        &lt;p class=&quot;mb-3&quot;&gt;The analysis script ran for three full days, processing every single file. I built it in Go for raw performance and precise CPU control - PHP analysis at this scale needs serious optimization.&lt;/p&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;


  &lt;section class=&quot; mt-48&quot;&gt;
    &lt;h2 class=&quot;text-4xl mt-8 font-bold tracking-tight text-zinc-800 sm:text-5xl dark:text-zinc-100&quot;&gt;
      TL;DR - Too Long? Didn’t Read? Here’s the Short Version!
    &lt;/h2&gt;

    &lt;p class=&quot;text-gray-600 text-lg mt-4 mb-4&quot;&gt;
      PHP open-source is modern, modular, and maintainable. But beware of over-reliance on a few core packages and under-maintained projects.
    &lt;/p&gt;
    

      &lt;div &gt;
        &lt;div class=&quot;font-bold&quot;&gt;
          Modern PHP is modular and concise
        &lt;/div&gt;
        &lt;ul class=&quot;ml-8 list-disc list-inside&quot;&gt;
          &lt;li&gt;36% of files have fewer than 100 lines of code&lt;/li&gt;
          &lt;li&gt;Heavy use of interfaces makes the ecosystem highly modular.&lt;/li&gt;
        &lt;/ul&gt;

        &lt;div class=&quot;font-bold mt-4&quot;&gt;Complexity is mostly under control, but…&lt;/div&gt;
        &lt;ul class=&quot;ml-8 list-disc list-inside&quot;&gt;
          &lt;li&gt;76.5% of files have low cyclomatic complexity (0-10).&lt;/li&gt;
          &lt;li&gt;However, 23.5% of the code is complex, posing maintainability challenge&lt;/li&gt;
        &lt;/ul&gt;

        &lt;div class=&quot;font-bold mt-4&quot;&gt;Extreme dependency on a small set of packages&lt;/div&gt;
        &lt;ul class=&quot;ml-8 list-disc list-inside&quot;&gt;
          &lt;li&gt;Just 136 packages (0.05%) account for 50% of all downloads.&lt;/li&gt;
          &lt;li&gt;These core libraries are well-maintained, but they create strong dependency risks.&lt;/li&gt;
        &lt;/ul&gt;

        &lt;div class=&quot;font-bold mt-4&quot;&gt;The &quot;shadow infrastructure&quot; problem&lt;/div&gt;
        &lt;ul class=&quot;ml-8 list-disc list-inside&quot;&gt;
          &lt;li&gt;Some highly used packages receive very little recognition (few GitHub stars).&lt;/li&gt;
          &lt;li&gt;Critical dependencies may be maintained by very small teams.&lt;/li&gt;
        &lt;/ul&gt;

        &lt;div class=&quot;font-bold mt-4&quot;&gt;The PHP ecosystem is healthy but aging&lt;/div&gt;
        &lt;ul class=&quot;ml-8 list-disc list-inside&quot;&gt;
          &lt;li&gt;70% of packages show minimal activity.&lt;/li&gt;
          &lt;li&gt;23% appear abandoned, making dependency audits essential.&lt;/li&gt;
        &lt;/ul&gt;

      &lt;/div&gt;  
    
    &lt;/section&gt;  

  &lt;section class=&quot; mt-48&quot;&gt;
    &lt;h2 class=&quot;text-4xl mt-8 font-bold tracking-tight text-zinc-800 sm:text-5xl dark:text-zinc-100&quot;&gt;X-Ray of the PHP OpenSource Ecosystem&lt;/h2&gt;


    &lt;div class=&quot;mt-6 mb-6 bg-yellow-50 p-5 rounded-lg border-l-4 border-yellow-500&quot;&gt;
      &lt;h4 class=&quot;font-bold text-lg mb-2&quot;&gt;Open Source vs Enterprise Context:&lt;/h4&gt;
      &lt;p&gt;It&apos;s important to note that these metrics come from open-source projects, which differ significantly from typical enterprise codebases. Open-source libraries often prioritize extensibility and abstraction (explaining the high interface-to-class ratio), while enterprise projects may focus more on business logic implementation and specific use cases. When benchmarking your own codebase, consider whether your context aligns more with library design patterns or application-specific implementations.&lt;/p&gt;
    &lt;/div&gt;

    &lt;div class=&quot;bg-white rounded-lg shadow p-6&quot;&gt;
      &lt;p class=&quot;mb-5 text-lg&quot;&gt;

        I analyzed &lt;span class=&quot;font-bold text-indigo-600&quot;&gt;11.1 million&lt;/span&gt; PHP files, 
        totaling over &lt;span class=&quot;font-bold text-indigo-600&quot;&gt;707.6 million lines&lt;/span&gt; of PHP code—part of a 
        staggering &lt;span class=&quot;font-bold text-indigo-600&quot;&gt;2.3 billion lines of code&lt;/span&gt; across all languages in the Packagist ecosystem. 
        
        These metrics offer a real-world snapshot of how PHP is structured in the wild, beyond theoretical best practices.&quot;
      &lt;/p&gt;
      
      &lt;div class=&quot;grid grid-cols-1 md:grid-cols-2 gap-8 mb-8&quot;&gt;
        &lt;div class=&quot;border-l-4 border-indigo-500 pl-5 py-2&quot;&gt;
          &lt;h3 class=&quot;font-bold text-xl mb-3 text-gray-800&quot;&gt;File Size Distribution&lt;/h3&gt;
          &lt;p class=&quot;mb-3&quot;&gt;An overwhelming &lt;span class=&quot;font-bold&quot; &gt;36%&lt;/span&gt; 
            of files contain fewer than 100 logical lines of code, with a median of just &lt;span class=&quot;font-bold&quot;&gt;&lt;span &gt;4.0&lt;/span&gt; lines&lt;/span&gt;.&lt;/p&gt;
          &lt;p class=&quot;text-gray-600&quot;&gt;This reveals a strong ecosystem-wide preference for small, focused implementations - much smaller than most style guides would suggest as maximums. It also reflects the massive use of interfaces in the ecosystem, with 
            &lt;span class=&quot;font-bold&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

3.7M

interfaces&lt;/span&gt; compared to 
            &lt;span class=&quot;font-bold&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.4M

classes&lt;/span&gt; - many of these interfaces containing no logical code, only method signatures.&lt;/p&gt;
        &lt;/div&gt;
        
        &lt;div class=&quot;border-l-4 border-indigo-500 pl-5 py-2&quot;&gt;
          &lt;h3 class=&quot;font-bold text-xl mb-3 text-gray-800&quot;&gt;Cyclomatic Complexity&lt;/h3&gt;
          &lt;p class=&quot;mb-3&quot;&gt;&lt;span class=&quot;font-bold&quot;&gt;76.5%&lt;/span&gt; of files maintain low complexity (0-10), while &lt;span class=&quot;font-bold&quot;&gt;23.5%&lt;/span&gt; venture into higher complexity territory.&lt;/p&gt;
          &lt;p class=&quot;text-gray-600&quot;&gt;Even with a focus on small files, nearly a quarter of the codebase still shows significant complexity – highlighting areas where maintainability might be challenging.&lt;/p&gt;
        &lt;/div&gt;

      &lt;/div&gt;
      
    &lt;/div&gt;
  &lt;/section&gt;
  &lt;!-- Overview Stats --&gt;
  &lt;section class=&quot;mt-4&quot;&gt;
    &lt;div class=&quot;grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6&quot;&gt;
      &lt;!-- Total Packages --&gt;
      &lt;div class=&quot;bg-white rounded-lg shadow p-6&quot;&gt;
        &lt;div class=&quot;flex items-center justify-between mb-4&quot;&gt;
          &lt;h3 class=&quot;text-lg font-medium text-gray-700&quot;&gt;Analyzed Packages&lt;/h3&gt;
          &lt;span class=&quot;text-indigo-600 bg-indigo-100 p-2 rounded-full&quot;&gt;
            &lt;i class=&quot;fas fa-box-open&quot;&gt;&lt;/i&gt;
          &lt;/span&gt;
        &lt;/div&gt;
        &lt;p class=&quot;text-3xl font-bold mb-2&quot; &gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

411.6K

&lt;/p&gt;
        &lt;p class=&quot;text-sm text-gray-500&quot;&gt;PHP packages from PHP ecosystem&lt;/p&gt;
      &lt;/div&gt;
      
      &lt;!-- Total Classes --&gt;
      &lt;div class=&quot;bg-white rounded-lg shadow p-6&quot;&gt;
        &lt;div class=&quot;flex items-center justify-between mb-4&quot;&gt;
          &lt;h3 class=&quot;text-lg font-medium text-gray-700&quot;&gt;Files&lt;/h3&gt;
          &lt;span class=&quot;text-green-600 bg-green-100 p-2 rounded-full&quot;&gt;
            &lt;i class=&quot;fas fa-cubes&quot;&gt;&lt;/i&gt;
          &lt;/span&gt;
        &lt;/div&gt;
        &lt;p class=&quot;text-3xl font-bold mb-2&quot; &gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.1M

&lt;/p&gt;
        &lt;p class=&quot;text-sm text-gray-500&quot;&gt;Detected PHP files&lt;/p&gt;
      &lt;/div&gt;
      
      &lt;!-- Total Methods --&gt;
      &lt;div class=&quot;bg-white rounded-lg shadow p-6&quot;&gt;
        &lt;div class=&quot;flex items-center justify-between mb-4&quot;&gt;
          &lt;h3 class=&quot;text-lg font-medium text-gray-700&quot;&gt;Methods&lt;/h3&gt;
          &lt;span class=&quot;text-blue-600 bg-blue-100 p-2 rounded-full&quot;&gt;
            &lt;i class=&quot;fas fa-code&quot;&gt;&lt;/i&gt;
          &lt;/span&gt;
        &lt;/div&gt;
        &lt;p class=&quot;text-3xl font-bold mb-2&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

53.4M

&lt;/p&gt;
        &lt;p class=&quot;text-sm text-gray-500&quot;&gt;Analyzed PHP methods&lt;/p&gt;
      &lt;/div&gt;
      
      &lt;!-- Total LOC --&gt;
      &lt;div class=&quot;bg-white rounded-lg shadow p-6&quot;&gt;
        &lt;div class=&quot;flex items-center justify-between mb-4&quot;&gt;
          &lt;h3 class=&quot;text-lg font-medium text-gray-700&quot;&gt;Lines of Code&lt;/h3&gt;
          &lt;span class=&quot;text-orange-600 bg-orange-100 p-2 rounded-full&quot;&gt;
            &lt;i class=&quot;fas fa-file-code&quot;&gt;&lt;/i&gt;
          &lt;/span&gt;
        &lt;/div&gt;
        &lt;p class=&quot;text-3xl font-bold mb-2&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

707.6M

&lt;/p&gt;
        &lt;div class=&quot;text-sm text-gray-500&quot;&gt;
          Total lines of code
          &lt;div class=&quot;text-gray-400&quot;&gt;Including &lt;span class=&quot;font-bold &quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

362.9M

&lt;/span&gt; logical lines of code&lt;/span&gt;
          &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/section&gt;
  
  
  &lt;!-- Section de distribution des métriques --&gt;
  &lt;section class=&quot;mt-4 grid grid-cols-1 lg:grid-cols-2 gap-8 mb-12&quot;&gt;
    &lt;!-- Distribution des lignes de code --&gt;
    &lt;div class=&quot;bg-white rounded-lg shadow p-6&quot;&gt;
      &lt;h3 class=&quot;text-xl font-bold mb-4 text-gray-700&quot;&gt;Logical lines of code distribution&lt;/h3&gt; 
      &lt;div class=&quot;h-64&quot;&gt;
        &lt;canvas id=&quot;loc-distribution-chart&quot;&gt;&lt;/canvas&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;!-- Distribution de la complexité cyclomatique --&gt;
    &lt;div class=&quot;bg-white rounded-lg shadow p-6&quot;&gt;
      &lt;h3 class=&quot;text-xl font-bold mb-4 text-gray-700&quot;&gt;Cyclomatic complexity distribution&lt;/h3&gt;
      &lt;div class=&quot;h-64&quot;&gt;
        &lt;canvas id=&quot;cyclomatic-distribution-chart&quot;&gt;&lt;/canvas&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/section&gt;



  &lt;section class=&quot; mt-48&quot;&gt;
    &lt;h2 class=&quot;text-4xl mt-8 font-bold tracking-tight text-zinc-800 sm:text-5xl dark:text-zinc-100&quot;&gt;Download Concentration: The Foundation of PHP&lt;/h2&gt;

    &lt;p class=&quot;text-gray-600 text-lg mt-4 mb-4&quot;&gt;
      One of the most revealing aspects of the PHP ecosystem is the extreme concentration of downloads.
    &lt;/p&gt;

    &lt;div class=&quot;bg-white rounded-lg shadow p-6 mt-4&quot;&gt;
      &lt;p class=&quot;text-gray-800 mb-4&quot;&gt;
        The data shows that &lt;span class=&quot;font-bold text-indigo-600&quot;&gt;just 136 packages 
          (0.05 % of all packages)&lt;/span&gt; account for more than half of all monthly downloads in the entire ecosystem.
      &lt;/p&gt;
      
      &lt;p class=&quot;text-gray-800 mb-4&quot;&gt;
        This concentration reflects several important dynamics in the PHP world:
      &lt;/p&gt;
      
      &lt;div class=&quot;grid grid-cols-1 md:grid-cols-2 gap-6 my-6&quot;&gt;
        &lt;div class=&quot;border-l-4 border-green-500 pl-4 py-2&quot;&gt;
          &lt;h3 class=&quot;font-bold text-lg mb-2&quot;&gt;Consolidated Foundation&lt;/h3&gt;
          &lt;p class=&quot;text-gray-700&quot;&gt;The PHP ecosystem has matured to rely on a relatively small set of core libraries that provide essential functionality. This stable foundation means developers don&apos;t need to constantly evaluate competing implementations for basic needs.&lt;/p&gt;
        &lt;/div&gt;
        
        &lt;div class=&quot;border-l-4 border-amber-500 pl-4 py-2&quot;&gt;
          &lt;h3 class=&quot;font-bold text-lg mb-2&quot;&gt;Dependency Chain Effects&lt;/h3&gt;
          &lt;p class=&quot;text-gray-700&quot;&gt;Many top packages are fundamental building blocks that get pulled in as transitive dependencies across thousands of projects. This cascading effect magnifies download counts for these foundational packages.&lt;/p&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;div class=&quot;rounded-lg shadow p-6 mt-4 bg-green-100 text-green-800&quot;&gt;
      &lt;h3 class=&quot;font-bold text-lg mb-3&quot;&gt;🎉 The Good News &lt;/h3&gt;
      &lt;p class=&quot;text-gray-800 mb-3&quot;&gt;
        The encouraging finding is that these high-impact packages are overwhelmingly well-maintained and healthy:
      &lt;/p&gt;
      &lt;ul class=&quot;space-y-2&quot;&gt;
        &lt;li class=&quot;flex items-start&quot;&gt;
          &lt;span class=&quot;text-green-500 mr-2&quot;&gt;✓&lt;/span&gt;
          &lt;span&gt;Nearly all top packages have active contributor communities&lt;/span&gt;
        &lt;/li&gt;
        &lt;li class=&quot;flex items-start&quot;&gt;
          &lt;span class=&quot;text-green-500 mr-2&quot;&gt;✓&lt;/span&gt;
          &lt;span&gt;Most have robust test suites and CI/CD pipelines in place&lt;/span&gt;
        &lt;/li&gt;
        &lt;li class=&quot;flex items-start&quot;&gt;
          &lt;span class=&quot;text-green-500 mr-2&quot;&gt;✓&lt;/span&gt;
          &lt;span&gt;Many are backed by companies or foundations ensuring long-term stability&lt;/span&gt;
        &lt;/li&gt;
      &lt;/ul&gt;
    &lt;/div&gt;
  &lt;/section&gt;

  &lt;!-- Section pour la concentration des téléchargements (Download Dominance) --&gt;
&lt;section class=&quot;bg-white rounded-lg shadow p-6 mb-12 mt-4&quot;&gt;
&lt;h3 class=&quot;text-xl font-bold mb-4 text-gray-700&quot;&gt;Download Dominance&lt;/h3&gt;
&lt;p class=&quot;text-gray-600 mb-4&quot;&gt;Packages that collectively represent 50% or more of monthly downloads&lt;/p&gt;

&lt;div class=&quot;grid grid-cols-1 md:grid-cols-3 gap-4 mb-6&quot;&gt;
&lt;div class=&quot; rounded p-4 bg-indigo-50&quot;&gt;
  &lt;p class=&quot;text-sm text-gray-500&quot;&gt;Number of dominant packages&lt;/p&gt;
  &lt;p class=&quot;font-bold text-2xl text-indigo-600&quot; &gt; 136&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot; rounded p-4 bg-indigo-50&quot;&gt;
  &lt;p class=&quot;text-sm text-gray-500&quot;&gt;% of total downloads&lt;/p&gt;
  &lt;p class=&quot;font-bold text-2xl text-indigo-600&quot; &gt;0.05&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot; rounded p-4 bg-indigo-50&quot;&gt;
  &lt;p class=&quot;text-sm text-gray-500&quot;&gt;Monthly total downloads&lt;/p&gt;
  &lt;p class=&quot;font-bold text-2xl text-indigo-600&quot; &gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

2808.6M

&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;h-64 mb-4&quot;&gt;
&lt;canvas id=&quot;download-dominance-chart&quot;&gt;&lt;/canvas&gt;
&lt;/div&gt;

&lt;div class=&quot;overflow-x-auto mt-6&quot; style=&quot;height:500px; overflow:auto;&quot;&gt;
&lt;table class=&quot;min-w-full divide-y divide-gray-200&quot;&gt;
  &lt;thead class=&quot;bg-gray-50&quot;&gt;
    &lt;tr&gt;
      &lt;th class=&quot;px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider&quot;&gt;Package&lt;/th&gt;
      &lt;th class=&quot;px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider&quot;&gt;Monthly downloads&lt;/th&gt;
      &lt;th class=&quot;px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider&quot;&gt;%&lt;/th&gt;
      &lt;th class=&quot;px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider&quot;&gt;Stars&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody class=&quot;bg-white divide-y divide-gray-200&quot;&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/deprecation-contracts&quot; target=_&quot;blank&quot;&gt;symfony/deprecation-contracts&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

15.7M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.55&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 2053.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/polyfill-mbstring&quot; target=_&quot;blank&quot;&gt;symfony/polyfill-mbstring&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

15.5M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.55&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 7847.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/psr/log&quot; target=_&quot;blank&quot;&gt;psr/log&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

15.2M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.55&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 10421.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/psr/container&quot; target=_&quot;blank&quot;&gt;psr/container&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

15.0M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.55&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 9981.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/console&quot; target=_&quot;blank&quot;&gt;symfony/console&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

14.9M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.55&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 9785.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/service-contracts&quot; target=_&quot;blank&quot;&gt;symfony/service-contracts&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

14.7M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.5&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 2595.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/polyfill-intl-normalizer&quot; target=_&quot;blank&quot;&gt;symfony/polyfill-intl-normalizer&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

14.6M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.5&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 2026.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/psr/http-message&quot; target=_&quot;blank&quot;&gt;psr/http-message&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

14.5M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.5&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 6998.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/guzzlehttp/psr7&quot; target=_&quot;blank&quot;&gt;guzzlehttp/psr7&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

14.2M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.5&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 7905.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/guzzlehttp/promises&quot; target=_&quot;blank&quot;&gt;guzzlehttp/promises&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

14.1M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.5&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 7645.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/process&quot; target=_&quot;blank&quot;&gt;symfony/process&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

14.1M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.5&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 7453.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/polyfill-ctype&quot; target=_&quot;blank&quot;&gt;symfony/polyfill-ctype&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

14.1M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.5&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 4056.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/finder&quot; target=_&quot;blank&quot;&gt;symfony/finder&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

14.0M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.5&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 8436.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/guzzlehttp/guzzle&quot; target=_&quot;blank&quot;&gt;guzzlehttp/guzzle&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

14.0M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.5&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 23313.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/string&quot; target=_&quot;blank&quot;&gt;symfony/string&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

13.9M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.5&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 1741.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/ralouphie/getallheaders&quot; target=_&quot;blank&quot;&gt;ralouphie/getallheaders&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

13.9M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.5&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 3778.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/psr/http-factory&quot; target=_&quot;blank&quot;&gt;psr/http-factory&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

13.6M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.5&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 1827.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/nikic/php-parser&quot; target=_&quot;blank&quot;&gt;nikic/php-parser&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

13.6M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.5&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 17179.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/polyfill-php80&quot; target=_&quot;blank&quot;&gt;symfony/polyfill-php80&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

13.6M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.5&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 1731.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/polyfill-intl-grapheme&quot; target=_&quot;blank&quot;&gt;symfony/polyfill-intl-grapheme&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

13.6M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.5&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 1689.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/psr/http-client&quot; target=_&quot;blank&quot;&gt;psr/http-client&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

13.3M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 1685.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/event-dispatcher&quot; target=_&quot;blank&quot;&gt;symfony/event-dispatcher&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

13.3M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 8534.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/monolog/monolog&quot; target=_&quot;blank&quot;&gt;monolog/monolog&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

13.1M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 21132.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/event-dispatcher-contracts&quot; target=_&quot;blank&quot;&gt;symfony/event-dispatcher-contracts&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

13.1M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 3394.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/var-dumper&quot; target=_&quot;blank&quot;&gt;symfony/var-dumper&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

13.0M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 7426.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/myclabs/deep-copy&quot; target=_&quot;blank&quot;&gt;myclabs/deep-copy&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.8M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 8807.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/phpunit/phpunit&quot; target=_&quot;blank&quot;&gt;phpunit/phpunit&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.7M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 19791.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/http-foundation&quot; target=_&quot;blank&quot;&gt;symfony/http-foundation&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.7M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 8652.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/sebastian/diff&quot; target=_&quot;blank&quot;&gt;sebastian/diff&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.6M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 7600.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/doctrine/lexer&quot; target=_&quot;blank&quot;&gt;doctrine/lexer&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.6M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 11102.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/translation-contracts&quot; target=_&quot;blank&quot;&gt;symfony/translation-contracts&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.4M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 2576.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/phpunit/php-code-coverage&quot; target=_&quot;blank&quot;&gt;phpunit/php-code-coverage&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.3M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 8863.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/polyfill-intl-idn&quot; target=_&quot;blank&quot;&gt;symfony/polyfill-intl-idn&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.3M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 3339.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/sebastian/comparator&quot; target=_&quot;blank&quot;&gt;sebastian/comparator&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.3M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 7024.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/sebastian/exporter&quot; target=_&quot;blank&quot;&gt;sebastian/exporter&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.3M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 6801.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/psr/event-dispatcher&quot; target=_&quot;blank&quot;&gt;psr/event-dispatcher&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.2M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 2209.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/phpunit/php-file-iterator&quot; target=_&quot;blank&quot;&gt;phpunit/php-file-iterator&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.2M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 7444.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/sebastian/recursion-context&quot; target=_&quot;blank&quot;&gt;sebastian/recursion-context&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.2M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 6556.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/sebastian/global-state&quot; target=_&quot;blank&quot;&gt;sebastian/global-state&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.1M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 6584.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/sebastian/version&quot; target=_&quot;blank&quot;&gt;sebastian/version&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.1M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 6557.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/phpunit/php-timer&quot; target=_&quot;blank&quot;&gt;phpunit/php-timer&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.1M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 7684.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/sebastian/environment&quot; target=_&quot;blank&quot;&gt;sebastian/environment&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.1M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 6752.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/webmozart/assert&quot; target=_&quot;blank&quot;&gt;webmozart/assert&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.1M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 7588.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/phpunit/php-text-template&quot; target=_&quot;blank&quot;&gt;phpunit/php-text-template&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.1M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 7389.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/http-kernel&quot; target=_&quot;blank&quot;&gt;symfony/http-kernel&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.0M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 8126.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/sebastian/object-enumerator&quot; target=_&quot;blank&quot;&gt;sebastian/object-enumerator&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.0M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 6507.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/sebastian/code-unit-reverse-lookup&quot; target=_&quot;blank&quot;&gt;sebastian/code-unit-reverse-lookup&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.9M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 6690.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/yaml&quot; target=_&quot;blank&quot;&gt;symfony/yaml&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.9M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 3826.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/error-handler&quot; target=_&quot;blank&quot;&gt;symfony/error-handler&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.9M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 2623.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/theseer/tokenizer&quot; target=_&quot;blank&quot;&gt;theseer/tokenizer&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.9M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 5177.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/phar-io/manifest&quot; target=_&quot;blank&quot;&gt;phar-io/manifest&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.9M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 7431.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/translation&quot; target=_&quot;blank&quot;&gt;symfony/translation&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.9M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 6620.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/sebastian/object-reflector&quot; target=_&quot;blank&quot;&gt;sebastian/object-reflector&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.8M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 6257.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/mime&quot; target=_&quot;blank&quot;&gt;symfony/mime&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.8M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 2794.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/phar-io/version&quot; target=_&quot;blank&quot;&gt;phar-io/version&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.8M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 7429.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/routing&quot; target=_&quot;blank&quot;&gt;symfony/routing&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.8M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 7623.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/psr/simple-cache&quot; target=_&quot;blank&quot;&gt;psr/simple-cache&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.5M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 8104.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/sebastian/type&quot; target=_&quot;blank&quot;&gt;sebastian/type&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.5M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 1434.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/doctrine/inflector&quot; target=_&quot;blank&quot;&gt;doctrine/inflector&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.5M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 11290.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/psr/cache&quot; target=_&quot;blank&quot;&gt;psr/cache&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.2M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 5138.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/egulias/email-validator&quot; target=_&quot;blank&quot;&gt;egulias/email-validator&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.2M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 11517.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/sebastian/cli-parser&quot; target=_&quot;blank&quot;&gt;sebastian/cli-parser&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.1M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 1132.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/css-selector&quot; target=_&quot;blank&quot;&gt;symfony/css-selector&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.1M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 7440.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/sebastian/complexity&quot; target=_&quot;blank&quot;&gt;sebastian/complexity&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.1M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 1183.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/sebastian/lines-of-code&quot; target=_&quot;blank&quot;&gt;sebastian/lines-of-code&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.0M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 1101.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/phpunit/php-invoker&quot; target=_&quot;blank&quot;&gt;phpunit/php-invoker&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.0M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 1256.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/ramsey/uuid&quot; target=_&quot;blank&quot;&gt;ramsey/uuid&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.0M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 12515.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/sebastian/code-unit&quot; target=_&quot;blank&quot;&gt;sebastian/code-unit&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.0M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 1131.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/psr/clock&quot; target=_&quot;blank&quot;&gt;psr/clock&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

10.9M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 526.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/doctrine/deprecations&quot; target=_&quot;blank&quot;&gt;doctrine/deprecations&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

10.7M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 1707.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/brick/math&quot; target=_&quot;blank&quot;&gt;brick/math&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

10.6M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 1907.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/ramsey/collection&quot; target=_&quot;blank&quot;&gt;ramsey/collection&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

10.4M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.35&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 1153.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/nesbot/carbon&quot; target=_&quot;blank&quot;&gt;nesbot/carbon&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

10.1M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.35&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 48.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/polyfill-php83&quot; target=_&quot;blank&quot;&gt;symfony/polyfill-php83&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

9.8M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.35&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 357.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/league/flysystem&quot; target=_&quot;blank&quot;&gt;league/flysystem&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

9.7M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.35&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 13410.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/league/mime-type-detection&quot; target=_&quot;blank&quot;&gt;league/mime-type-detection&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

9.6M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.35&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 1300.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/mailer&quot; target=_&quot;blank&quot;&gt;symfony/mailer&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

9.6M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.35&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 1527.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/doctrine/instantiator&quot; target=_&quot;blank&quot;&gt;doctrine/instantiator&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

9.4M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.35&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 10975.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/filesystem&quot; target=_&quot;blank&quot;&gt;symfony/filesystem&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

9.2M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.35&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 4621.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/vlucas/phpdotenv&quot; target=_&quot;blank&quot;&gt;vlucas/phpdotenv&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

9.0M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 13265.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/phpoption/phpoption&quot; target=_&quot;blank&quot;&gt;phpoption/phpoption&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.9M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 2644.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/psy/psysh&quot; target=_&quot;blank&quot;&gt;psy/psysh&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.7M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 9767.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/dragonmantank/cron-expression&quot; target=_&quot;blank&quot;&gt;dragonmantank/cron-expression&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.6M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 4594.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/nette/utils&quot; target=_&quot;blank&quot;&gt;nette/utils&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.5M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 2034.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/carbonphp/carbon-doctrine-types&quot; target=_&quot;blank&quot;&gt;carbonphp/carbon-doctrine-types&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.4M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 156.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/tijsverkoyen/css-to-inline-styles&quot; target=_&quot;blank&quot;&gt;tijsverkoyen/css-to-inline-styles&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.4M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 5826.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/voku/portable-ascii&quot; target=_&quot;blank&quot;&gt;voku/portable-ascii&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.4M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 555.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/composer/semver&quot; target=_&quot;blank&quot;&gt;composer/semver&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.4M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 3195.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/fakerphp/faker&quot; target=_&quot;blank&quot;&gt;fakerphp/faker&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.3M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 3696.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/graham-campbell/result-type&quot; target=_&quot;blank&quot;&gt;graham-campbell/result-type&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.3M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 504.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/paragonie/random_compat&quot; target=_&quot;blank&quot;&gt;paragonie/random_compat&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.2M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 8178.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/laravel/serializable-closure&quot; target=_&quot;blank&quot;&gt;laravel/serializable-closure&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.2M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 550.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/dflydev/dot-access-data&quot; target=_&quot;blank&quot;&gt;dflydev/dot-access-data&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.2M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 666.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/options-resolver&quot; target=_&quot;blank&quot;&gt;symfony/options-resolver&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.1M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 3222.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/phpdocumentor/type-resolver&quot; target=_&quot;blank&quot;&gt;phpdocumentor/type-resolver&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.1M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 9161.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/phpstan/phpdoc-parser&quot; target=_&quot;blank&quot;&gt;phpstan/phpdoc-parser&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.0M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 1398.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/polyfill-php81&quot; target=_&quot;blank&quot;&gt;symfony/polyfill-php81&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.0M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 877.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/polyfill-uuid&quot; target=_&quot;blank&quot;&gt;symfony/polyfill-uuid&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.0M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 657.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/doctrine/dbal&quot; target=_&quot;blank&quot;&gt;doctrine/dbal&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.0M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 9554.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/phpdocumentor/reflection-common&quot; target=_&quot;blank&quot;&gt;phpdocumentor/reflection-common&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.9M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 9045.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/league/commonmark&quot; target=_&quot;blank&quot;&gt;league/commonmark&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.9M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 2807.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/uid&quot; target=_&quot;blank&quot;&gt;symfony/uid&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.9M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 568.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/laravel/framework&quot; target=_&quot;blank&quot;&gt;laravel/framework&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.9M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 33237.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/nette/schema&quot; target=_&quot;blank&quot;&gt;nette/schema&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.8M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 939.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/composer/pcre&quot; target=_&quot;blank&quot;&gt;composer/pcre&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.8M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 597.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/mockery/mockery&quot; target=_&quot;blank&quot;&gt;mockery/mockery&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.8M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 10661.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/doctrine/event-manager&quot; target=_&quot;blank&quot;&gt;doctrine/event-manager&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.8M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 5964.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/firebase/php-jwt&quot; target=_&quot;blank&quot;&gt;firebase/php-jwt&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.7M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 9526.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/hamcrest/hamcrest-php&quot; target=_&quot;blank&quot;&gt;hamcrest/hamcrest-php&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.7M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 6970.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/aws/aws-sdk-php&quot; target=_&quot;blank&quot;&gt;aws/aws-sdk-php&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.7M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 6082.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/var-exporter&quot; target=_&quot;blank&quot;&gt;symfony/var-exporter&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.7M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 2065.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/mtdowling/jmespath.php&quot; target=_&quot;blank&quot;&gt;mtdowling/jmespath.php&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.7M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 1954.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/sebastian/resource-operations&quot; target=_&quot;blank&quot;&gt;sebastian/resource-operations&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.5M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 6281.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/league/flysystem-local&quot; target=_&quot;blank&quot;&gt;league/flysystem-local&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.5M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 180.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/laravel/tinker&quot; target=_&quot;blank&quot;&gt;laravel/tinker&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.4M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 7363.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/phpdocumentor/reflection-docblock&quot; target=_&quot;blank&quot;&gt;phpdocumentor/reflection-docblock&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.4M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 9360.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/league/config&quot; target=_&quot;blank&quot;&gt;league/config&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.4M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 519.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/doctrine/cache&quot; target=_&quot;blank&quot;&gt;doctrine/cache&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.4M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 7868.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/paragonie/constant_time_encoding&quot; target=_&quot;blank&quot;&gt;paragonie/constant_time_encoding&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.1M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 836.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/phpseclib/phpseclib&quot; target=_&quot;blank&quot;&gt;phpseclib/phpseclib&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.1M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 5434.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/aws/aws-crt-php&quot; target=_&quot;blank&quot;&gt;aws/aws-crt-php&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.1M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 367.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/filp/whoops&quot; target=_&quot;blank&quot;&gt;filp/whoops&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.0M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 13218.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/phpstan/phpstan&quot; target=_&quot;blank&quot;&gt;phpstan/phpstan&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.0M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 13240.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/guzzlehttp/uri-template&quot; target=_&quot;blank&quot;&gt;guzzlehttp/uri-template&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.9M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 154.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/nunomaduro/termwind&quot; target=_&quot;blank&quot;&gt;nunomaduro/termwind&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.9M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 2377.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/config&quot; target=_&quot;blank&quot;&gt;symfony/config&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.7M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 4248.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/laravel/prompts&quot; target=_&quot;blank&quot;&gt;laravel/prompts&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.7M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 562.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/fruitcake/php-cors&quot; target=_&quot;blank&quot;&gt;fruitcake/php-cors&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.7M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 261.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/dependency-injection&quot; target=_&quot;blank&quot;&gt;symfony/dependency-injection&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.7M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 4130.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/http-client-contracts&quot; target=_&quot;blank&quot;&gt;symfony/http-client-contracts&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.6M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 1952.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/polyfill-php73&quot; target=_&quot;blank&quot;&gt;symfony/polyfill-php73&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.6M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 2399.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/clock&quot; target=_&quot;blank&quot;&gt;symfony/clock&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.5M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 353.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/league/uri-interfaces&quot; target=_&quot;blank&quot;&gt;league/uri-interfaces&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.5M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 482.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/cache&quot; target=_&quot;blank&quot;&gt;symfony/cache&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.5M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 4125.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/league/uri&quot; target=_&quot;blank&quot;&gt;league/uri&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.5M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 1060.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/doctrine/annotations&quot; target=_&quot;blank&quot;&gt;doctrine/annotations&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.4M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 6742.0&lt;/td&gt;
    &lt;/tr&gt;
    
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/section&gt;


&lt;!-- Section pour les packages sous-reconnus --&gt;
&lt;section class=&quot;mt-48&quot;&gt;
&lt;h2 class=&quot;text-4xl mt-8 font-bold tracking-tight text-zinc-800 sm:text-5xl dark:text-zinc-100&quot;&gt;
The Invisible Infrastructure: High-Impact, Low-Recognition Packages
&lt;/h2&gt;
&lt;p class=&quot;text-gray-600 text-lg mt-4 mb-4&quot;&gt;
While analyzing the relationship between GitHub stars and download counts, we discovered a fascinating subset of PHP packages: those with massive download numbers but surprisingly few stars. These packages form what might be called the &quot;invisible infrastructure&quot; of PHP—critically important components that operate silently beneath the surface of countless applications.
&lt;/p&gt;

&lt;div class=&quot;bg-white rounded-lg shadow p-6 mt-4&quot;&gt;
  
  
  &lt;p class=&quot;text-gray-600 mb-4&quot;&gt;
    This &quot;recognition gap&quot; has several important implications:
  &lt;/p&gt;

  &lt;ul class=&quot;space-y-2&quot;&gt;
    &lt;li class=&quot;flex items-start&quot;&gt;
      &lt;span class=&quot;text-red-500 mr-2&quot;&gt;⚠&lt;/span&gt;
      &lt;span class=&quot;text-red-700 mr-2&quot;&gt;Packages with high impact but low visibility might struggle to attract contributors&lt;/span&gt;
    &lt;/li&gt;
    &lt;li class=&quot;flex items-start&quot;&gt;
      &lt;span class=&quot;text-red-500 mr-2&quot;&gt;⚠&lt;/span&gt;
      &lt;span class=&quot;text-red-700 mr-2&quot;&gt;Critical dependencies may be maintained by small teams despite their outsized ecosystem importance&lt;/span&gt;
    &lt;/li&gt;
    &lt;li class=&quot;flex items-start&quot;&gt;
      &lt;span class=&quot;text-green-500 mr-2&quot;&gt;✓&lt;/span&gt;
      &lt;span class=&quot;text-green-700 mr-2&quot;&gt;However, many represent opportunities for developers looking to make meaningful contributions to the PHP ecosystem&lt;/span&gt;
    &lt;/li&gt;
    &lt;/ul&gt;
&lt;div class=&quot;h-64 mt-4 mb-4&quot;&gt;
  &lt;canvas id=&quot;underrated-packages-chart&quot;&gt;&lt;/canvas&gt;
&lt;/div&gt;

&lt;div class=&quot;overflow-x-auto mt-6&quot; style=&quot;height: 500px; overflow: scroll;&quot;&gt;
  &lt;table class=&quot;min-w-full divide-y divide-gray-200&quot;&gt;
    &lt;thead class=&quot;bg-gray-50&quot;&gt;
      &lt;tr&gt;
        &lt;th class=&quot;px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider&quot;&gt;Package&lt;/th&gt;
        &lt;th class=&quot;px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider&quot;&gt;Monthly download&lt;/th&gt;
        &lt;th class=&quot;px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider&quot;&gt;Stars&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody class=&quot;bg-white divide-y divide-gray-200&quot;&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/staabm/side-effects-detector&quot; target=_&quot;blank&quot;&gt;staabm/side-effects-detector&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

3.6M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;76.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/spatie/error-solutions&quot; target=_&quot;blank&quot;&gt;spatie/error-solutions&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

2.5M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;42.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/google/longrunning&quot; target=_&quot;blank&quot;&gt;google/longrunning&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

2.3M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;43.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/illuminate/macroable&quot; target=_&quot;blank&quot;&gt;illuminate/macroable&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.8M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;73.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/pestphp/pest-plugin&quot; target=_&quot;blank&quot;&gt;pestphp/pest-plugin&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.6M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;37.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/php-http/guzzle7-adapter&quot; target=_&quot;blank&quot;&gt;php-http/guzzle7-adapter&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.6M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;79.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/ta-tikoma/phpunit-architecture-test&quot; target=_&quot;blank&quot;&gt;ta-tikoma/phpunit-architecture-test&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.5M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;91.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/pestphp/pest-plugin-arch&quot; target=_&quot;blank&quot;&gt;pestphp/pest-plugin-arch&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.4M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;33.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/symfony/polyfill-php82&quot; target=_&quot;blank&quot;&gt;symfony/polyfill-php82&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.4M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;55.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/illuminate/conditionable&quot; target=_&quot;blank&quot;&gt;illuminate/conditionable&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.4M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;81.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/kelunik/certificate&quot; target=_&quot;blank&quot;&gt;kelunik/certificate&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.4M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;92.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/amphp/pipeline&quot; target=_&quot;blank&quot;&gt;amphp/pipeline&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.4M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;54.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/open-telemetry/api&quot; target=_&quot;blank&quot;&gt;open-telemetry/api&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.3M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;14.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/pear/pear_exception&quot; target=_&quot;blank&quot;&gt;pear/pear_exception&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.3M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;97.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/open-telemetry/context&quot; target=_&quot;blank&quot;&gt;open-telemetry/context&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.2M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;11.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/pear/pear-core-minimal&quot; target=_&quot;blank&quot;&gt;pear/pear-core-minimal&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.2M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;77.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/pear/console_getopt&quot; target=_&quot;blank&quot;&gt;pear/console_getopt&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.1M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;84.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/phpcsstandards/phpcsutils&quot; target=_&quot;blank&quot;&gt;phpcsstandards/phpcsutils&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.1M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;55.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/mpdf/psr-log-aware-trait&quot; target=_&quot;blank&quot;&gt;mpdf/psr-log-aware-trait&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.0M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;46.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/codeception/module-asserts&quot; target=_&quot;blank&quot;&gt;codeception/module-asserts&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.0M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;80.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/pear/archive_tar&quot; target=_&quot;blank&quot;&gt;pear/archive_tar&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.0M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;74.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/mpdf/psr-http-message-shim&quot; target=_&quot;blank&quot;&gt;mpdf/psr-http-message-shim&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

998.5K

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;33.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/consolidation/self-update&quot; target=_&quot;blank&quot;&gt;consolidation/self-update&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

960.2K

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;98.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/async-aws/core&quot; target=_&quot;blank&quot;&gt;async-aws/core&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

957.8K

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;85.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/consolidation/site-alias&quot; target=_&quot;blank&quot;&gt;consolidation/site-alias&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

917.2K

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;59.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/symfony/polyfill-php84&quot; target=_&quot;blank&quot;&gt;symfony/polyfill-php84&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

892.0K

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;20.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/consolidation/filter-via-dot-access-data&quot; target=_&quot;blank&quot;&gt;consolidation/filter-via-dot-access-data&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

880.8K

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;45.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/codeception/lib-innerbrowser&quot; target=_&quot;blank&quot;&gt;codeception/lib-innerbrowser&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

874.9K

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;83.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/consolidation/site-process&quot; target=_&quot;blank&quot;&gt;consolidation/site-process&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

866.7K

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;50.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/drupal/core-composer-scaffold&quot; target=_&quot;blank&quot;&gt;drupal/core-composer-scaffold&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

858.8K

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;49.0&lt;/td&gt;
      &lt;/tr&gt;
      
    &lt;/tbody&gt;
  &lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;

&lt;!-- Graphique à bulles de popularité --&gt;
&lt;section class=&quot;bg-white rounded-lg shadow p-6 mt-4&quot;&gt;
&lt;h3 class=&quot;text-xl font-bold mb-4 text-gray-700&quot;&gt;Popular packages&lt;/h3&gt;
&lt;p&gt;
In contrast, these packages are both very popular and widely used. They are the cornerstone of PHP.
&lt;/p&gt;
&lt;div class=&quot;h-80 mt-4&quot;&gt;
&lt;canvas id=&quot;popularity-bubble-chart&quot;&gt;&lt;/canvas&gt;
&lt;/div&gt;
&lt;p class=&quot;text-sm text-right text-xs text-gray-500 mt-4&quot;&gt;bubble size proportional to GitHub forks&lt;/p&gt;
&lt;/section&gt;



  &lt;!-- Additional Stats --&gt;
  &lt;section class=&quot;mb-12 mt-48&quot;&gt;
    &lt;h2 class=&quot;text-4xl mt-8 font-bold tracking-tight text-zinc-800 sm:text-5xl dark:text-zinc-100&quot;&gt;
      Packagist Ecosystem Health Status

    &lt;/h2&gt;

    &lt;p class=&quot;text-gray-600 text-lg mt-4 mb-4&quot;&gt;
      Analysis of PHP packages reveals significant health concerns in the Packagist ecosystem: 
      &lt;span class=&quot;font-bold text-indigo-600&quot;&gt;70%
       show minimal activity&lt;/span&gt; with few downloads and GitHub interactions, while 
       &lt;span class=&quot;font-bold text-indigo-600&quot;&gt;23% appear abandoned&lt;/span&gt; with no recent updates. With an average of just 60 stars per package (heavily skewed by popular outliers), these metrics highlight the typical &quot;long tail&quot; distribution in open source ecosystems. This data emphasizes the importance of carefully evaluating dependency health through metrics like bus factor, update frequency, and community engagement when integrating packages into production projects.
    &lt;/p&gt;


    &lt;div class=&quot;grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6&quot;&gt;
      &lt;!-- Empty Packages --&gt;
      &lt;div class=&quot;bg-white rounded-lg shadow p-6&quot;&gt;
        &lt;div class=&quot;flex items-center justify-between mb-4&quot;&gt;
          &lt;h3 class=&quot;text-lg font-medium text-gray-700&quot;&gt;Empty Packages&lt;/h3&gt;
          &lt;span class=&quot;text-red-600 bg-red-100 p-2 rounded-full&quot;&gt;
            &lt;i class=&quot;far fa-file&quot;&gt;&lt;/i&gt;
          &lt;/span&gt;
        &lt;/div&gt;
        &lt;p class=&quot;text-3xl font-bold mb-2&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

94.9K

&lt;/p&gt;
        &lt;p class=&quot;text-sm text-gray-500&quot;&gt;Abandonned packages&lt;/p&gt;
      &lt;/div&gt;
      
      &lt;!-- Zero Stars Packages --&gt;
      &lt;div class=&quot;bg-white rounded-lg shadow p-6&quot;&gt;
        &lt;div class=&quot;flex items-center justify-between mb-4&quot;&gt;
          &lt;h3 class=&quot;text-lg font-medium text-gray-700&quot;&gt;Shadow packages&lt;/h3&gt;
          &lt;span class=&quot;text-yellow-600 bg-yellow-100 p-2 rounded-full&quot;&gt;
            &lt;i class=&quot;far fa-star&quot;&gt;&lt;/i&gt;
          &lt;/span&gt;
        &lt;/div&gt;
        &lt;p class=&quot;text-3xl font-bold mb-2&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

288.5K

&lt;/p&gt;
        &lt;p class=&quot;text-sm text-gray-500&quot;&gt;Packages with few downloads, few GitHub stars or forks&lt;/p&gt;
      &lt;/div&gt;
      
      &lt;!-- Average Popularity --&gt;
      &lt;div class=&quot;bg-white rounded-lg shadow p-6&quot;&gt;
        &lt;div class=&quot;flex items-center justify-between mb-4&quot;&gt;
          &lt;h3 class=&quot;text-lg font-medium text-gray-700&quot;&gt;Average Popularity&lt;/h3&gt;
          &lt;span class=&quot;text-purple-600 bg-purple-100 p-2 rounded-full&quot;&gt;
            &lt;i class=&quot;fas fa-star&quot;&gt;&lt;/i&gt;
          &lt;/span&gt;
        &lt;/div&gt;
        &lt;p class=&quot;text-3xl font-bold mb-2&quot; &gt;60&lt;/p&gt;
        &lt;div class=&quot;text-sm text-gray-500&quot;&gt;
          Average stars per package
          &lt;div class=&quot;text-xs&quot;&gt;Excluding non-starred packages&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      
    &lt;/div&gt;


    &lt;div class=&quot;grid grid-cols-1 md:grid-cols-2 gap-6 mt-4&quot;&gt;
      &lt;div class=&quot;bg-white rounded-lg shadow p-6&quot;&gt;
        &lt;h3 class=&quot;text-xl font-bold mb-4 text-gray-700&quot;&gt;Top required packages for development&lt;/h3&gt;
        &lt;div style=&quot;max-height: 400px; overflow-y: auto;&quot;&gt;
          &lt;table class=&quot;w-full&quot;&gt;
            &lt;thead&gt;
              &lt;tr&gt;
                &lt;th class=&quot;text-left&quot;&gt;Package&lt;/th&gt;
                &lt;th class=&quot;text-right&quot;&gt;Number of packages using it&lt;/th&gt;
              &lt;/tr&gt;
            &lt;/thead&gt;
            &lt;tbody&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/phpunit/phpunit&quot; target=_&quot;blank&quot;&gt;phpunit/phpunit&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

201.5K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/orchestra/testbench&quot; target=_&quot;blank&quot;&gt;orchestra/testbench&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

33.9K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/squizlabs/php_codesniffer&quot; target=_&quot;blank&quot;&gt;squizlabs/php_codesniffer&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

33.8K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/phpstan/phpstan&quot; target=_&quot;blank&quot;&gt;phpstan/phpstan&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

32.3K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/mockery/mockery&quot; target=_&quot;blank&quot;&gt;mockery/mockery&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

31.8K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/friendsofphp/php-cs-fixer&quot; target=_&quot;blank&quot;&gt;friendsofphp/php-cs-fixer&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

24.4K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/pestphp/pest&quot; target=_&quot;blank&quot;&gt;pestphp/pest&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.5K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/vimeo/psalm&quot; target=_&quot;blank&quot;&gt;vimeo/psalm&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

10.5K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/phpstan/phpstan-phpunit&quot; target=_&quot;blank&quot;&gt;phpstan/phpstan-phpunit&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

9.7K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/laravel/pint&quot; target=_&quot;blank&quot;&gt;laravel/pint&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

9.6K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/phpstan/extension-installer&quot; target=_&quot;blank&quot;&gt;phpstan/extension-installer&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

9.0K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/symfony/var-dumper&quot; target=_&quot;blank&quot;&gt;symfony/var-dumper&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.9K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/nunomaduro/collision&quot; target=_&quot;blank&quot;&gt;nunomaduro/collision&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.4K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/roave/security-advisories&quot; target=_&quot;blank&quot;&gt;roave/security-advisories&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.2K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/phpmd/phpmd&quot; target=_&quot;blank&quot;&gt;phpmd/phpmd&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.7K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/symfony/phpunit-bridge&quot; target=_&quot;blank&quot;&gt;symfony/phpunit-bridge&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.5K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/phpstan/phpstan-deprecation-rules&quot; target=_&quot;blank&quot;&gt;phpstan/phpstan-deprecation-rules&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.4K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/pestphp/pest-plugin-laravel&quot; target=_&quot;blank&quot;&gt;pestphp/pest-plugin-laravel&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.2K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/satooshi/php-coveralls&quot; target=_&quot;blank&quot;&gt;satooshi/php-coveralls&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.0K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/php-coveralls/php-coveralls&quot; target=_&quot;blank&quot;&gt;php-coveralls/php-coveralls&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

5.8K

&lt;/td&gt;
              &lt;/tr&gt;
              
            &lt;/tbody&gt;
          &lt;/table&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class=&quot;bg-white rounded-lg shadow p-6&quot;&gt;
        &lt;h3 class=&quot;text-xl font-bold mb-4 text-gray-700&quot;&gt;Top required packages for production&lt;/h3&gt;
        &lt;div style=&quot;max-height: 400px; overflow-y: auto;&quot;&gt;
          &lt;table class=&quot;w-full&quot;&gt;
            &lt;thead&gt;
              &lt;tr&gt;
                &lt;th class=&quot;text-left&quot;&gt;Package&lt;/th&gt;
                &lt;th class=&quot;text-right&quot;&gt;Number of packages using it&lt;/th&gt;
              &lt;/tr&gt;
            &lt;/thead&gt;
            &lt;tbody&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/illuminate/support&quot; target=_&quot;blank&quot;&gt;illuminate/support&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

46.4K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/guzzlehttp/guzzle&quot; target=_&quot;blank&quot;&gt;guzzlehttp/guzzle&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

39.7K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/laravel/framework&quot; target=_&quot;blank&quot;&gt;laravel/framework&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

18.9K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/illuminate/contracts&quot; target=_&quot;blank&quot;&gt;illuminate/contracts&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

16.2K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/yiisoft/yii2&quot; target=_&quot;blank&quot;&gt;yiisoft/yii2&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

16.2K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/symfony/console&quot; target=_&quot;blank&quot;&gt;symfony/console&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

13.0K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/psr/log&quot; target=_&quot;blank&quot;&gt;psr/log&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.7K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/symfony/framework-bundle&quot; target=_&quot;blank&quot;&gt;symfony/framework-bundle&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.2K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/illuminate/database&quot; target=_&quot;blank&quot;&gt;illuminate/database&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

10.2K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/composer/installers&quot; target=_&quot;blank&quot;&gt;composer/installers&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

9.0K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/symfony/yaml&quot; target=_&quot;blank&quot;&gt;symfony/yaml&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.8K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/psr/http-message&quot; target=_&quot;blank&quot;&gt;psr/http-message&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.6K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/monolog/monolog&quot; target=_&quot;blank&quot;&gt;monolog/monolog&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.0K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/symfony/dependency-injection&quot; target=_&quot;blank&quot;&gt;symfony/dependency-injection&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.7K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/symfony/http-kernel&quot; target=_&quot;blank&quot;&gt;symfony/http-kernel&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.6K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/symfony/http-foundation&quot; target=_&quot;blank&quot;&gt;symfony/http-foundation&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.1K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/symfony/config&quot; target=_&quot;blank&quot;&gt;symfony/config&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.1K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/twig/twig&quot; target=_&quot;blank&quot;&gt;twig/twig&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.6K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/spatie/laravel-package-tools&quot; target=_&quot;blank&quot;&gt;spatie/laravel-package-tools&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.5K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/doctrine/orm&quot; target=_&quot;blank&quot;&gt;doctrine/orm&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.4K

&lt;/td&gt;
              &lt;/tr&gt;
              
            &lt;/tbody&gt;
          &lt;/table&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/section&gt;
  


  &lt;section class=&quot;mt-48&quot;&gt;
    &lt;h2 class=&quot;text-4xl mt-8 font-bold tracking-tight text-zinc-800 sm:text-5xl dark:text-zinc-100&quot;&gt;
      But What About Quality?
    &lt;/h2&gt;
    &lt;p class=&quot;text-gray-600 text-lg mt-4 mb-4&quot;&gt;
      Despite PHP&apos;s occasionally maligned reputation, the data tells a different story. 
      &lt;span class=&quot;font-bold text-indigo-600&quot;&gt;PHP packages show surprisingly clean code that&apos;s easy to maintain and understand. &lt;/span&gt;
      Functions are refreshingly short, different parts of the code play well together, and packages stay reasonably sized. Turns out PHP developers aren&apos;t writing spaghetti code after all - they&apos;re crafting well-structured dishes that would make even the pickiest code chefs nod in approval.
    &lt;/p&gt;
  &lt;/section&gt;
  
  &lt;!-- Charts Row 1 --&gt;
  &lt;section class=&quot;grid grid-cols-1 lg:grid-cols-2 gap-8&quot;&gt;
    &lt;!-- Complexity Distribution --&gt;
    &lt;div class=&quot;bg-white rounded-lg shadow p-6&quot;&gt;
      &lt;h3 class=&quot;text-xl font-bold mb-4 text-gray-700&quot;&gt;Complexity Distribution&lt;/h3&gt;
      &lt;div class=&quot;h-64&quot;&gt;
        &lt;canvas id=&quot;complexity-chart&quot;&gt;&lt;/canvas&gt;
      &lt;/div&gt;
      &lt;div class=&quot;grid grid-cols-2 gap-4 mt-4&quot;&gt;
        &lt;div class=&quot;rounded p-3 bg-blue-50&quot;&gt;
          &lt;p class=&quot;text-sm text-gray-500&quot;&gt;Average Cyclomatic&lt;/p&gt;
          &lt;p class=&quot;font-bold text-xl&quot; &gt;8.8&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot; rounded p-3 bg-blue-50&quot;&gt;
          &lt;p class=&quot;text-sm text-gray-500&quot;&gt;Median Cyclomatic&lt;/p&gt;
          &lt;p class=&quot;font-bold text-xl&quot; &gt;4.0&lt;/p&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    
    &lt;!-- Code Metrics --&gt;
    &lt;div class=&quot;bg-white rounded-lg shadow p-6&quot;&gt;
      &lt;h3 class=&quot;text-xl font-bold mb-4 text-gray-700&quot;&gt;Code Quality&lt;/h3&gt;
      &lt;div class=&quot;h-64&quot;&gt;
        &lt;canvas id=&quot;code-quality-chart&quot;&gt;&lt;/canvas&gt;
      &lt;/div&gt;
      &lt;div class=&quot;grid grid-cols-2 gap-4 mt-4&quot;&gt;
        &lt;div class=&quot;rounded p-3 bg-blue-50&quot;&gt;
          &lt;p class=&quot;text-sm text-gray-500&quot;&gt;Maintainability Index&lt;/p&gt;
          &lt;p class=&quot;font-bold text-xl&quot; &gt;108&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;rounded p-3 bg-blue-50&quot;&gt;
          &lt;p class=&quot;text-sm text-gray-500&quot;&gt;Lines per Method&lt;/p&gt;
          &lt;p class=&quot;font-bold text-xl&quot;&gt;3.0&lt;/p&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/section&gt;


  &lt;div class=&quot;mt-4 bg-white rounded-lg shadow p-6&quot;&gt;
    &lt;h3 class=&quot;text-xl font-bold text-gray-700&quot;&gt;Packages with Most Lines of Code&lt;/h3&gt;
    &lt;p class=&quot;text-gray-600 text-lg mt-4 mb-4&quot;&gt;
      If you are curious about the packages with the most lines of code, you can find the top 100 packages below.
      Note that the biggest packages concerns geographical data, and embed a lot of data in their code.
    &lt;/p&gt;
    &lt;div class=&quot;overflow-x-auto&quot;&gt;
      &lt;table class=&quot;min-w-full divide-y divide-gray-200&quot;&gt;
        &lt;thead class=&quot;bg-gray-50&quot;&gt;
          &lt;tr&gt;
            &lt;th class=&quot;px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider&quot;&gt;Package&lt;/th&gt;
            &lt;th class=&quot;px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider&quot;&gt;Lines of Code&lt;/th&gt;
            &lt;th class=&quot;px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider&quot;&gt;Files&lt;/th&gt;
          &lt;/tr&gt;
        &lt;/thead&gt;
        &lt;tbody class=&quot;bg-white divide-y divide-gray-200&quot;&gt;
          
          &lt;tr&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
              &lt;a href=&quot;https://packagist.org/packages/xnrcms/alipaysdk&quot; target=_&quot;blank&quot;&gt;xnrcms/alipaysdk&lt;/a&gt;
            &lt;/td&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

2.8M

&lt;/td&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.4K

&lt;/td&gt;
          &lt;/tr&gt;
          
          &lt;tr&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
              &lt;a href=&quot;https://packagist.org/packages/zerossb/laravel-world&quot; target=_&quot;blank&quot;&gt;zerossb/laravel-world&lt;/a&gt;
            &lt;/td&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

2.2M

&lt;/td&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

48.0

&lt;/td&gt;
          &lt;/tr&gt;
          
          &lt;tr&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
              &lt;a href=&quot;https://packagist.org/packages/tencentcloud/tencentcloud-sdk-php-intl-en&quot; target=_&quot;blank&quot;&gt;tencentcloud/tencentcloud-sdk-php-intl-en&lt;/a&gt;
            &lt;/td&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.9M

&lt;/td&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

19.2K

&lt;/td&gt;
          &lt;/tr&gt;
          
          &lt;tr&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
              &lt;a href=&quot;https://packagist.org/packages/vccmas/tencentcloud-sdk-php-intl-en&quot; target=_&quot;blank&quot;&gt;vccmas/tencentcloud-sdk-php-intl-en&lt;/a&gt;
            &lt;/td&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.6M

&lt;/td&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

15.5K

&lt;/td&gt;
          &lt;/tr&gt;
          
          &lt;tr&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
              &lt;a href=&quot;https://packagist.org/packages/zohocrm/php-sdk-6.0&quot; target=_&quot;blank&quot;&gt;zohocrm/php-sdk-6.0&lt;/a&gt;
            &lt;/td&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.5M

&lt;/td&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

15.7K

&lt;/td&gt;
          &lt;/tr&gt;
          
          &lt;tr&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
              &lt;a href=&quot;https://packagist.org/packages/udemy/googleads-php-lib&quot; target=_&quot;blank&quot;&gt;udemy/googleads-php-lib&lt;/a&gt;
            &lt;/td&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.5M

&lt;/td&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.1K

&lt;/td&gt;
          &lt;/tr&gt;
          
          &lt;tr&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
              &lt;a href=&quot;https://packagist.org/packages/volcengine/volcengine-php-sdk&quot; target=_&quot;blank&quot;&gt;volcengine/volcengine-php-sdk&lt;/a&gt;
            &lt;/td&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.3M

&lt;/td&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

3.1K

&lt;/td&gt;
          &lt;/tr&gt;
          
          &lt;tr&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
              &lt;a href=&quot;https://packagist.org/packages/vavajke/bxcore&quot; target=_&quot;blank&quot;&gt;vavajke/bxcore&lt;/a&gt;
            &lt;/td&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.3M

&lt;/td&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

4.4K

&lt;/td&gt;
          &lt;/tr&gt;
          
          &lt;tr&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
              &lt;a href=&quot;https://packagist.org/packages/sos-solution/other-framework&quot; target=_&quot;blank&quot;&gt;sos-solution/other-framework&lt;/a&gt;
            &lt;/td&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.2M

&lt;/td&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.7K

&lt;/td&gt;
          &lt;/tr&gt;
          
          &lt;tr&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
              &lt;a href=&quot;https://packagist.org/packages/tombeachell/forza-magento&quot; target=_&quot;blank&quot;&gt;tombeachell/forza-magento&lt;/a&gt;
            &lt;/td&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.2M

&lt;/td&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

4.2K

&lt;/td&gt;
          &lt;/tr&gt;
          
        &lt;/tbody&gt;
      &lt;/table&gt;
    &lt;/div&gt;
  &lt;/div&gt;


  &lt;section class=&quot; mt-48&quot;&gt;
    &lt;h2 class=&quot;text-4xl mt-8 font-bold tracking-tight text-zinc-800 sm:text-5xl dark:text-zinc-100&quot;&gt;
      What Else Is Hiding in PHP Repositories?
  &lt;/h2&gt;

    &lt;div class=&quot;text-gray-600 text-lg mt-4 mb-4&quot;&gt;
    Analyzing PHP on Packagist is one thing, but what about everything around PHP? The reality is, modern PHP projects rely on a diverse tech stack.
    &lt;/div&gt;

    &lt;div class=&quot;mt-4 grid grid-cols-1 md:grid-cols-2 gap-8&quot;&gt;
      &lt;div &gt;
        &lt;div&gt;🔍 Here’s what I found inside the repositories:&lt;/div&gt;
        &lt;ul class=&quot;list-disc list-inside text-gray-700 mb-4 ml-8&quot;&gt;
            &lt;li&gt;&lt;strong&gt;PHP dominates&lt;/strong&gt; (707M+ lines of code), but it’s far from alone.&lt;/li&gt;
            &lt;li&gt;&lt;strong&gt;JavaScript&lt;/strong&gt; is the most common companion (216M+ lines), proving that front-end and back-end integration is key.&lt;/li&gt;
            &lt;li&gt;&lt;strong&gt;JSON &amp; YAML&lt;/strong&gt; are everywhere (200M+ lines combined), highlighting the rise of configuration-driven architectures.&lt;/li&gt;
            &lt;li&gt;&lt;strong&gt;CSS &amp; HTML&lt;/strong&gt; (167M+ lines total) showcase the full-stack nature of many PHP applications.&lt;/li&gt;
            &lt;li&gt;&lt;strong&gt;SQL, XML, and Markdown&lt;/strong&gt; reflect the importance of structured data and documentation in modern PHP projects.&lt;/li&gt;
        &lt;/ul&gt;
        
        &lt;div class=&quot;mb-2&quot;&gt;💡 Surprising finds:&lt;/div&gt;
        &lt;ul class=&quot;list-disc list-inside text-gray-700 mb-4 ml-8&quot;&gt;
            &lt;li&gt;&lt;strong&gt;TypeScript is growing&lt;/strong&gt; (2.7M lines) – are PHP projects becoming more structured in the front end?&lt;/li&gt;
            &lt;li&gt;&lt;strong&gt;C, Go, and Rust&lt;/strong&gt; appear in small amounts – a sign of performance-critical extensions?&lt;/li&gt;
            &lt;li&gt;&lt;strong&gt;Even Lua, Perl, and Scheme&lt;/strong&gt; pop up – legacy integrations or scripting tools within PHP projects?&lt;/li&gt;
        &lt;/ul&gt;
        
        &lt;p class=&quot;text-gray-700 mb-4&quot;&gt;👉 &lt;strong class=&quot;font-bold text-purple-600&quot;&gt;Modern PHP is more than just PHP.&lt;/strong&gt; It’s an ecosystem that blends with various technologies, making cross-language skills increasingly valuable for PHP developers.&lt;/p&gt;
      &lt;/div&gt;    
      &lt;div class=&quot;bg-white rounded-lg shadow-md p-6&quot;&gt;
        &lt;div class=&quot;text-center text-gray-700 mb-4&quot;&gt;
          Repartition of lines of code by language (top 10)
        &lt;/div&gt;
        &lt;canvas id=&quot;chart-languages&quot;&gt;&lt;/canvas&gt;
      &lt;/div&gt;
    &lt;/div&gt;

  &lt;/section&gt;  


  
  &lt;section class=&quot;mt-48&quot;&gt;
    &lt;h2 class=&quot;text-4xl mt-8 font-bold tracking-tight text-zinc-800 sm:text-5xl dark:text-zinc-100&quot;&gt;
      What&apos;s Next?
    &lt;/h2&gt;

    &lt;div class=&quot;text-gray-600 text-lg mt-4 mb-4&quot;&gt;
      &lt;p class=&quot;text-gray-600 text-lg mt-4 mb-4&quot;&gt;
        Now that most of the process is scripted, I still have some code cleanup to tackle, but I think it would be valuable to run this analysis at least twice a year to keep an up-to-date view of the PHP ecosystem. So far, I&apos;ve focused exclusively on Packagist, but it&apos;s not the only package manager out there - adding new code sources would provide a more comprehensive picture.
      &lt;/p&gt;
      &lt;p class=&quot;text-gray-600 text-lg mt-4 mb-4&quot;&gt;
      I&apos;m also considering implementing more advanced code metrics to deepen the analysis. The current metrics give us a good foundation, but there&apos;s always more we could learn about code quality and ecosystem health.
      &lt;/p&gt;
      &lt;p class=&quot;text-gray-600 text-lg mt-4 mb-4&quot;&gt;
      If you have ideas for additional metrics or features that would enhance this dashboard, please reach out - your insights could help shape the future direction of this project!
      &lt;/p&gt;
    &lt;/div&gt;
  &lt;/section&gt;


  

&lt;script&gt;
// Function to format large numbers
function formatNumber(num) {
  if (num &gt; 1000000) {
    return (num / 1000000).toFixed(1) + &apos;M&apos;;
  } else if (num &gt; 1000) {
    return (num / 1000).toFixed(1) + &apos;k&apos;;
  }
  return num.toLocaleString();
}

// Load data
async function loadData() {

  // sync
  const fileAnalysis = {&quot;avg_cyclomatic_per_file&quot;:8.778060316311837,&quot;avg_files_per_package&quot;:32.58677030922509,&quot;cyclomatic_distribution&quot;:{&quot;0-10&quot;:8485264,&quot;10-20&quot;:1487838,&quot;100+&quot;:65804,&quot;20-30&quot;:526004,&quot;30-40&quot;:239622,&quot;40-50&quot;:122259,&quot;50-60&quot;:70031,&quot;60-70&quot;:43466,&quot;70-80&quot;:27005,&quot;80-90&quot;:20079,&quot;90-100&quot;:13572},&quot;loc_distribution&quot;:{&quot;0-25&quot;:10335731,&quot;100-150&quot;:28654,&quot;1000+&quot;:824,&quot;150-300&quot;:14845,&quot;25-50&quot;:556632,&quot;300-500&quot;:3117,&quot;50-75&quot;:118018,&quot;500-750&quot;:1125,&quot;75-100&quot;:41645,&quot;750-1000&quot;:353},&quot;median_cyclomatic&quot;:3,&quot;median_loc&quot;:4,&quot;total_classes&quot;:7393743,&quot;total_cyclomatic&quot;:97444756,&quot;total_interfaces&quot;:3707201,&quot;total_loc&quot;:107877627,&quot;total_methods&quot;:0,&quot;under_median_cyclomatic&quot;:4715701,&quot;under_median_loc&quot;:4084482};
  const statsOverview = {&quot;avg_bus_factor&quot;:0.14041291161262826,&quot;avg_commits&quot;:3.033670453019579,&quot;total_packages&quot;:411610};
  const dependencies = [{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:12679593,&quot;downloads_total&quot;:738780466,&quot;files&quot;:0,&quot;forks&quot;:2206,&quot;name&quot;:&quot;phpunit/phpunit&quot;,&quot;stars&quot;:19791,&quot;usage&quot;:139853},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:1543959,&quot;downloads_total&quot;:86284806,&quot;files&quot;:0,&quot;forks&quot;:162,&quot;name&quot;:&quot;illuminate/support&quot;,&quot;stars&quot;:569,&quot;usage&quot;:33345},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:13951038,&quot;downloads_total&quot;:800504182,&quot;files&quot;:0,&quot;forks&quot;:2398,&quot;name&quot;:&quot;guzzlehttp/guzzle&quot;,&quot;stars&quot;:23313,&quot;usage&quot;:33276},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:5588465,&quot;downloads_total&quot;:293188100,&quot;files&quot;:0,&quot;forks&quot;:68,&quot;name&quot;:&quot;squizlabs/php_codesniffer&quot;,&quot;stars&quot;:1112,&quot;usage&quot;:26759},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:559298,&quot;downloads_total&quot;:25513726,&quot;files&quot;:21,&quot;forks&quot;:137,&quot;name&quot;:&quot;orchestra/testbench&quot;,&quot;stars&quot;:2147,&quot;usage&quot;:26435},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:7824504,&quot;downloads_total&quot;:392501786,&quot;files&quot;:0,&quot;forks&quot;:457,&quot;name&quot;:&quot;mockery/mockery&quot;,&quot;stars&quot;:10661,&quot;usage&quot;:24273},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:6957511,&quot;downloads_total&quot;:245482397,&quot;files&quot;:0,&quot;forks&quot;:910,&quot;name&quot;:&quot;phpstan/phpstan&quot;,&quot;stars&quot;:13240,&quot;usage&quot;:23015},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:3995039,&quot;downloads_total&quot;:178240286,&quot;files&quot;:0,&quot;forks&quot;:1591,&quot;name&quot;:&quot;friendsofphp/php-cs-fixer&quot;,&quot;stars&quot;:13031,&quot;usage&quot;:18882},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:7860661,&quot;downloads_total&quot;:399349443,&quot;files&quot;:0,&quot;forks&quot;:11237,&quot;name&quot;:&quot;laravel/framework&quot;,&quot;stars&quot;:33237,&quot;usage&quot;:17310},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:300838,&quot;downloads_total&quot;:24674549,&quot;files&quot;:0,&quot;forks&quot;:134,&quot;name&quot;:&quot;yiisoft/yii2&quot;,&quot;stars&quot;:236,&quot;usage&quot;:13078},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:14851715,&quot;downloads_total&quot;:885244463,&quot;files&quot;:0,&quot;forks&quot;:259,&quot;name&quot;:&quot;symfony/console&quot;,&quot;stars&quot;:9785,&quot;usage&quot;:11415},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:4032056,&quot;downloads_total&quot;:182066185,&quot;files&quot;:0,&quot;forks&quot;:120,&quot;name&quot;:&quot;symfony/framework-bundle&quot;,&quot;stars&quot;:3539,&quot;usage&quot;:10312},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:11919219,&quot;downloads_total&quot;:627915019,&quot;files&quot;:10,&quot;forks&quot;:130,&quot;name&quot;:&quot;symfony/yaml&quot;,&quot;stars&quot;:3826,&quot;usage&quot;:9438},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:659077,&quot;downloads_total&quot;:43011212,&quot;files&quot;:0,&quot;forks&quot;:597,&quot;name&quot;:&quot;illuminate/database&quot;,&quot;stars&quot;:2705,&quot;usage&quot;:8836},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:1631363,&quot;downloads_total&quot;:27459868,&quot;files&quot;:0,&quot;forks&quot;:372,&quot;name&quot;:&quot;pestphp/pest&quot;,&quot;stars&quot;:9891,&quot;usage&quot;:8744},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:15188541,&quot;downloads_total&quot;:963431048,&quot;files&quot;:20,&quot;forks&quot;:182,&quot;name&quot;:&quot;psr/log&quot;,&quot;stars&quot;:10421,&quot;usage&quot;:8736},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:2126980,&quot;downloads_total&quot;:96035049,&quot;files&quot;:7,&quot;forks&quot;:82,&quot;name&quot;:&quot;illuminate/contracts&quot;,&quot;stars&quot;:682,&quot;usage&quot;:8216},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:2126980,&quot;downloads_total&quot;:96035049,&quot;files&quot;:146,&quot;forks&quot;:82,&quot;name&quot;:&quot;illuminate/contracts&quot;,&quot;stars&quot;:682,&quot;usage&quot;:8216},{&quot;bus_factor&quot;:2,&quot;downloads_monthly&quot;:12977531,&quot;downloads_total&quot;:678659761,&quot;files&quot;:68,&quot;forks&quot;:103,&quot;name&quot;:&quot;symfony/var-dumper&quot;,&quot;stars&quot;:7426,&quot;usage&quot;:7637},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:13099994,&quot;downloads_total&quot;:788872755,&quot;files&quot;:0,&quot;forks&quot;:1899,&quot;name&quot;:&quot;monolog/monolog&quot;,&quot;stars&quot;:21132,&quot;usage&quot;:7517},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:4547568,&quot;downloads_total&quot;:82720391,&quot;files&quot;:0,&quot;forks&quot;:152,&quot;name&quot;:&quot;laravel/pint&quot;,&quot;stars&quot;:2852,&quot;usage&quot;:7455},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:1896963,&quot;downloads_total&quot;:109882055,&quot;files&quot;:298,&quot;forks&quot;:412,&quot;name&quot;:&quot;composer/installers&quot;,&quot;stars&quot;:1437,&quot;usage&quot;:7264},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:1896963,&quot;downloads_total&quot;:109882055,&quot;files&quot;:33,&quot;forks&quot;:412,&quot;name&quot;:&quot;composer/installers&quot;,&quot;stars&quot;:1437,&quot;usage&quot;:7264},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:1084020,&quot;downloads_total&quot;:57812172,&quot;files&quot;:0,&quot;forks&quot;:672,&quot;name&quot;:&quot;vimeo/psalm&quot;,&quot;stars&quot;:5628,&quot;usage&quot;:7055},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:6657245,&quot;downloads_total&quot;:345448677,&quot;files&quot;:0,&quot;forks&quot;:94,&quot;name&quot;:&quot;symfony/dependency-injection&quot;,&quot;stars&quot;:4130,&quot;usage&quot;:6775},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:3900391,&quot;downloads_total&quot;:234366172,&quot;files&quot;:0,&quot;forks&quot;:2531,&quot;name&quot;:&quot;doctrine/orm&quot;,&quot;stars&quot;:10005,&quot;usage&quot;:6735},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:6432473,&quot;downloads_total&quot;:244614019,&quot;files&quot;:71,&quot;forks&quot;:164,&quot;name&quot;:&quot;nunomaduro/collision&quot;,&quot;stars&quot;:4552,&quot;usage&quot;:6700},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:2205651,&quot;downloads_total&quot;:67589115,&quot;files&quot;:24,&quot;forks&quot;:48,&quot;name&quot;:&quot;phpstan/phpstan-phpunit&quot;,&quot;stars&quot;:480,&quot;usage&quot;:6550},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:6129049,&quot;downloads_total&quot;:367534605,&quot;files&quot;:0,&quot;forks&quot;:1266,&quot;name&quot;:&quot;twig/twig&quot;,&quot;stars&quot;:8256,&quot;usage&quot;:6479},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:2355242,&quot;downloads_total&quot;:62115058,&quot;files&quot;:11,&quot;forks&quot;:27,&quot;name&quot;:&quot;phpstan/extension-installer&quot;,&quot;stars&quot;:446,&quot;usage&quot;:6404},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:1486528,&quot;downloads_total&quot;:75758927,&quot;files&quot;:0,&quot;forks&quot;:106,&quot;name&quot;:&quot;roave/security-advisories&quot;,&quot;stars&quot;:2756,&quot;usage&quot;:6377},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:6704982,&quot;downloads_total&quot;:368966055,&quot;files&quot;:89,&quot;forks&quot;:42,&quot;name&quot;:&quot;symfony/config&quot;,&quot;stars&quot;:4248,&quot;usage&quot;:6033},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:12047419,&quot;downloads_total&quot;:658708043,&quot;files&quot;:0,&quot;forks&quot;:92,&quot;name&quot;:&quot;symfony/http-kernel&quot;,&quot;stars&quot;:8126,&quot;usage&quot;:6033},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:7955368,&quot;downloads_total&quot;:478582482,&quot;files&quot;:0,&quot;forks&quot;:1353,&quot;name&quot;:&quot;doctrine/dbal&quot;,&quot;stars&quot;:9554,&quot;usage&quot;:5761},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:8979714,&quot;downloads_total&quot;:474766398,&quot;files&quot;:41,&quot;forks&quot;:638,&quot;name&quot;:&quot;vlucas/phpdotenv&quot;,&quot;stars&quot;:13265,&quot;usage&quot;:5597},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:1434302,&quot;downloads_total&quot;:89017087,&quot;files&quot;:0,&quot;forks&quot;:348,&quot;name&quot;:&quot;phpmd/phpmd&quot;,&quot;stars&quot;:2348,&quot;usage&quot;:5307},{&quot;bus_factor&quot;:2,&quot;downloads_monthly&quot;:14087178,&quot;downloads_total&quot;:785182390,&quot;files&quot;:22,&quot;forks&quot;:104,&quot;name&quot;:&quot;symfony/process&quot;,&quot;stars&quot;:7453,&quot;usage&quot;:5283},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:14537371,&quot;downloads_total&quot;:823272846,&quot;files&quot;:10,&quot;forks&quot;:186,&quot;name&quot;:&quot;psr/http-message&quot;,&quot;stars&quot;:6998,&quot;usage&quot;:5164},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:12655313,&quot;downloads_total&quot;:704319494,&quot;files&quot;:0,&quot;forks&quot;:298,&quot;name&quot;:&quot;symfony/http-foundation&quot;,&quot;stars&quot;:8652,&quot;usage&quot;:5080},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:2008908,&quot;downloads_total&quot;:49058927,&quot;files&quot;:22,&quot;forks&quot;:19,&quot;name&quot;:&quot;phpstan/phpstan-deprecation-rules&quot;,&quot;stars&quot;:389,&quot;usage&quot;:4862},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:10090649,&quot;downloads_total&quot;:524194808,&quot;files&quot;:0,&quot;forks&quot;:6,&quot;name&quot;:&quot;nesbot/carbon&quot;,&quot;stars&quot;:48,&quot;usage&quot;:4857},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:3369301,&quot;downloads_total&quot;:71591818,&quot;files&quot;:19,&quot;forks&quot;:147,&quot;name&quot;:&quot;spatie/laravel-package-tools&quot;,&quot;stars&quot;:828,&quot;usage&quot;:4850},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:1203781,&quot;downloads_total&quot;:18818296,&quot;files&quot;:19,&quot;forks&quot;:42,&quot;name&quot;:&quot;pestphp/pest-plugin-laravel&quot;,&quot;stars&quot;:191,&quot;usage&quot;:4724},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:11080,&quot;downloads_total&quot;:5004008,&quot;files&quot;:1,&quot;forks&quot;:101,&quot;name&quot;:&quot;satooshi/php-coveralls&quot;,&quot;stars&quot;:455,&quot;usage&quot;:4558},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:555231,&quot;downloads_total&quot;:36257490,&quot;files&quot;:2,&quot;forks&quot;:26,&quot;name&quot;:&quot;illuminate/console&quot;,&quot;stars&quot;:124,&quot;usage&quot;:4445},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:555231,&quot;downloads_total&quot;:36257490,&quot;files&quot;:104,&quot;forks&quot;:26,&quot;name&quot;:&quot;illuminate/console&quot;,&quot;stars&quot;:124,&quot;usage&quot;:4445},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:2876824,&quot;downloads_total&quot;:163123088,&quot;files&quot;:24,&quot;forks&quot;:47,&quot;name&quot;:&quot;symfony/phpunit-bridge&quot;,&quot;stars&quot;:2460,&quot;usage&quot;:4237},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:950380,&quot;downloads_total&quot;:220790619,&quot;files&quot;:0,&quot;forks&quot;:3333,&quot;name&quot;:&quot;fzaninotto/faker&quot;,&quot;stars&quot;:26398,&quot;usage&quot;:4057},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:433617,&quot;downloads_total&quot;:29675737,&quot;files&quot;:0,&quot;forks&quot;:37,&quot;name&quot;:&quot;illuminate/http&quot;,&quot;stars&quot;:114,&quot;usage&quot;:3962},{&quot;bus_factor&quot;:3,&quot;downloads_monthly&quot;:433617,&quot;downloads_total&quot;:29675737,&quot;files&quot;:20,&quot;forks&quot;:37,&quot;name&quot;:&quot;illuminate/http&quot;,&quot;stars&quot;:114,&quot;usage&quot;:3962},{&quot;bus_factor&quot;:2,&quot;downloads_monthly&quot;:13973488,&quot;downloads_total&quot;:807162519,&quot;files&quot;:23,&quot;forks&quot;:55,&quot;name&quot;:&quot;symfony/finder&quot;,&quot;stars&quot;:8436,&quot;usage&quot;:3936},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:264604,&quot;downloads_total&quot;:33391615,&quot;files&quot;:282,&quot;forks&quot;:280,&quot;name&quot;:&quot;phpspec/phpspec&quot;,&quot;stars&quot;:1888,&quot;usage&quot;:3851},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:159614,&quot;downloads_total&quot;:10636365,&quot;files&quot;:0,&quot;forks&quot;:125,&quot;name&quot;:&quot;php-coveralls/php-coveralls&quot;,&quot;stars&quot;:517,&quot;usage&quot;:3812},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:3096800,&quot;downloads_total&quot;:198345829,&quot;files&quot;:0,&quot;forks&quot;:454,&quot;name&quot;:&quot;doctrine/doctrine-bundle&quot;,&quot;stars&quot;:4736,&quot;usage&quot;:3594},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:3096800,&quot;downloads_total&quot;:198345829,&quot;files&quot;:67,&quot;forks&quot;:454,&quot;name&quot;:&quot;doctrine/doctrine-bundle&quot;,&quot;stars&quot;:4736,&quot;usage&quot;:3594},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:15042293,&quot;downloads_total&quot;:803012501,&quot;files&quot;:3,&quot;forks&quot;:51,&quot;name&quot;:&quot;psr/container&quot;,&quot;stars&quot;:9981,&quot;usage&quot;:3581},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:1207757,&quot;downloads_total&quot;:70626039,&quot;files&quot;:0,&quot;forks&quot;:1304,&quot;name&quot;:&quot;codeception/codeception&quot;,&quot;stars&quot;:4797,&quot;usage&quot;:3527},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:3235616,&quot;downloads_total&quot;:75693058,&quot;files&quot;:0,&quot;forks&quot;:708,&quot;name&quot;:&quot;rector/rector&quot;,&quot;stars&quot;:9397,&quot;usage&quot;:3526},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:210114,&quot;downloads_total&quot;:9086682,&quot;files&quot;:0,&quot;forks&quot;:18,&quot;name&quot;:&quot;typo3/cms-core&quot;,&quot;stars&quot;:31,&quot;usage&quot;:3502},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:10973720,&quot;downloads_total&quot;:545844010,&quot;files&quot;:114,&quot;forks&quot;:502,&quot;name&quot;:&quot;ramsey/uuid&quot;,&quot;stars&quot;:12515,&quot;usage&quot;:3372},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:293035,&quot;downloads_total&quot;:82846617,&quot;files&quot;:0,&quot;forks&quot;:9562,&quot;name&quot;:&quot;symfony/symfony&quot;,&quot;stars&quot;:30071,&quot;usage&quot;:3311},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:4867081,&quot;downloads_total&quot;:224448440,&quot;files&quot;:0,&quot;forks&quot;:104,&quot;name&quot;:&quot;symfony/validator&quot;,&quot;stars&quot;:2657,&quot;usage&quot;:3300},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:1084250,&quot;downloads_total&quot;:39273399,&quot;files&quot;:44,&quot;forks&quot;:49,&quot;name&quot;:&quot;phpstan/phpstan-strict-rules&quot;,&quot;stars&quot;:632,&quot;usage&quot;:3263},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:13306972,&quot;downloads_total&quot;:801654417,&quot;files&quot;:30,&quot;forks&quot;:67,&quot;name&quot;:&quot;symfony/event-dispatcher&quot;,&quot;stars&quot;:8534,&quot;usage&quot;:3263},{&quot;bus_factor&quot;:2,&quot;downloads_monthly&quot;:9249313,&quot;downloads_total&quot;:540730584,&quot;files&quot;:8,&quot;forks&quot;:50,&quot;name&quot;:&quot;symfony/filesystem&quot;,&quot;stars&quot;:4621,&quot;usage&quot;:3125},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:1521225,&quot;downloads_total&quot;:85396918,&quot;files&quot;:53,&quot;forks&quot;:102,&quot;name&quot;:&quot;mikey179/vfsstream&quot;,&quot;stars&quot;:1423,&quot;usage&quot;:3010},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:14212004,&quot;downloads_total&quot;:814214923,&quot;files&quot;:35,&quot;forks&quot;:295,&quot;name&quot;:&quot;guzzlehttp/psr7&quot;,&quot;stars&quot;:7905,&quot;usage&quot;:2970},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:14212004,&quot;downloads_total&quot;:814214923,&quot;files&quot;:32,&quot;forks&quot;:295,&quot;name&quot;:&quot;guzzlehttp/psr7&quot;,&quot;stars&quot;:7905,&quot;usage&quot;:2970},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:2335890,&quot;downloads_total&quot;:118912046,&quot;files&quot;:0,&quot;forks&quot;:111,&quot;name&quot;:&quot;symfony/form&quot;,&quot;stars&quot;:2753,&quot;usage&quot;:2938},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:36940,&quot;downloads_total&quot;:2933435,&quot;files&quot;:0,&quot;forks&quot;:824,&quot;name&quot;:&quot;silverstripe/framework&quot;,&quot;stars&quot;:720,&quot;usage&quot;:2919},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:8333861,&quot;downloads_total&quot;:249334538,&quot;files&quot;:0,&quot;forks&quot;:3584,&quot;name&quot;:&quot;fakerphp/faker&quot;,&quot;stars&quot;:3696,&quot;usage&quot;:2886},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:2511894,&quot;downloads_total&quot;:154249705,&quot;files&quot;:0,&quot;forks&quot;:4596,&quot;name&quot;:&quot;composer/composer&quot;,&quot;stars&quot;:28836,&quot;usage&quot;:2847},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:2511894,&quot;downloads_total&quot;:154249705,&quot;files&quot;:280,&quot;forks&quot;:4596,&quot;name&quot;:&quot;composer/composer&quot;,&quot;stars&quot;:28836,&quot;usage&quot;:2847},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:6330192,&quot;downloads_total&quot;:232554721,&quot;files&quot;:0,&quot;forks&quot;:50,&quot;name&quot;:&quot;symfony/http-client&quot;,&quot;stars&quot;:1979,&quot;usage&quot;:2631},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:11103163,&quot;downloads_total&quot;:605685717,&quot;files&quot;:50,&quot;forks&quot;:46,&quot;name&quot;:&quot;symfony/css-selector&quot;,&quot;stars&quot;:7440,&quot;usage&quot;:2628},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:4473774,&quot;downloads_total&quot;:243106523,&quot;files&quot;:0,&quot;forks&quot;:990,&quot;name&quot;:&quot;predis/predis&quot;,&quot;stars&quot;:7648,&quot;usage&quot;:2600},{&quot;bus_factor&quot;:2,&quot;downloads_monthly&quot;:4267067,&quot;downloads_total&quot;:226331863,&quot;files&quot;:14,&quot;forks&quot;:59,&quot;name&quot;:&quot;symfony/browser-kit&quot;,&quot;stars&quot;:2977,&quot;usage&quot;:2568},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:444890,&quot;downloads_total&quot;:38950806,&quot;files&quot;:0,&quot;forks&quot;:439,&quot;name&quot;:&quot;nunomaduro/larastan&quot;,&quot;stars&quot;:5756,&quot;usage&quot;:2484},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:2814721,&quot;downloads_total&quot;:153068840,&quot;files&quot;:331,&quot;forks&quot;:1495,&quot;name&quot;:&quot;intervention/image&quot;,&quot;stars&quot;:14082,&quot;usage&quot;:2445},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:2814721,&quot;downloads_total&quot;:153068840,&quot;files&quot;:331,&quot;forks&quot;:1495,&quot;name&quot;:&quot;intervention/image&quot;,&quot;stars&quot;:14082,&quot;usage&quot;:2445},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:915936,&quot;downloads_total&quot;:49510052,&quot;files&quot;:0,&quot;forks&quot;:24,&quot;name&quot;:&quot;illuminate/filesystem&quot;,&quot;stars&quot;:149,&quot;usage&quot;:2439},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:915936,&quot;downloads_total&quot;:49510052,&quot;files&quot;:12,&quot;forks&quot;:24,&quot;name&quot;:&quot;illuminate/filesystem&quot;,&quot;stars&quot;:149,&quot;usage&quot;:2439},{&quot;bus_factor&quot;:2,&quot;downloads_monthly&quot;:5434878,&quot;downloads_total&quot;:309910053,&quot;files&quot;:23,&quot;forks&quot;:125,&quot;name&quot;:&quot;symfony/dom-crawler&quot;,&quot;stars&quot;:3986,&quot;usage&quot;:2412},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:28791,&quot;downloads_total&quot;:3176111,&quot;files&quot;:0,&quot;forks&quot;:645,&quot;name&quot;:&quot;craftcms/cms&quot;,&quot;stars&quot;:3328,&quot;usage&quot;:2373},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:6438522,&quot;downloads_total&quot;:475446441,&quot;files&quot;:0,&quot;forks&quot;:235,&quot;name&quot;:&quot;doctrine/annotations&quot;,&quot;stars&quot;:6742,&quot;usage&quot;:2361},{&quot;bus_factor&quot;:3,&quot;downloads_monthly&quot;:5332557,&quot;downloads_total&quot;:234580355,&quot;files&quot;:130,&quot;forks&quot;:77,&quot;name&quot;:&quot;symfony/serializer&quot;,&quot;stars&quot;:2518,&quot;usage&quot;:2341},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:514788,&quot;downloads_total&quot;:35489214,&quot;files&quot;:20,&quot;forks&quot;:11,&quot;name&quot;:&quot;illuminate/config&quot;,&quot;stars&quot;:106,&quot;usage&quot;:2330},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:514788,&quot;downloads_total&quot;:35489214,&quot;files&quot;:31,&quot;forks&quot;:11,&quot;name&quot;:&quot;illuminate/config&quot;,&quot;stars&quot;:106,&quot;usage&quot;:2330},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:1448220,&quot;downloads_total&quot;:16467245,&quot;files&quot;:0,&quot;forks&quot;:19,&quot;name&quot;:&quot;pestphp/pest-plugin-arch&quot;,&quot;stars&quot;:33,&quot;usage&quot;:2328},{&quot;bus_factor&quot;:2,&quot;downloads_monthly&quot;:4248293,&quot;downloads_total&quot;:173669521,&quot;files&quot;:7,&quot;forks&quot;:29,&quot;name&quot;:&quot;symfony/dotenv&quot;,&quot;stars&quot;:3766,&quot;usage&quot;:2316},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:41564,&quot;downloads_total&quot;:3593448,&quot;files&quot;:118,&quot;forks&quot;:12,&quot;name&quot;:&quot;spryker/code-sniffer&quot;,&quot;stars&quot;:36,&quot;usage&quot;:2310},{&quot;bus_factor&quot;:2,&quot;downloads_monthly&quot;:3347591,&quot;downloads_total&quot;:148387034,&quot;files&quot;:45,&quot;forks&quot;:24,&quot;name&quot;:&quot;symfony/twig-bundle&quot;,&quot;stars&quot;:2494,&quot;usage&quot;:2303},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:632848,&quot;downloads_total&quot;:27234750,&quot;files&quot;:46,&quot;forks&quot;:62,&quot;name&quot;:&quot;spatie/laravel-ray&quot;,&quot;stars&quot;:295,&quot;usage&quot;:2301},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:7724250,&quot;downloads_total&quot;:408080867,&quot;files&quot;:0,&quot;forks&quot;:1228,&quot;name&quot;:&quot;aws/aws-sdk-php&quot;,&quot;stars&quot;:6082,&quot;usage&quot;:2259},{&quot;bus_factor&quot;:2,&quot;downloads_monthly&quot;:9684327,&quot;downloads_total&quot;:503869950,&quot;files&quot;:182,&quot;forks&quot;:833,&quot;name&quot;:&quot;league/flysystem&quot;,&quot;stars&quot;:13410,&quot;usage&quot;:2226},{&quot;bus_factor&quot;:2,&quot;downloads_monthly&quot;:6484555,&quot;downloads_total&quot;:264779554,&quot;files&quot;:72,&quot;forks&quot;:60,&quot;name&quot;:&quot;symfony/cache&quot;,&quot;stars&quot;:4125,&quot;usage&quot;:2217},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:405232,&quot;downloads_total&quot;:42225537,&quot;files&quot;:0,&quot;forks&quot;:179,&quot;name&quot;:&quot;sebastian/phpcpd&quot;,&quot;stars&quot;:2221,&quot;usage&quot;:2214},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:18799,&quot;downloads_total&quot;:1317733,&quot;files&quot;:0,&quot;forks&quot;:57,&quot;name&quot;:&quot;contao/core-bundle&quot;,&quot;stars&quot;:123,&quot;usage&quot;:2185},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:1188011,&quot;downloads_total&quot;:79495397,&quot;files&quot;:523,&quot;forks&quot;:612,&quot;name&quot;:&quot;behat/behat&quot;,&quot;stars&quot;:3919,&quot;usage&quot;:2173},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:1188011,&quot;downloads_total&quot;:79495397,&quot;files&quot;:531,&quot;forks&quot;:612,&quot;name&quot;:&quot;behat/behat&quot;,&quot;stars&quot;:3919,&quot;usage&quot;:2173}];
  const complexity = {&quot;avg_cyclomatic&quot;:8.778060316311837,&quot;avg_loc&quot;:9.717878677705247};
  const codeMetrics = {&quot;avg_afferent_coupling&quot;:4.8363979774115275,&quot;avg_efferent_coupling&quot;:2.1615589376608653,&quot;avg_files&quot;:21.60292529331748,&quot;avg_mi&quot;:108.70067995046874,&quot;total_classes&quot;:8563791,&quot;total_files&quot;:11100944,&quot;total_methods&quot;:45794037};
  const packageStats = {&quot;avg_stars&quot;:60.12450462351387,&quot;download_dominance&quot;:{&quot;cumulative_percentage&quot;:50.1130958244871,&quot;dominant_package_count&quot;:136,&quot;packages&quot;:[{&quot;dependents&quot;:354,&quot;downloads_monthly&quot;:15689866,&quot;forks&quot;:8,&quot;name&quot;:&quot;symfony/deprecation-contracts&quot;,&quot;percentage&quot;:0.5586445166114565,&quot;stars&quot;:2053},{&quot;dependents&quot;:462,&quot;downloads_monthly&quot;:15528737,&quot;forks&quot;:38,&quot;name&quot;:&quot;symfony/polyfill-mbstring&quot;,&quot;percentage&quot;:0.5529074483460495,&quot;stars&quot;:7847},{&quot;dependents&quot;:8736,&quot;downloads_monthly&quot;:15188541,&quot;forks&quot;:182,&quot;name&quot;:&quot;psr/log&quot;,&quot;percentage&quot;:0.540794621507812,&quot;stars&quot;:10421},{&quot;dependents&quot;:3581,&quot;downloads_monthly&quot;:15042293,&quot;forks&quot;:51,&quot;name&quot;:&quot;psr/container&quot;,&quot;percentage&quot;:0.5355873977325808,&quot;stars&quot;:9981},{&quot;dependents&quot;:11415,&quot;downloads_monthly&quot;:14851715,&quot;forks&quot;:259,&quot;name&quot;:&quot;symfony/console&quot;,&quot;percentage&quot;:0.5288017849882286,&quot;stars&quot;:9785},{&quot;dependents&quot;:209,&quot;downloads_monthly&quot;:14653286,&quot;forks&quot;:9,&quot;name&quot;:&quot;symfony/service-contracts&quot;,&quot;percentage&quot;:0.5217366339673916,&quot;stars&quot;:2595},{&quot;dependents&quot;:28,&quot;downloads_monthly&quot;:14595392,&quot;forks&quot;:4,&quot;name&quot;:&quot;symfony/polyfill-intl-normalizer&quot;,&quot;percentage&quot;:0.5196752928670467,&quot;stars&quot;:2026},{&quot;dependents&quot;:5164,&quot;downloads_monthly&quot;:14537371,&quot;forks&quot;:186,&quot;name&quot;:&quot;psr/http-message&quot;,&quot;percentage&quot;:0.5176094298763549,&quot;stars&quot;:6998},{&quot;dependents&quot;:2970,&quot;downloads_monthly&quot;:14212004,&quot;forks&quot;:295,&quot;name&quot;:&quot;guzzlehttp/psr7&quot;,&quot;percentage&quot;:0.5060245960456314,&quot;stars&quot;:7905},{&quot;dependents&quot;:461,&quot;downloads_monthly&quot;:14089514,&quot;forks&quot;:117,&quot;name&quot;:&quot;guzzlehttp/promises&quot;,&quot;percentage&quot;:0.5016632862141939,&quot;stars&quot;:7645},{&quot;dependents&quot;:5283,&quot;downloads_monthly&quot;:14087178,&quot;forks&quot;:104,&quot;name&quot;:&quot;symfony/process&quot;,&quot;percentage&quot;:0.5015801119161594,&quot;stars&quot;:7453},{&quot;dependents&quot;:111,&quot;downloads_monthly&quot;:14082419,&quot;forks&quot;:7,&quot;name&quot;:&quot;symfony/polyfill-ctype&quot;,&quot;percentage&quot;:0.5014106656471757,&quot;stars&quot;:4056},{&quot;dependents&quot;:3936,&quot;downloads_monthly&quot;:13973488,&quot;forks&quot;:55,&quot;name&quot;:&quot;symfony/finder&quot;,&quot;percentage&quot;:0.49753212991978313,&quot;stars&quot;:8436},{&quot;dependents&quot;:33276,&quot;downloads_monthly&quot;:13951038,&quot;forks&quot;:2398,&quot;name&quot;:&quot;guzzlehttp/guzzle&quot;,&quot;percentage&quot;:0.496732787886019,&quot;stars&quot;:23313},{&quot;dependents&quot;:620,&quot;downloads_monthly&quot;:13899161,&quot;forks&quot;:21,&quot;name&quot;:&quot;symfony/string&quot;,&quot;percentage&quot;:0.4948856846928972,&quot;stars&quot;:1741},{&quot;dependents&quot;:88,&quot;downloads_monthly&quot;:13865220,&quot;forks&quot;:34,&quot;name&quot;:&quot;ralouphie/getallheaders&quot;,&quot;percentage&quot;:0.49367720059632747,&quot;stars&quot;:3778},{&quot;dependents&quot;:1627,&quot;downloads_monthly&quot;:13615876,&quot;forks&quot;:24,&quot;name&quot;:&quot;psr/http-factory&quot;,&quot;percentage&quot;:0.48479919881161077,&quot;stars&quot;:1827},{&quot;dependents&quot;:1678,&quot;downloads_monthly&quot;:13603450,&quot;forks&quot;:1097,&quot;name&quot;:&quot;nikic/php-parser&quot;,&quot;percentage&quot;:0.48435676566633,&quot;stars&quot;:17179},{&quot;dependents&quot;:499,&quot;downloads_monthly&quot;:13565065,&quot;forks&quot;:26,&quot;name&quot;:&quot;symfony/polyfill-php80&quot;,&quot;percentage&quot;:0.48299005101305437,&quot;stars&quot;:1731},{&quot;dependents&quot;:21,&quot;downloads_monthly&quot;:13553454,&quot;forks&quot;:4,&quot;name&quot;:&quot;symfony/polyfill-intl-grapheme&quot;,&quot;percentage&quot;:0.48257663629795255,&quot;stars&quot;:1689},{&quot;dependents&quot;:1647,&quot;downloads_monthly&quot;:13335711,&quot;forks&quot;:17,&quot;name&quot;:&quot;psr/http-client&quot;,&quot;percentage&quot;:0.4748238018900278,&quot;stars&quot;:1685},{&quot;dependents&quot;:3263,&quot;downloads_monthly&quot;:13306972,&quot;forks&quot;:67,&quot;name&quot;:&quot;symfony/event-dispatcher&quot;,&quot;percentage&quot;:0.4738005372705023,&quot;stars&quot;:8534},{&quot;dependents&quot;:7517,&quot;downloads_monthly&quot;:13099994,&quot;forks&quot;:1899,&quot;name&quot;:&quot;monolog/monolog&quot;,&quot;percentage&quot;:0.46643099537899047,&quot;stars&quot;:21132},{&quot;dependents&quot;:288,&quot;downloads_monthly&quot;:13070516,&quot;forks&quot;:3,&quot;name&quot;:&quot;symfony/event-dispatcher-contracts&quot;,&quot;percentage&quot;:0.46538141834240704,&quot;stars&quot;:3394},{&quot;dependents&quot;:7637,&quot;downloads_monthly&quot;:12977531,&quot;forks&quot;:103,&quot;name&quot;:&quot;symfony/var-dumper&quot;,&quot;percentage&quot;:0.46207064689431965,&quot;stars&quot;:7426},{&quot;dependents&quot;:163,&quot;downloads_monthly&quot;:12776353,&quot;forks&quot;:103,&quot;name&quot;:&quot;myclabs/deep-copy&quot;,&quot;percentage&quot;:0.4549076165304619,&quot;stars&quot;:8807},{&quot;dependents&quot;:139853,&quot;downloads_monthly&quot;:12679593,&quot;forks&quot;:2206,&quot;name&quot;:&quot;phpunit/phpunit&quot;,&quot;percentage&quot;:0.45146243456222046,&quot;stars&quot;:19791},{&quot;dependents&quot;:5080,&quot;downloads_monthly&quot;:12655313,&quot;forks&quot;:298,&quot;name&quot;:&quot;symfony/http-foundation&quot;,&quot;percentage&quot;:0.4505979345809379,&quot;stars&quot;:8652},{&quot;dependents&quot;:237,&quot;downloads_monthly&quot;:12611304,&quot;forks&quot;:84,&quot;name&quot;:&quot;sebastian/diff&quot;,&quot;percentage&quot;:0.44903097495670946,&quot;stars&quot;:7600},{&quot;dependents&quot;:102,&quot;downloads_monthly&quot;:12566777,&quot;forks&quot;:59,&quot;name&quot;:&quot;doctrine/lexer&quot;,&quot;percentage&quot;:0.44744557171673544,&quot;stars&quot;:11102},{&quot;dependents&quot;:296,&quot;downloads_monthly&quot;:12438223,&quot;forks&quot;:3,&quot;name&quot;:&quot;symfony/translation-contracts&quot;,&quot;percentage&quot;:0.4428683505225921,&quot;stars&quot;:2576},{&quot;dependents&quot;:1522,&quot;downloads_monthly&quot;:12325214,&quot;forks&quot;:376,&quot;name&quot;:&quot;phpunit/php-code-coverage&quot;,&quot;percentage&quot;:0.43884461582799733,&quot;stars&quot;:8863},{&quot;dependents&quot;:70,&quot;downloads_monthly&quot;:12285878,&quot;forks&quot;:6,&quot;name&quot;:&quot;symfony/polyfill-intl-idn&quot;,&quot;percentage&quot;:0.4374440404052736,&quot;stars&quot;:3339},{&quot;dependents&quot;:152,&quot;downloads_monthly&quot;:12268127,&quot;forks&quot;:69,&quot;name&quot;:&quot;sebastian/comparator&quot;,&quot;percentage&quot;:0.4368120083143449,&quot;stars&quot;:7024},{&quot;dependents&quot;:75,&quot;downloads_monthly&quot;:12257054,&quot;forks&quot;:34,&quot;name&quot;:&quot;sebastian/exporter&quot;,&quot;percentage&quot;:0.43641774932370486,&quot;stars&quot;:6801},{&quot;dependents&quot;:740,&quot;downloads_monthly&quot;:12221849,&quot;forks&quot;:21,&quot;name&quot;:&quot;psr/event-dispatcher&quot;,&quot;percentage&quot;:0.43516425995628094,&quot;stars&quot;:2209},{&quot;dependents&quot;:69,&quot;downloads_monthly&quot;:12168364,&quot;forks&quot;:46,&quot;name&quot;:&quot;phpunit/php-file-iterator&quot;,&quot;percentage&quot;:0.4332599032223889,&quot;stars&quot;:7444},{&quot;dependents&quot;:18,&quot;downloads_monthly&quot;:12155378,&quot;forks&quot;:18,&quot;name&quot;:&quot;sebastian/recursion-context&quot;,&quot;percentage&quot;:0.43279753103305874,&quot;stars&quot;:6556},{&quot;dependents&quot;:35,&quot;downloads_monthly&quot;:12144945,&quot;forks&quot;:19,&quot;name&quot;:&quot;sebastian/global-state&quot;,&quot;percentage&quot;:0.4324260595213321,&quot;stars&quot;:6584},{&quot;dependents&quot;:138,&quot;downloads_monthly&quot;:12138063,&quot;forks&quot;:32,&quot;name&quot;:&quot;sebastian/version&quot;,&quot;percentage&quot;:0.43218102291213994,&quot;stars&quot;:6557},{&quot;dependents&quot;:141,&quot;downloads_monthly&quot;:12136356,&quot;forks&quot;:65,&quot;name&quot;:&quot;phpunit/php-timer&quot;,&quot;percentage&quot;:0.4321202444332252,&quot;stars&quot;:7684},{&quot;dependents&quot;:63,&quot;downloads_monthly&quot;:12118159,&quot;forks&quot;:35,&quot;name&quot;:&quot;sebastian/environment&quot;,&quot;percentage&quot;:0.43147233231792875,&quot;stars&quot;:6752},{&quot;dependents&quot;:1141,&quot;downloads_monthly&quot;:12103984,&quot;forks&quot;:147,&quot;name&quot;:&quot;webmozart/assert&quot;,&quot;percentage&quot;:0.43096762526542953,&quot;stars&quot;:7588},{&quot;dependents&quot;:73,&quot;downloads_monthly&quot;:12076509,&quot;forks&quot;:30,&quot;name&quot;:&quot;phpunit/php-text-template&quot;,&quot;percentage&quot;:0.42998936591675824,&quot;stars&quot;:7389},{&quot;dependents&quot;:6033,&quot;downloads_monthly&quot;:12047419,&quot;forks&quot;:92,&quot;name&quot;:&quot;symfony/http-kernel&quot;,&quot;percentage&quot;:0.42895360378926606,&quot;stars&quot;:8126},{&quot;dependents&quot;:20,&quot;downloads_monthly&quot;:12008759,&quot;forks&quot;:9,&quot;name&quot;:&quot;sebastian/object-enumerator&quot;,&quot;percentage&quot;:0.4275770976411448,&quot;stars&quot;:6507},{&quot;dependents&quot;:8,&quot;downloads_monthly&quot;:11935574,&quot;forks&quot;:6,&quot;name&quot;:&quot;sebastian/code-unit-reverse-lookup&quot;,&quot;percentage&quot;:0.4249713138219452,&quot;stars&quot;:6690},{&quot;dependents&quot;:9438,&quot;downloads_monthly&quot;:11919219,&quot;forks&quot;:130,&quot;name&quot;:&quot;symfony/yaml&quot;,&quot;percentage&quot;:0.42438898691939675,&quot;stars&quot;:3826},{&quot;dependents&quot;:456,&quot;downloads_monthly&quot;:11910831,&quot;forks&quot;:19,&quot;name&quot;:&quot;symfony/error-handler&quot;,&quot;percentage&quot;:0.4240903285238861,&quot;stars&quot;:2623},{&quot;dependents&quot;:12,&quot;downloads_monthly&quot;:11885178,&quot;forks&quot;:21,&quot;name&quot;:&quot;theseer/tokenizer&quot;,&quot;percentage&quot;:0.42317694227924685,&quot;stars&quot;:5177},{&quot;dependents&quot;:22,&quot;downloads_monthly&quot;:11867838,&quot;forks&quot;:14,&quot;name&quot;:&quot;phar-io/manifest&quot;,&quot;percentage&quot;:0.42255954402243295,&quot;stars&quot;:7431},{&quot;dependents&quot;:1954,&quot;downloads_monthly&quot;:11857545,&quot;forks&quot;:88,&quot;name&quot;:&quot;symfony/translation&quot;,&quot;percentage&quot;:0.42219305727171874,&quot;stars&quot;:6620},{&quot;dependents&quot;:10,&quot;downloads_monthly&quot;:11849916,&quot;forks&quot;:3,&quot;name&quot;:&quot;sebastian/object-reflector&quot;,&quot;percentage&quot;:0.42192142340198213,&quot;stars&quot;:6257},{&quot;dependents&quot;:728,&quot;downloads_monthly&quot;:11845211,&quot;forks&quot;:38,&quot;name&quot;:&quot;symfony/mime&quot;,&quot;percentage&quot;:0.42175389982653183,&quot;stars&quot;:2794},{&quot;dependents&quot;:38,&quot;downloads_monthly&quot;:11771140,&quot;forks&quot;:16,&quot;name&quot;:&quot;phar-io/version&quot;,&quot;percentage&quot;:0.4191165695912113,&quot;stars&quot;:7429},{&quot;dependents&quot;:1585,&quot;downloads_monthly&quot;:11769804,&quot;forks&quot;:96,&quot;name&quot;:&quot;symfony/routing&quot;,&quot;percentage&quot;:0.41906900072897924,&quot;stars&quot;:7623},{&quot;dependents&quot;:1891,&quot;downloads_monthly&quot;:11525601,&quot;forks&quot;:52,&quot;name&quot;:&quot;psr/simple-cache&quot;,&quot;percentage&quot;:0.41037404648972275,&quot;stars&quot;:8104},{&quot;dependents&quot;:17,&quot;downloads_monthly&quot;:11466895,&quot;forks&quot;:13,&quot;name&quot;:&quot;sebastian/type&quot;,&quot;percentage&quot;:0.4082837937755063,&quot;stars&quot;:1434},{&quot;dependents&quot;:787,&quot;downloads_monthly&quot;:11455405,&quot;forks&quot;:135,&quot;name&quot;:&quot;doctrine/inflector&quot;,&quot;percentage&quot;:0.40787468731813653,&quot;stars&quot;:11290},{&quot;dependents&quot;:1250,&quot;downloads_monthly&quot;:11178404,&quot;forks&quot;:45,&quot;name&quot;:&quot;psr/cache&quot;,&quot;percentage&quot;:0.39801194599543244,&quot;stars&quot;:5138},{&quot;dependents&quot;:287,&quot;downloads_monthly&quot;:11165847,&quot;forks&quot;:217,&quot;name&quot;:&quot;egulias/email-validator&quot;,&quot;percentage&quot;:0.3975648485380616,&quot;stars&quot;:11517},{&quot;dependents&quot;:29,&quot;downloads_monthly&quot;:11116948,&quot;forks&quot;:5,&quot;name&quot;:&quot;sebastian/cli-parser&quot;,&quot;percentage&quot;:0.3958237783327594,&quot;stars&quot;:1132},{&quot;dependents&quot;:2628,&quot;downloads_monthly&quot;:11103163,&quot;forks&quot;:46,&quot;name&quot;:&quot;symfony/css-selector&quot;,&quot;percentage&quot;:0.3953329574002231,&quot;stars&quot;:7440},{&quot;dependents&quot;:9,&quot;downloads_monthly&quot;:11054904,&quot;forks&quot;:9,&quot;name&quot;:&quot;sebastian/complexity&quot;,&quot;percentage&quot;:0.39361467467383454,&quot;stars&quot;:1183},{&quot;dependents&quot;:11,&quot;downloads_monthly&quot;:11036131,&quot;forks&quot;:7,&quot;name&quot;:&quot;sebastian/lines-of-code&quot;,&quot;percentage&quot;:0.3929462538275158,&quot;stars&quot;:1101},{&quot;dependents&quot;:349,&quot;downloads_monthly&quot;:10990295,&quot;forks&quot;:18,&quot;name&quot;:&quot;phpunit/php-invoker&quot;,&quot;percentage&quot;:0.3913142430720764,&quot;stars&quot;:1256},{&quot;dependents&quot;:3372,&quot;downloads_monthly&quot;:10973720,&quot;forks&quot;:502,&quot;name&quot;:&quot;ramsey/uuid&quot;,&quot;percentage&quot;:0.3907240829736514,&quot;stars&quot;:12515},{&quot;dependents&quot;:13,&quot;downloads_monthly&quot;:10961496,&quot;forks&quot;:8,&quot;name&quot;:&quot;sebastian/code-unit&quot;,&quot;percentage&quot;:0.39028884212640275,&quot;stars&quot;:1131},{&quot;dependents&quot;:206,&quot;downloads_monthly&quot;:10904701,&quot;forks&quot;:6,&quot;name&quot;:&quot;psr/clock&quot;,&quot;percentage&quot;:0.3882666314000047,&quot;stars&quot;:526},{&quot;dependents&quot;:26,&quot;downloads_monthly&quot;:10675187,&quot;forks&quot;:18,&quot;name&quot;:&quot;doctrine/deprecations&quot;,&quot;percentage&quot;:0.3800946854072498,&quot;stars&quot;:1707},{&quot;dependents&quot;:203,&quot;downloads_monthly&quot;:10551624,&quot;forks&quot;:79,&quot;name&quot;:&quot;brick/math&quot;,&quot;percentage&quot;:0.3756951709431963,&quot;stars&quot;:1907},{&quot;dependents&quot;:67,&quot;downloads_monthly&quot;:10391793,&quot;forks&quot;:57,&quot;name&quot;:&quot;ramsey/collection&quot;,&quot;percentage&quot;:0.3700043185334609,&quot;stars&quot;:1153},{&quot;dependents&quot;:4857,&quot;downloads_monthly&quot;:10090649,&quot;forks&quot;:6,&quot;name&quot;:&quot;nesbot/carbon&quot;,&quot;percentage&quot;:0.3592819551741791,&quot;stars&quot;:48},{&quot;dependents&quot;:95,&quot;downloads_monthly&quot;:9842084,&quot;forks&quot;:3,&quot;name&quot;:&quot;symfony/polyfill-php83&quot;,&quot;percentage&quot;:0.3504316900239524,&quot;stars&quot;:357},{&quot;dependents&quot;:2226,&quot;downloads_monthly&quot;:9684327,&quot;forks&quot;:833,&quot;name&quot;:&quot;league/flysystem&quot;,&quot;percentage&quot;:0.34481468328807124,&quot;stars&quot;:13410},{&quot;dependents&quot;:93,&quot;downloads_monthly&quot;:9632153,&quot;forks&quot;:21,&quot;name&quot;:&quot;league/mime-type-detection&quot;,&quot;percentage&quot;:0.34295700528051615,&quot;stars&quot;:1300},{&quot;dependents&quot;:786,&quot;downloads_monthly&quot;:9592543,&quot;forks&quot;:48,&quot;name&quot;:&quot;symfony/mailer&quot;,&quot;percentage&quot;:0.34154667396838256,&quot;stars&quot;:1527},{&quot;dependents&quot;:349,&quot;downloads_monthly&quot;:9427651,&quot;forks&quot;:62,&quot;name&quot;:&quot;doctrine/instantiator&quot;,&quot;percentage&quot;:0.33567562244805116,&quot;stars&quot;:10975},{&quot;dependents&quot;:3125,&quot;downloads_monthly&quot;:9249313,&quot;forks&quot;:50,&quot;name&quot;:&quot;symfony/filesystem&quot;,&quot;percentage&quot;:0.3293258202379205,&quot;stars&quot;:4621},{&quot;dependents&quot;:5597,&quot;downloads_monthly&quot;:8979714,&quot;forks&quot;:638,&quot;name&quot;:&quot;vlucas/phpdotenv&quot;,&quot;percentage&quot;:0.31972663035102583,&quot;stars&quot;:13265},{&quot;dependents&quot;:167,&quot;downloads_monthly&quot;:8902122,&quot;forks&quot;:63,&quot;name&quot;:&quot;phpoption/phpoption&quot;,&quot;percentage&quot;:0.31696393337624507,&quot;stars&quot;:2644},{&quot;dependents&quot;:801,&quot;downloads_monthly&quot;:8692473,&quot;forks&quot;:313,&quot;name&quot;:&quot;psy/psysh&quot;,&quot;percentage&quot;:0.3094992893657051,&quot;stars&quot;:9767},{&quot;dependents&quot;:407,&quot;downloads_monthly&quot;:8569485,&quot;forks&quot;:340,&quot;name&quot;:&quot;dragonmantank/cron-expression&quot;,&quot;percentage&quot;:0.3051202480272379,&quot;stars&quot;:4594},{&quot;dependents&quot;:1604,&quot;downloads_monthly&quot;:8458789,&quot;forks&quot;:149,&quot;name&quot;:&quot;nette/utils&quot;,&quot;percentage&quot;:0.3011788687056541,&quot;stars&quot;:2034},{&quot;dependents&quot;:3,&quot;downloads_monthly&quot;:8446563,&quot;forks&quot;:2,&quot;name&quot;:&quot;carbonphp/carbon-doctrine-types&quot;,&quot;percentage&quot;:0.30074355664753377,&quot;stars&quot;:156},{&quot;dependents&quot;:236,&quot;downloads_monthly&quot;:8382399,&quot;forks&quot;:185,&quot;name&quot;:&quot;tijsverkoyen/css-to-inline-styles&quot;,&quot;percentage&quot;:0.29845896946470774,&quot;stars&quot;:5826},{&quot;dependents&quot;:119,&quot;downloads_monthly&quot;:8380919,&quot;forks&quot;:35,&quot;name&quot;:&quot;voku/portable-ascii&quot;,&quot;percentage&quot;:0.29840627341972015,&quot;stars&quot;:555},{&quot;dependents&quot;:574,&quot;downloads_monthly&quot;:8376172,&quot;forks&quot;:74,&quot;name&quot;:&quot;composer/semver&quot;,&quot;percentage&quot;:0.29823725441596616,&quot;stars&quot;:3195},{&quot;dependents&quot;:2886,&quot;downloads_monthly&quot;:8333861,&quot;forks&quot;:3584,&quot;name&quot;:&quot;fakerphp/faker&quot;,&quot;percentage&quot;:0.2967307528217303,&quot;stars&quot;:3696},{&quot;dependents&quot;:4,&quot;downloads_monthly&quot;:8301890,&quot;forks&quot;:7,&quot;name&quot;:&quot;graham-campbell/result-type&quot;,&quot;percentage&quot;:0.2955924114336913,&quot;stars&quot;:504},{&quot;dependents&quot;:440,&quot;downloads_monthly&quot;:8244296,&quot;forks&quot;:146,&quot;name&quot;:&quot;paragonie/random_compat&quot;,&quot;percentage&quot;:0.29354175196408716,&quot;stars&quot;:8178},{&quot;dependents&quot;:162,&quot;downloads_monthly&quot;:8204645,&quot;forks&quot;:35,&quot;name&quot;:&quot;laravel/serializable-closure&quot;,&quot;percentage&quot;:0.2921299608290857,&quot;stars&quot;:550},{&quot;dependents&quot;:79,&quot;downloads_monthly&quot;:8159550,&quot;forks&quot;:18,&quot;name&quot;:&quot;dflydev/dot-access-data&quot;,&quot;percentage&quot;:0.29052433370157593,&quot;stars&quot;:666},{&quot;dependents&quot;:1627,&quot;downloads_monthly&quot;:8107720,&quot;forks&quot;:32,&quot;name&quot;:&quot;symfony/options-resolver&quot;,&quot;percentage&quot;:0.28867890396393686,&quot;stars&quot;:3222},{&quot;dependents&quot;:125,&quot;downloads_monthly&quot;:8060636,&quot;forks&quot;:50,&quot;name&quot;:&quot;phpdocumentor/type-resolver&quot;,&quot;percentage&quot;:0.28700245762461607,&quot;stars&quot;:9161},{&quot;dependents&quot;:337,&quot;downloads_monthly&quot;:8021326,&quot;forks&quot;:64,&quot;name&quot;:&quot;phpstan/phpdoc-parser&quot;,&quot;percentage&quot;:0.28560280794322324,&quot;stars&quot;:1398},{&quot;dependents&quot;:172,&quot;downloads_monthly&quot;:8006742,&quot;forks&quot;:9,&quot;name&quot;:&quot;symfony/polyfill-php81&quot;,&quot;percentage&quot;:0.28508353826748084,&quot;stars&quot;:877},{&quot;dependents&quot;:56,&quot;downloads_monthly&quot;:7986881,&quot;forks&quot;:2,&quot;name&quot;:&quot;symfony/polyfill-uuid&quot;,&quot;percentage&quot;:0.28437637870700916,&quot;stars&quot;:657},{&quot;dependents&quot;:5761,&quot;downloads_monthly&quot;:7955368,&quot;forks&quot;:1353,&quot;name&quot;:&quot;doctrine/dbal&quot;,&quot;percentage&quot;:0.2832543446085677,&quot;stars&quot;:9554},{&quot;dependents&quot;:26,&quot;downloads_monthly&quot;:7936497,&quot;forks&quot;:21,&quot;name&quot;:&quot;phpdocumentor/reflection-common&quot;,&quot;percentage&quot;:0.28258243442954034,&quot;stars&quot;:9045},{&quot;dependents&quot;:571,&quot;downloads_monthly&quot;:7914641,&quot;forks&quot;:195,&quot;name&quot;:&quot;league/commonmark&quot;,&quot;percentage&quot;:0.2818042420246428,&quot;stars&quot;:2807},{&quot;dependents&quot;:490,&quot;downloads_monthly&quot;:7873090,&quot;forks&quot;:12,&quot;name&quot;:&quot;symfony/uid&quot;,&quot;percentage&quot;:0.2803248005616168,&quot;stars&quot;:568},{&quot;dependents&quot;:17310,&quot;downloads_monthly&quot;:7860661,&quot;forks&quot;:11237,&quot;name&quot;:&quot;laravel/framework&quot;,&quot;percentage&quot;:0.2798822606000286,&quot;stars&quot;:33237},{&quot;dependents&quot;:101,&quot;downloads_monthly&quot;:7839875,&quot;forks&quot;:26,&quot;name&quot;:&quot;nette/schema&quot;,&quot;percentage&quot;:0.27914216601143965,&quot;stars&quot;:939},{&quot;dependents&quot;:29,&quot;downloads_monthly&quot;:7828016,&quot;forks&quot;:14,&quot;name&quot;:&quot;composer/pcre&quot;,&quot;percentage&quot;:0.27871992114825883,&quot;stars&quot;:597},{&quot;dependents&quot;:24273,&quot;downloads_monthly&quot;:7824504,&quot;forks&quot;:457,&quot;name&quot;:&quot;mockery/mockery&quot;,&quot;percentage&quot;:0.2785948748577208,&quot;stars&quot;:10661},{&quot;dependents&quot;:98,&quot;downloads_monthly&quot;:7784794,&quot;forks&quot;:22,&quot;name&quot;:&quot;doctrine/event-manager&quot;,&quot;percentage&quot;:0.27718098300200694,&quot;stars&quot;:5964},{&quot;dependents&quot;:2006,&quot;downloads_monthly&quot;:7741281,&quot;forks&quot;:1419,&quot;name&quot;:&quot;firebase/php-jwt&quot;,&quot;percentage&quot;:0.2756316836739366,&quot;stars&quot;:9526},{&quot;dependents&quot;:115,&quot;downloads_monthly&quot;:7730385,&quot;forks&quot;:43,&quot;name&quot;:&quot;hamcrest/hamcrest-php&quot;,&quot;percentage&quot;:0.2752437268454335,&quot;stars&quot;:6970},{&quot;dependents&quot;:2259,&quot;downloads_monthly&quot;:7724250,&quot;forks&quot;:1228,&quot;name&quot;:&quot;aws/aws-sdk-php&quot;,&quot;percentage&quot;:0.2750252874967857,&quot;stars&quot;:6082},{&quot;dependents&quot;:289,&quot;downloads_monthly&quot;:7717192,&quot;forks&quot;:7,&quot;name&quot;:&quot;symfony/var-exporter&quot;,&quot;percentage&quot;:0.2747739843308923,&quot;stars&quot;:2065},{&quot;dependents&quot;:136,&quot;downloads_monthly&quot;:7656513,&quot;forks&quot;:54,&quot;name&quot;:&quot;mtdowling/jmespath.php&quot;,&quot;percentage&quot;:0.27261348209183767,&quot;stars&quot;:1954},{&quot;dependents&quot;:16,&quot;downloads_monthly&quot;:7539219,&quot;forks&quot;:13,&quot;name&quot;:&quot;sebastian/resource-operations&quot;,&quot;percentage&quot;:0.2684371781048295,&quot;stars&quot;:6281},{&quot;dependents&quot;:9,&quot;downloads_monthly&quot;:7538044,&quot;forks&quot;:2,&quot;name&quot;:&quot;league/flysystem-local&quot;,&quot;percentage&quot;:0.26839534171776164,&quot;stars&quot;:180},{&quot;dependents&quot;:1953,&quot;downloads_monthly&quot;:7438510,&quot;forks&quot;:128,&quot;name&quot;:&quot;laravel/tinker&quot;,&quot;percentage&quot;:0.2648513902706043,&quot;stars&quot;:7363},{&quot;dependents&quot;:1113,&quot;downloads_monthly&quot;:7415634,&quot;forks&quot;:124,&quot;name&quot;:&quot;phpdocumentor/reflection-docblock&quot;,&quot;percentage&quot;:0.2640368803211883,&quot;stars&quot;:9360},{&quot;dependents&quot;:24,&quot;downloads_monthly&quot;:7359474,&quot;forks&quot;:14,&quot;name&quot;:&quot;league/config&quot;,&quot;percentage&quot;:0.2620372790465248,&quot;stars&quot;:519},{&quot;dependents&quot;:1173,&quot;downloads_monthly&quot;:7354190,&quot;forks&quot;:214,&quot;name&quot;:&quot;doctrine/cache&quot;,&quot;percentage&quot;:0.26184913992374487,&quot;stars&quot;:7868},{&quot;dependents&quot;:139,&quot;downloads_monthly&quot;:7129546,&quot;forks&quot;:35,&quot;name&quot;:&quot;paragonie/constant_time_encoding&quot;,&quot;percentage&quot;:0.2538505924033477,&quot;stars&quot;:836},{&quot;dependents&quot;:1250,&quot;downloads_monthly&quot;:7106394,&quot;forks&quot;:898,&quot;name&quot;:&quot;phpseclib/phpseclib&quot;,&quot;percentage&quot;:0.25302625535365025,&quot;stars&quot;:5434},{&quot;dependents&quot;:4,&quot;downloads_monthly&quot;:7066887,&quot;forks&quot;:13,&quot;name&quot;:&quot;aws/aws-crt-php&quot;,&quot;percentage&quot;:0.25161959140140433,&quot;stars&quot;:367},{&quot;dependents&quot;:1778,&quot;downloads_monthly&quot;:7032322,&quot;forks&quot;:602,&quot;name&quot;:&quot;filp/whoops&quot;,&quot;percentage&quot;:0.25038888951289395,&quot;stars&quot;:13218},{&quot;dependents&quot;:23015,&quot;downloads_monthly&quot;:6957511,&quot;forks&quot;:910,&quot;name&quot;:&quot;phpstan/phpstan&quot;,&quot;percentage&quot;:0.24772521125507965,&quot;stars&quot;:13240},{&quot;dependents&quot;:55,&quot;downloads_monthly&quot;:6945810,&quot;forks&quot;:8,&quot;name&quot;:&quot;guzzlehttp/uri-template&quot;,&quot;percentage&quot;:0.24730859205075564,&quot;stars&quot;:154},{&quot;dependents&quot;:251,&quot;downloads_monthly&quot;:6940907,&quot;forks&quot;:85,&quot;name&quot;:&quot;nunomaduro/termwind&quot;,&quot;percentage&quot;:0.24713401859901638,&quot;stars&quot;:2377},{&quot;dependents&quot;:6033,&quot;downloads_monthly&quot;:6704982,&quot;forks&quot;:42,&quot;name&quot;:&quot;symfony/config&quot;,&quot;percentage&quot;:0.23873380615733217,&quot;stars&quot;:4248},{&quot;dependents&quot;:308,&quot;downloads_monthly&quot;:6688929,&quot;forks&quot;:98,&quot;name&quot;:&quot;laravel/prompts&quot;,&quot;percentage&quot;:0.23816223209639606,&quot;stars&quot;:562},{&quot;dependents&quot;:37,&quot;downloads_monthly&quot;:6671598,&quot;forks&quot;:15,&quot;name&quot;:&quot;fruitcake/php-cors&quot;,&quot;percentage&quot;:0.23754515428850445,&quot;stars&quot;:261},{&quot;dependents&quot;:6775,&quot;downloads_monthly&quot;:6657245,&quot;forks&quot;:94,&quot;name&quot;:&quot;symfony/dependency-injection&quot;,&quot;percentage&quot;:0.2370341094684324,&quot;stars&quot;:4130},{&quot;dependents&quot;:144,&quot;downloads_monthly&quot;:6615220,&quot;forks&quot;:3,&quot;name&quot;:&quot;symfony/http-client-contracts&quot;,&quot;percentage&quot;:0.23553779102883604,&quot;stars&quot;:1952},{&quot;dependents&quot;:84,&quot;downloads_monthly&quot;:6600274,&quot;forks&quot;:8,&quot;name&quot;:&quot;symfony/polyfill-php73&quot;,&quot;percentage&quot;:0.23500563218533319,&quot;stars&quot;:2399},{&quot;dependents&quot;:95,&quot;downloads_monthly&quot;:6543673,&quot;forks&quot;:3,&quot;name&quot;:&quot;symfony/clock&quot;,&quot;percentage&quot;:0.23299032891348082,&quot;stars&quot;:353},{&quot;dependents&quot;:17,&quot;downloads_monthly&quot;:6508996,&quot;forks&quot;:7,&quot;name&quot;:&quot;league/uri-interfaces&quot;,&quot;percentage&quot;:0.23175563921616055,&quot;stars&quot;:482},{&quot;dependents&quot;:2217,&quot;downloads_monthly&quot;:6484555,&quot;forks&quot;:60,&quot;name&quot;:&quot;symfony/cache&quot;,&quot;percentage&quot;:0.23088540675971378,&quot;stars&quot;:4125},{&quot;dependents&quot;:253,&quot;downloads_monthly&quot;:6472130,&quot;forks&quot;:66,&quot;name&quot;:&quot;league/uri&quot;,&quot;percentage&quot;:0.2304430092198688,&quot;stars&quot;:1060},{&quot;dependents&quot;:2361,&quot;downloads_monthly&quot;:6438522,&quot;forks&quot;:235,&quot;name&quot;:&quot;doctrine/annotations&quot;,&quot;percentage&quot;:0.22924638173342132,&quot;stars&quot;:6742}],&quot;threshold&quot;:1404279961,&quot;total_monthly_downloads&quot;:2808559922},&quot;empty_packages&quot;:94911,&quot;loc_packages&quot;:{&quot;classes&quot;:8563791,&quot;classes_loc&quot;:0,&quot;cloc&quot;:164548311,&quot;downloads_monthly&quot;:2601999280,&quot;downloads_total&quot;:123822717088,&quot;files&quot;:5558384,&quot;forks&quot;:2414,&quot;interfaces&quot;:11100944,&quot;lloc&quot;:362889228,&quot;loc&quot;:604562538,&quot;open_issues&quot;:444893,&quot;stars&quot;:29,&quot;total_packages&quot;:513863},&quot;packages_needing_help&quot;:[{&quot;bus_factor&quot;:1,&quot;classes&quot;:10,&quot;downloads_monthly&quot;:559298,&quot;downloads_total&quot;:25513726,&quot;files&quot;:21,&quot;forks&quot;:137,&quot;name&quot;:&quot;orchestra/testbench&quot;,&quot;stars&quot;:2147,&quot;usage&quot;:26435},{&quot;bus_factor&quot;:1,&quot;classes&quot;:9,&quot;downloads_monthly&quot;:11919219,&quot;downloads_total&quot;:627915019,&quot;files&quot;:10,&quot;forks&quot;:130,&quot;name&quot;:&quot;symfony/yaml&quot;,&quot;stars&quot;:3826,&quot;usage&quot;:9438},{&quot;bus_factor&quot;:0,&quot;classes&quot;:19,&quot;downloads_monthly&quot;:15188541,&quot;downloads_total&quot;:963431048,&quot;files&quot;:20,&quot;forks&quot;:182,&quot;name&quot;:&quot;psr/log&quot;,&quot;stars&quot;:10421,&quot;usage&quot;:8736},{&quot;bus_factor&quot;:0,&quot;classes&quot;:17,&quot;downloads_monthly&quot;:2126980,&quot;downloads_total&quot;:96035049,&quot;files&quot;:146,&quot;forks&quot;:82,&quot;name&quot;:&quot;illuminate/contracts&quot;,&quot;stars&quot;:682,&quot;usage&quot;:8216},{&quot;bus_factor&quot;:0,&quot;classes&quot;:1,&quot;downloads_monthly&quot;:2126980,&quot;downloads_total&quot;:96035049,&quot;files&quot;:7,&quot;forks&quot;:82,&quot;name&quot;:&quot;illuminate/contracts&quot;,&quot;stars&quot;:682,&quot;usage&quot;:8216},{&quot;bus_factor&quot;:2,&quot;classes&quot;:62,&quot;downloads_monthly&quot;:12977531,&quot;downloads_total&quot;:678659761,&quot;files&quot;:68,&quot;forks&quot;:103,&quot;name&quot;:&quot;symfony/var-dumper&quot;,&quot;stars&quot;:7426,&quot;usage&quot;:7637},{&quot;bus_factor&quot;:0,&quot;classes&quot;:276,&quot;downloads_monthly&quot;:1896963,&quot;downloads_total&quot;:109882055,&quot;files&quot;:298,&quot;forks&quot;:412,&quot;name&quot;:&quot;composer/installers&quot;,&quot;stars&quot;:1437,&quot;usage&quot;:7264},{&quot;bus_factor&quot;:1,&quot;classes&quot;:33,&quot;downloads_monthly&quot;:1896963,&quot;downloads_total&quot;:109882055,&quot;files&quot;:33,&quot;forks&quot;:412,&quot;name&quot;:&quot;composer/installers&quot;,&quot;stars&quot;:1437,&quot;usage&quot;:7264},{&quot;bus_factor&quot;:1,&quot;classes&quot;:80,&quot;downloads_monthly&quot;:6432473,&quot;downloads_total&quot;:244614019,&quot;files&quot;:71,&quot;forks&quot;:164,&quot;name&quot;:&quot;nunomaduro/collision&quot;,&quot;stars&quot;:4552,&quot;usage&quot;:6700},{&quot;bus_factor&quot;:1,&quot;classes&quot;:24,&quot;downloads_monthly&quot;:2205651,&quot;downloads_total&quot;:67589115,&quot;files&quot;:24,&quot;forks&quot;:48,&quot;name&quot;:&quot;phpstan/phpstan-phpunit&quot;,&quot;stars&quot;:480,&quot;usage&quot;:6550},{&quot;bus_factor&quot;:1,&quot;classes&quot;:7,&quot;downloads_monthly&quot;:2355242,&quot;downloads_total&quot;:62115058,&quot;files&quot;:11,&quot;forks&quot;:27,&quot;name&quot;:&quot;phpstan/extension-installer&quot;,&quot;stars&quot;:446,&quot;usage&quot;:6404},{&quot;bus_factor&quot;:0,&quot;classes&quot;:0,&quot;downloads_monthly&quot;:1486528,&quot;downloads_total&quot;:75758927,&quot;files&quot;:0,&quot;forks&quot;:106,&quot;name&quot;:&quot;roave/security-advisories&quot;,&quot;stars&quot;:2756,&quot;usage&quot;:6377},{&quot;bus_factor&quot;:1,&quot;classes&quot;:71,&quot;downloads_monthly&quot;:6704982,&quot;downloads_total&quot;:368966055,&quot;files&quot;:89,&quot;forks&quot;:42,&quot;name&quot;:&quot;symfony/config&quot;,&quot;stars&quot;:4248,&quot;usage&quot;:6033},{&quot;bus_factor&quot;:1,&quot;classes&quot;:33,&quot;downloads_monthly&quot;:8979714,&quot;downloads_total&quot;:474766398,&quot;files&quot;:41,&quot;forks&quot;:638,&quot;name&quot;:&quot;vlucas/phpdotenv&quot;,&quot;stars&quot;:13265,&quot;usage&quot;:5597},{&quot;bus_factor&quot;:2,&quot;classes&quot;:20,&quot;downloads_monthly&quot;:14087178,&quot;downloads_total&quot;:785182390,&quot;files&quot;:22,&quot;forks&quot;:104,&quot;name&quot;:&quot;symfony/process&quot;,&quot;stars&quot;:7453,&quot;usage&quot;:5283},{&quot;bus_factor&quot;:0,&quot;classes&quot;:3,&quot;downloads_monthly&quot;:14537371,&quot;downloads_total&quot;:823272846,&quot;files&quot;:10,&quot;forks&quot;:186,&quot;name&quot;:&quot;psr/http-message&quot;,&quot;stars&quot;:6998,&quot;usage&quot;:5164},{&quot;bus_factor&quot;:1,&quot;classes&quot;:21,&quot;downloads_monthly&quot;:2008908,&quot;downloads_total&quot;:49058927,&quot;files&quot;:22,&quot;forks&quot;:19,&quot;name&quot;:&quot;phpstan/phpstan-deprecation-rules&quot;,&quot;stars&quot;:389,&quot;usage&quot;:4862},{&quot;bus_factor&quot;:1,&quot;classes&quot;:17,&quot;downloads_monthly&quot;:3369301,&quot;downloads_total&quot;:71591818,&quot;files&quot;:19,&quot;forks&quot;:147,&quot;name&quot;:&quot;spatie/laravel-package-tools&quot;,&quot;stars&quot;:828,&quot;usage&quot;:4850},{&quot;bus_factor&quot;:1,&quot;classes&quot;:6,&quot;downloads_monthly&quot;:1203781,&quot;downloads_total&quot;:18818296,&quot;files&quot;:19,&quot;forks&quot;:42,&quot;name&quot;:&quot;pestphp/pest-plugin-laravel&quot;,&quot;stars&quot;:191,&quot;usage&quot;:4724},{&quot;bus_factor&quot;:0,&quot;classes&quot;:1,&quot;downloads_monthly&quot;:11080,&quot;downloads_total&quot;:5004008,&quot;files&quot;:1,&quot;forks&quot;:101,&quot;name&quot;:&quot;satooshi/php-coveralls&quot;,&quot;stars&quot;:455,&quot;usage&quot;:4558},{&quot;bus_factor&quot;:0,&quot;classes&quot;:95,&quot;downloads_monthly&quot;:555231,&quot;downloads_total&quot;:36257490,&quot;files&quot;:104,&quot;forks&quot;:26,&quot;name&quot;:&quot;illuminate/console&quot;,&quot;stars&quot;:124,&quot;usage&quot;:4445},{&quot;bus_factor&quot;:0,&quot;classes&quot;:2,&quot;downloads_monthly&quot;:555231,&quot;downloads_total&quot;:36257490,&quot;files&quot;:2,&quot;forks&quot;:26,&quot;name&quot;:&quot;illuminate/console&quot;,&quot;stars&quot;:124,&quot;usage&quot;:4445},{&quot;bus_factor&quot;:1,&quot;classes&quot;:26,&quot;downloads_monthly&quot;:2876824,&quot;downloads_total&quot;:163123088,&quot;files&quot;:24,&quot;forks&quot;:47,&quot;name&quot;:&quot;symfony/phpunit-bridge&quot;,&quot;stars&quot;:2460,&quot;usage&quot;:4237},{&quot;bus_factor&quot;:0,&quot;classes&quot;:0,&quot;downloads_monthly&quot;:433617,&quot;downloads_total&quot;:29675737,&quot;files&quot;:0,&quot;forks&quot;:37,&quot;name&quot;:&quot;illuminate/http&quot;,&quot;stars&quot;:114,&quot;usage&quot;:3962},{&quot;bus_factor&quot;:2,&quot;classes&quot;:23,&quot;downloads_monthly&quot;:13973488,&quot;downloads_total&quot;:807162519,&quot;files&quot;:23,&quot;forks&quot;:55,&quot;name&quot;:&quot;symfony/finder&quot;,&quot;stars&quot;:8436,&quot;usage&quot;:3936},{&quot;bus_factor&quot;:1,&quot;classes&quot;:235,&quot;downloads_monthly&quot;:264604,&quot;downloads_total&quot;:33391615,&quot;files&quot;:282,&quot;forks&quot;:280,&quot;name&quot;:&quot;phpspec/phpspec&quot;,&quot;stars&quot;:1888,&quot;usage&quot;:3851},{&quot;bus_factor&quot;:0,&quot;classes&quot;:66,&quot;downloads_monthly&quot;:3096800,&quot;downloads_total&quot;:198345829,&quot;files&quot;:67,&quot;forks&quot;:454,&quot;name&quot;:&quot;doctrine/doctrine-bundle&quot;,&quot;stars&quot;:4736,&quot;usage&quot;:3594},{&quot;bus_factor&quot;:0,&quot;classes&quot;:0,&quot;downloads_monthly&quot;:3096800,&quot;downloads_total&quot;:198345829,&quot;files&quot;:0,&quot;forks&quot;:454,&quot;name&quot;:&quot;doctrine/doctrine-bundle&quot;,&quot;stars&quot;:4736,&quot;usage&quot;:3594},{&quot;bus_factor&quot;:1,&quot;classes&quot;:2,&quot;downloads_monthly&quot;:15042293,&quot;downloads_total&quot;:803012501,&quot;files&quot;:3,&quot;forks&quot;:51,&quot;name&quot;:&quot;psr/container&quot;,&quot;stars&quot;:9981,&quot;usage&quot;:3581},{&quot;bus_factor&quot;:1,&quot;classes&quot;:90,&quot;downloads_monthly&quot;:10973720,&quot;downloads_total&quot;:545844010,&quot;files&quot;:114,&quot;forks&quot;:502,&quot;name&quot;:&quot;ramsey/uuid&quot;,&quot;stars&quot;:12515,&quot;usage&quot;:3372},{&quot;bus_factor&quot;:1,&quot;classes&quot;:44,&quot;downloads_monthly&quot;:1084250,&quot;downloads_total&quot;:39273399,&quot;files&quot;:44,&quot;forks&quot;:49,&quot;name&quot;:&quot;phpstan/phpstan-strict-rules&quot;,&quot;stars&quot;:632,&quot;usage&quot;:3263},{&quot;bus_factor&quot;:1,&quot;classes&quot;:23,&quot;downloads_monthly&quot;:13306972,&quot;downloads_total&quot;:801654417,&quot;files&quot;:30,&quot;forks&quot;:67,&quot;name&quot;:&quot;symfony/event-dispatcher&quot;,&quot;stars&quot;:8534,&quot;usage&quot;:3263},{&quot;bus_factor&quot;:2,&quot;classes&quot;:6,&quot;downloads_monthly&quot;:9249313,&quot;downloads_total&quot;:540730584,&quot;files&quot;:8,&quot;forks&quot;:50,&quot;name&quot;:&quot;symfony/filesystem&quot;,&quot;stars&quot;:4621,&quot;usage&quot;:3125},{&quot;bus_factor&quot;:0,&quot;classes&quot;:44,&quot;downloads_monthly&quot;:1521225,&quot;downloads_total&quot;:85396918,&quot;files&quot;:53,&quot;forks&quot;:102,&quot;name&quot;:&quot;mikey179/vfsstream&quot;,&quot;stars&quot;:1423,&quot;usage&quot;:3010},{&quot;bus_factor&quot;:0,&quot;classes&quot;:33,&quot;downloads_monthly&quot;:14212004,&quot;downloads_total&quot;:814214923,&quot;files&quot;:35,&quot;forks&quot;:295,&quot;name&quot;:&quot;guzzlehttp/psr7&quot;,&quot;stars&quot;:7905,&quot;usage&quot;:2970},{&quot;bus_factor&quot;:1,&quot;classes&quot;:31,&quot;downloads_monthly&quot;:14212004,&quot;downloads_total&quot;:814214923,&quot;files&quot;:32,&quot;forks&quot;:295,&quot;name&quot;:&quot;guzzlehttp/psr7&quot;,&quot;stars&quot;:7905,&quot;usage&quot;:2970},{&quot;bus_factor&quot;:0,&quot;classes&quot;:249,&quot;downloads_monthly&quot;:2511894,&quot;downloads_total&quot;:154249705,&quot;files&quot;:280,&quot;forks&quot;:4596,&quot;name&quot;:&quot;composer/composer&quot;,&quot;stars&quot;:28836,&quot;usage&quot;:2847},{&quot;bus_factor&quot;:0,&quot;classes&quot;:0,&quot;downloads_monthly&quot;:2511894,&quot;downloads_total&quot;:154249705,&quot;files&quot;:0,&quot;forks&quot;:4596,&quot;name&quot;:&quot;composer/composer&quot;,&quot;stars&quot;:28836,&quot;usage&quot;:2847},{&quot;bus_factor&quot;:0,&quot;classes&quot;:0,&quot;downloads_monthly&quot;:6330192,&quot;downloads_total&quot;:232554721,&quot;files&quot;:0,&quot;forks&quot;:50,&quot;name&quot;:&quot;symfony/http-client&quot;,&quot;stars&quot;:1979,&quot;usage&quot;:2631},{&quot;bus_factor&quot;:1,&quot;classes&quot;:44,&quot;downloads_monthly&quot;:11103163,&quot;downloads_total&quot;:605685717,&quot;files&quot;:50,&quot;forks&quot;:46,&quot;name&quot;:&quot;symfony/css-selector&quot;,&quot;stars&quot;:7440,&quot;usage&quot;:2628},{&quot;bus_factor&quot;:2,&quot;classes&quot;:13,&quot;downloads_monthly&quot;:4267067,&quot;downloads_total&quot;:226331863,&quot;files&quot;:14,&quot;forks&quot;:59,&quot;name&quot;:&quot;symfony/browser-kit&quot;,&quot;stars&quot;:2977,&quot;usage&quot;:2568},{&quot;bus_factor&quot;:0,&quot;classes&quot;:307,&quot;downloads_monthly&quot;:2814721,&quot;downloads_total&quot;:153068840,&quot;files&quot;:331,&quot;forks&quot;:1495,&quot;name&quot;:&quot;intervention/image&quot;,&quot;stars&quot;:14082,&quot;usage&quot;:2445},{&quot;bus_factor&quot;:1,&quot;classes&quot;:295,&quot;downloads_monthly&quot;:2814721,&quot;downloads_total&quot;:153068840,&quot;files&quot;:331,&quot;forks&quot;:1495,&quot;name&quot;:&quot;intervention/image&quot;,&quot;stars&quot;:14082,&quot;usage&quot;:2445},{&quot;bus_factor&quot;:0,&quot;classes&quot;:11,&quot;downloads_monthly&quot;:915936,&quot;downloads_total&quot;:49510052,&quot;files&quot;:12,&quot;forks&quot;:24,&quot;name&quot;:&quot;illuminate/filesystem&quot;,&quot;stars&quot;:149,&quot;usage&quot;:2439},{&quot;bus_factor&quot;:0,&quot;classes&quot;:0,&quot;downloads_monthly&quot;:915936,&quot;downloads_total&quot;:49510052,&quot;files&quot;:0,&quot;forks&quot;:24,&quot;name&quot;:&quot;illuminate/filesystem&quot;,&quot;stars&quot;:149,&quot;usage&quot;:2439},{&quot;bus_factor&quot;:2,&quot;classes&quot;:21,&quot;downloads_monthly&quot;:5434878,&quot;downloads_total&quot;:309910053,&quot;files&quot;:23,&quot;forks&quot;:125,&quot;name&quot;:&quot;symfony/dom-crawler&quot;,&quot;stars&quot;:3986,&quot;usage&quot;:2412},{&quot;bus_factor&quot;:0,&quot;classes&quot;:18,&quot;downloads_monthly&quot;:514788,&quot;downloads_total&quot;:35489214,&quot;files&quot;:20,&quot;forks&quot;:11,&quot;name&quot;:&quot;illuminate/config&quot;,&quot;stars&quot;:106,&quot;usage&quot;:2330},{&quot;bus_factor&quot;:1,&quot;classes&quot;:27,&quot;downloads_monthly&quot;:514788,&quot;downloads_total&quot;:35489214,&quot;files&quot;:31,&quot;forks&quot;:11,&quot;name&quot;:&quot;illuminate/config&quot;,&quot;stars&quot;:106,&quot;usage&quot;:2330},{&quot;bus_factor&quot;:0,&quot;classes&quot;:0,&quot;downloads_monthly&quot;:1448220,&quot;downloads_total&quot;:16467245,&quot;files&quot;:0,&quot;forks&quot;:19,&quot;name&quot;:&quot;pestphp/pest-plugin-arch&quot;,&quot;stars&quot;:33,&quot;usage&quot;:2328},{&quot;bus_factor&quot;:2,&quot;classes&quot;:6,&quot;downloads_monthly&quot;:4248293,&quot;downloads_total&quot;:173669521,&quot;files&quot;:7,&quot;forks&quot;:29,&quot;name&quot;:&quot;symfony/dotenv&quot;,&quot;stars&quot;:3766,&quot;usage&quot;:2316},{&quot;bus_factor&quot;:1,&quot;classes&quot;:117,&quot;downloads_monthly&quot;:41564,&quot;downloads_total&quot;:3593448,&quot;files&quot;:118,&quot;forks&quot;:12,&quot;name&quot;:&quot;spryker/code-sniffer&quot;,&quot;stars&quot;:36,&quot;usage&quot;:2310},{&quot;bus_factor&quot;:2,&quot;classes&quot;:37,&quot;downloads_monthly&quot;:3347591,&quot;downloads_total&quot;:148387034,&quot;files&quot;:45,&quot;forks&quot;:24,&quot;name&quot;:&quot;symfony/twig-bundle&quot;,&quot;stars&quot;:2494,&quot;usage&quot;:2303},{&quot;bus_factor&quot;:1,&quot;classes&quot;:42,&quot;downloads_monthly&quot;:632848,&quot;downloads_total&quot;:27234750,&quot;files&quot;:46,&quot;forks&quot;:62,&quot;name&quot;:&quot;spatie/laravel-ray&quot;,&quot;stars&quot;:295,&quot;usage&quot;:2301},{&quot;bus_factor&quot;:2,&quot;classes&quot;:159,&quot;downloads_monthly&quot;:9684327,&quot;downloads_total&quot;:503869950,&quot;files&quot;:182,&quot;forks&quot;:833,&quot;name&quot;:&quot;league/flysystem&quot;,&quot;stars&quot;:13410,&quot;usage&quot;:2226},{&quot;bus_factor&quot;:2,&quot;classes&quot;:80,&quot;downloads_monthly&quot;:6484555,&quot;downloads_total&quot;:264779554,&quot;files&quot;:72,&quot;forks&quot;:60,&quot;name&quot;:&quot;symfony/cache&quot;,&quot;stars&quot;:4125,&quot;usage&quot;:2217},{&quot;bus_factor&quot;:0,&quot;classes&quot;:404,&quot;downloads_monthly&quot;:1188011,&quot;downloads_total&quot;:79495397,&quot;files&quot;:523,&quot;forks&quot;:612,&quot;name&quot;:&quot;behat/behat&quot;,&quot;stars&quot;:3919,&quot;usage&quot;:2173},{&quot;bus_factor&quot;:1,&quot;classes&quot;:412,&quot;downloads_monthly&quot;:1188011,&quot;downloads_total&quot;:79495397,&quot;files&quot;:531,&quot;forks&quot;:612,&quot;name&quot;:&quot;behat/behat&quot;,&quot;stars&quot;:3919,&quot;usage&quot;:2173},{&quot;bus_factor&quot;:1,&quot;classes&quot;:16,&quot;downloads_monthly&quot;:4978944,&quot;downloads_total&quot;:228690965,&quot;files&quot;:20,&quot;forks&quot;:24,&quot;name&quot;:&quot;symfony/property-access&quot;,&quot;stars&quot;:2803,&quot;usage&quot;:2159},{&quot;bus_factor&quot;:0,&quot;classes&quot;:16,&quot;downloads_monthly&quot;:1090474,&quot;downloads_total&quot;:63457043,&quot;files&quot;:16,&quot;forks&quot;:60,&quot;name&quot;:&quot;illuminate/container&quot;,&quot;stars&quot;:309,&quot;usage&quot;:2136},{&quot;bus_factor&quot;:1,&quot;classes&quot;:131,&quot;downloads_monthly&quot;:1623976,&quot;downloads_total&quot;:15520324,&quot;files&quot;:132,&quot;forks&quot;:439,&quot;name&quot;:&quot;larastan/larastan&quot;,&quot;stars&quot;:5756,&quot;usage&quot;:2097},{&quot;bus_factor&quot;:1,&quot;classes&quot;:12,&quot;downloads_monthly&quot;:7741281,&quot;downloads_total&quot;:341396244,&quot;files&quot;:14,&quot;forks&quot;:1419,&quot;name&quot;:&quot;firebase/php-jwt&quot;,&quot;stars&quot;:9526,&quot;usage&quot;:2006},{&quot;bus_factor&quot;:2,&quot;classes&quot;:77,&quot;downloads_monthly&quot;:11857545,&quot;downloads_total&quot;:673133390,&quot;files&quot;:95,&quot;forks&quot;:88,&quot;name&quot;:&quot;symfony/translation&quot;,&quot;stars&quot;:6620,&quot;usage&quot;:1954},{&quot;bus_factor&quot;:0,&quot;classes&quot;:10,&quot;downloads_monthly&quot;:7438510,&quot;downloads_total&quot;:322431601,&quot;files&quot;:24,&quot;forks&quot;:128,&quot;name&quot;:&quot;laravel/tinker&quot;,&quot;stars&quot;:7363,&quot;usage&quot;:1953},{&quot;bus_factor&quot;:0,&quot;classes&quot;:71,&quot;downloads_monthly&quot;:130808,&quot;downloads_total&quot;:6916798,&quot;files&quot;:99,&quot;forks&quot;:81,&quot;name&quot;:&quot;illuminate/routing&quot;,&quot;stars&quot;:118,&quot;usage&quot;:1905},{&quot;bus_factor&quot;:1,&quot;classes&quot;:9,&quot;downloads_monthly&quot;:3369607,&quot;downloads_total&quot;:204926356,&quot;files&quot;:10,&quot;forks&quot;:234,&quot;name&quot;:&quot;symfony/monolog-bundle&quot;,&quot;stars&quot;:2902,&quot;usage&quot;:1891},{&quot;bus_factor&quot;:0,&quot;classes&quot;:5,&quot;downloads_monthly&quot;:707414,&quot;downloads_total&quot;:44463068,&quot;files&quot;:6,&quot;forks&quot;:10,&quot;name&quot;:&quot;illuminate/events&quot;,&quot;stars&quot;:131,&quot;usage&quot;:1800},{&quot;bus_factor&quot;:2,&quot;classes&quot;:42,&quot;downloads_monthly&quot;:707414,&quot;downloads_total&quot;:44463068,&quot;files&quot;:43,&quot;forks&quot;:10,&quot;name&quot;:&quot;illuminate/events&quot;,&quot;stars&quot;:131,&quot;usage&quot;:1800},{&quot;bus_factor&quot;:0,&quot;classes&quot;:1,&quot;downloads_monthly&quot;:3351890,&quot;downloads_total&quot;:116579038,&quot;files&quot;:1,&quot;forks&quot;:37,&quot;name&quot;:&quot;dealerdirect/phpcodesniffer-composer-installer&quot;,&quot;stars&quot;:573,&quot;usage&quot;:1790},{&quot;bus_factor&quot;:0,&quot;classes&quot;:1,&quot;downloads_monthly&quot;:3351890,&quot;downloads_total&quot;:116579038,&quot;files&quot;:1,&quot;forks&quot;:37,&quot;name&quot;:&quot;dealerdirect/phpcodesniffer-composer-installer&quot;,&quot;stars&quot;:573,&quot;usage&quot;:1790},{&quot;bus_factor&quot;:0,&quot;classes&quot;:18,&quot;downloads_monthly&quot;:7032322,&quot;downloads_total&quot;:308147236,&quot;files&quot;:36,&quot;forks&quot;:602,&quot;name&quot;:&quot;filp/whoops&quot;,&quot;stars&quot;:13218,&quot;usage&quot;:1778},{&quot;bus_factor&quot;:1,&quot;classes&quot;:18,&quot;downloads_monthly&quot;:7032322,&quot;downloads_total&quot;:308147236,&quot;files&quot;:36,&quot;forks&quot;:602,&quot;name&quot;:&quot;filp/whoops&quot;,&quot;stars&quot;:13218,&quot;usage&quot;:1778},{&quot;bus_factor&quot;:1,&quot;classes&quot;:29,&quot;downloads_monthly&quot;:98564,&quot;downloads_total&quot;:5796328,&quot;files&quot;:30,&quot;forks&quot;:71,&quot;name&quot;:&quot;nette/tester&quot;,&quot;stars&quot;:465,&quot;usage&quot;:1758},{&quot;bus_factor&quot;:0,&quot;classes&quot;:28,&quot;downloads_monthly&quot;:1285391,&quot;downloads_total&quot;:160827164,&quot;files&quot;:30,&quot;forks&quot;:281,&quot;name&quot;:&quot;sensio/framework-extra-bundle&quot;,&quot;stars&quot;:3378,&quot;usage&quot;:1729},{&quot;bus_factor&quot;:0,&quot;classes&quot;:4,&quot;downloads_monthly&quot;:15970,&quot;downloads_total&quot;:1487192,&quot;files&quot;:4,&quot;forks&quot;:24,&quot;name&quot;:&quot;scrutinizer/ocular&quot;,&quot;stars&quot;:41,&quot;usage&quot;:1712},{&quot;bus_factor&quot;:1,&quot;classes&quot;:258,&quot;downloads_monthly&quot;:13603450,&quot;downloads_total&quot;:713118750,&quot;files&quot;:271,&quot;forks&quot;:1097,&quot;name&quot;:&quot;nikic/php-parser&quot;,&quot;stars&quot;:17179,&quot;usage&quot;:1678},{&quot;bus_factor&quot;:0,&quot;classes&quot;:8,&quot;downloads_monthly&quot;:1133281,&quot;downloads_total&quot;:114941493,&quot;files&quot;:9,&quot;forks&quot;:305,&quot;name&quot;:&quot;pimple/pimple&quot;,&quot;stars&quot;:2647,&quot;usage&quot;:1656},{&quot;bus_factor&quot;:0,&quot;classes&quot;:0,&quot;downloads_monthly&quot;:13335711,&quot;downloads_total&quot;:500430904,&quot;files&quot;:4,&quot;forks&quot;:17,&quot;name&quot;:&quot;psr/http-client&quot;,&quot;stars&quot;:1685,&quot;usage&quot;:1647},{&quot;bus_factor&quot;:1,&quot;classes&quot;:17,&quot;downloads_monthly&quot;:13615876,&quot;downloads_total&quot;:503416561,&quot;files&quot;:26,&quot;forks&quot;:24,&quot;name&quot;:&quot;psr/http-factory&quot;,&quot;stars&quot;:1827,&quot;usage&quot;:1627},{&quot;bus_factor&quot;:2,&quot;classes&quot;:21,&quot;downloads_monthly&quot;:8107720,&quot;downloads_total&quot;:382922850,&quot;files&quot;:23,&quot;forks&quot;:32,&quot;name&quot;:&quot;symfony/options-resolver&quot;,&quot;stars&quot;:3222,&quot;usage&quot;:1627},{&quot;bus_factor&quot;:0,&quot;classes&quot;:21,&quot;downloads_monthly&quot;:2414,&quot;downloads_total&quot;:1672829,&quot;files&quot;:24,&quot;forks&quot;:32,&quot;name&quot;:&quot;codeclimate/php-test-reporter&quot;,&quot;stars&quot;:65,&quot;usage&quot;:1610},{&quot;bus_factor&quot;:0,&quot;classes&quot;:19,&quot;downloads_monthly&quot;:2414,&quot;downloads_total&quot;:1672829,&quot;files&quot;:21,&quot;forks&quot;:32,&quot;name&quot;:&quot;codeclimate/php-test-reporter&quot;,&quot;stars&quot;:65,&quot;usage&quot;:1610},{&quot;bus_factor&quot;:2,&quot;classes&quot;:61,&quot;downloads_monthly&quot;:11769804,&quot;downloads_total&quot;:632059829,&quot;files&quot;:71,&quot;forks&quot;:96,&quot;name&quot;:&quot;symfony/routing&quot;,&quot;stars&quot;:7623,&quot;usage&quot;:1585},{&quot;bus_factor&quot;:0,&quot;classes&quot;:51,&quot;downloads_monthly&quot;:593887,&quot;downloads_total&quot;:36699879,&quot;files&quot;:52,&quot;forks&quot;:49,&quot;name&quot;:&quot;illuminate/view&quot;,&quot;stars&quot;:127,&quot;usage&quot;:1578},{&quot;bus_factor&quot;:0,&quot;classes&quot;:3,&quot;downloads_monthly&quot;:2465915,&quot;downloads_total&quot;:88671724,&quot;files&quot;:3,&quot;forks&quot;:183,&quot;name&quot;:&quot;slevomat/coding-standard&quot;,&quot;stars&quot;:1419,&quot;usage&quot;:1578},{&quot;bus_factor&quot;:1,&quot;classes&quot;:54,&quot;downloads_monthly&quot;:718261,&quot;downloads_total&quot;:40399116,&quot;files&quot;:72,&quot;forks&quot;:1946,&quot;name&quot;:&quot;slim/slim&quot;,&quot;stars&quot;:12052,&quot;usage&quot;:1508},{&quot;bus_factor&quot;:1,&quot;classes&quot;:13,&quot;downloads_monthly&quot;:2024222,&quot;downloads_total&quot;:58924446,&quot;files&quot;:19,&quot;forks&quot;:9,&quot;name&quot;:&quot;psr/http-server-middleware&quot;,&quot;stars&quot;:176,&quot;usage&quot;:1399},{&quot;bus_factor&quot;:1,&quot;classes&quot;:8,&quot;downloads_monthly&quot;:1165935,&quot;downloads_total&quot;:37641076,&quot;files&quot;:8,&quot;forks&quot;:40,&quot;name&quot;:&quot;phpspec/prophecy-phpunit&quot;,&quot;stars&quot;:178,&quot;usage&quot;:1393},{&quot;bus_factor&quot;:2,&quot;classes&quot;:23,&quot;downloads_monthly&quot;:3667097,&quot;downloads_total&quot;:159673353,&quot;files&quot;:25,&quot;forks&quot;:42,&quot;name&quot;:&quot;symfony/expression-language&quot;,&quot;stars&quot;:2814,&quot;usage&quot;:1385},{&quot;bus_factor&quot;:1,&quot;classes&quot;:21,&quot;downloads_monthly&quot;:241475,&quot;downloads_total&quot;:15686967,&quot;files&quot;:32,&quot;forks&quot;:244,&quot;name&quot;:&quot;omnipay/common&quot;,&quot;stars&quot;:333,&quot;usage&quot;:1259},{&quot;bus_factor&quot;:0,&quot;classes&quot;:2,&quot;downloads_monthly&quot;:1005223,&quot;downloads_total&quot;:37707351,&quot;files&quot;:2,&quot;forks&quot;:9,&quot;name&quot;:&quot;codeception/module-asserts&quot;,&quot;stars&quot;:80,&quot;usage&quot;:1254},{&quot;bus_factor&quot;:1,&quot;classes&quot;:7,&quot;downloads_monthly&quot;:1005223,&quot;downloads_total&quot;:37707351,&quot;files&quot;:7,&quot;forks&quot;:9,&quot;name&quot;:&quot;codeception/module-asserts&quot;,&quot;stars&quot;:80,&quot;usage&quot;:1254},{&quot;bus_factor&quot;:0,&quot;classes&quot;:6,&quot;downloads_monthly&quot;:639524,&quot;downloads_total&quot;:66384746,&quot;files&quot;:8,&quot;forks&quot;:749,&quot;name&quot;:&quot;laravelcollective/html&quot;,&quot;stars&quot;:3926,&quot;usage&quot;:1253},{&quot;bus_factor&quot;:1,&quot;classes&quot;:8,&quot;downloads_monthly&quot;:11178404,&quot;downloads_total&quot;:540483998,&quot;files&quot;:10,&quot;forks&quot;:45,&quot;name&quot;:&quot;psr/cache&quot;,&quot;stars&quot;:5138,&quot;usage&quot;:1250},{&quot;bus_factor&quot;:0,&quot;classes&quot;:0,&quot;downloads_monthly&quot;:24508,&quot;downloads_total&quot;:2621596,&quot;files&quot;:0,&quot;forks&quot;:7,&quot;name&quot;:&quot;spryker/kernel&quot;,&quot;stars&quot;:1,&quot;usage&quot;:1245},{&quot;bus_factor&quot;:0,&quot;classes&quot;:59,&quot;downloads_monthly&quot;:449205,&quot;downloads_total&quot;:30532825,&quot;files&quot;:82,&quot;forks&quot;:68,&quot;name&quot;:&quot;illuminate/validation&quot;,&quot;stars&quot;:181,&quot;usage&quot;:1219},{&quot;bus_factor&quot;:0,&quot;classes&quot;:11,&quot;downloads_monthly&quot;:7354190,&quot;downloads_total&quot;:519832384,&quot;files&quot;:20,&quot;forks&quot;:214,&quot;name&quot;:&quot;doctrine/cache&quot;,&quot;stars&quot;:7868,&quot;usage&quot;:1173},{&quot;bus_factor&quot;:0,&quot;classes&quot;:6,&quot;downloads_monthly&quot;:7354190,&quot;downloads_total&quot;:519832384,&quot;files&quot;:13,&quot;forks&quot;:214,&quot;name&quot;:&quot;doctrine/cache&quot;,&quot;stars&quot;:7868,&quot;usage&quot;:1173},{&quot;bus_factor&quot;:0,&quot;classes&quot;:15,&quot;downloads_monthly&quot;:20544,&quot;downloads_total&quot;:1216909,&quot;files&quot;:26,&quot;forks&quot;:9,&quot;name&quot;:&quot;contao/manager-plugin&quot;,&quot;stars&quot;:4,&quot;usage&quot;:1142},{&quot;bus_factor&quot;:1,&quot;classes&quot;:6,&quot;downloads_monthly&quot;:20544,&quot;downloads_total&quot;:1216909,&quot;files&quot;:12,&quot;forks&quot;:9,&quot;name&quot;:&quot;contao/manager-plugin&quot;,&quot;stars&quot;:4,&quot;usage&quot;:1142},{&quot;bus_factor&quot;:2,&quot;classes&quot;:3,&quot;downloads_monthly&quot;:12103984,&quot;downloads_total&quot;:738147462,&quot;files&quot;:5,&quot;forks&quot;:147,&quot;name&quot;:&quot;webmozart/assert&quot;,&quot;stars&quot;:7588,&quot;usage&quot;:1141}],&quot;risk_distribution&quot;:[{&quot;bus_factor_max&quot;:0.5,&quot;bus_factor_min&quot;:0,&quot;count&quot;:39,&quot;usage_max&quot;:5000,&quot;usage_min&quot;:1000},{&quot;bus_factor_max&quot;:1.5,&quot;bus_factor_min&quot;:1,&quot;count&quot;:31,&quot;usage_max&quot;:5000,&quot;usage_min&quot;:1000},{&quot;bus_factor_max&quot;:2.5,&quot;bus_factor_min&quot;:2,&quot;count&quot;:14,&quot;usage_max&quot;:5000,&quot;usage_min&quot;:1000}],&quot;shadow_packages&quot;:288519,&quot;top_dependent_packages&quot;:[{&quot;dep_count&quot;:7576,&quot;dependents&quot;:0,&quot;downloads_monthly&quot;:0,&quot;forks&quot;:0,&quot;name&quot;:&quot;jungleran/drupal-them-all&quot;,&quot;stars&quot;:1},{&quot;dep_count&quot;:2872,&quot;dependents&quot;:0,&quot;downloads_monthly&quot;:0,&quot;forks&quot;:1,&quot;name&quot;:&quot;dynamitechetan/lumenoctane&quot;,&quot;stars&quot;:0},{&quot;dep_count&quot;:2676,&quot;dependents&quot;:0,&quot;downloads_monthly&quot;:7,&quot;forks&quot;:0,&quot;name&quot;:&quot;codehubcare/moderyat&quot;,&quot;stars&quot;:1},{&quot;dep_count&quot;:2670,&quot;dependents&quot;:0,&quot;downloads_monthly&quot;:0,&quot;forks&quot;:0,&quot;name&quot;:&quot;altwaireb/filament-title-and-slug&quot;,&quot;stars&quot;:0},{&quot;dep_count&quot;:2606,&quot;dependents&quot;:0,&quot;downloads_monthly&quot;:0,&quot;forks&quot;:0,&quot;name&quot;:&quot;amohamed/offline-cashier&quot;,&quot;stars&quot;:6},{&quot;dep_count&quot;:2498,&quot;dependents&quot;:0,&quot;downloads_monthly&quot;:0,&quot;forks&quot;:0,&quot;name&quot;:&quot;chengz0109/demo&quot;,&quot;stars&quot;:0},{&quot;dep_count&quot;:2072,&quot;dependents&quot;:0,&quot;downloads_monthly&quot;:8,&quot;forks&quot;:0,&quot;name&quot;:&quot;codehubcare/laravel-deployer&quot;,&quot;stars&quot;:0},{&quot;dep_count&quot;:1982,&quot;dependents&quot;:0,&quot;downloads_monthly&quot;:0,&quot;forks&quot;:0,&quot;name&quot;:&quot;bambolee-digital/event-user-manager&quot;,&quot;stars&quot;:0},{&quot;dep_count&quot;:1974,&quot;dependents&quot;:0,&quot;downloads_monthly&quot;:0,&quot;forks&quot;:0,&quot;name&quot;:&quot;chep6915/test&quot;,&quot;stars&quot;:0},{&quot;dep_count&quot;:1950,&quot;dependents&quot;:0,&quot;downloads_monthly&quot;:0,&quot;forks&quot;:0,&quot;name&quot;:&quot;eutranet/laravel-init&quot;,&quot;stars&quot;:0},{&quot;dep_count&quot;:1804,&quot;dependents&quot;:0,&quot;downloads_monthly&quot;:0,&quot;forks&quot;:0,&quot;name&quot;:&quot;amohamed/autoportserve&quot;,&quot;stars&quot;:0},{&quot;dep_count&quot;:1798,&quot;dependents&quot;:0,&quot;downloads_monthly&quot;:3,&quot;forks&quot;:0,&quot;name&quot;:&quot;amirgaber2491/jt-express&quot;,&quot;stars&quot;:0},{&quot;dep_count&quot;:1744,&quot;dependents&quot;:0,&quot;downloads_monthly&quot;:0,&quot;forks&quot;:0,&quot;name&quot;:&quot;althinect/laravel-sendlk&quot;,&quot;stars&quot;:0},{&quot;dep_count&quot;:1740,&quot;dependents&quot;:0,&quot;downloads_monthly&quot;:0,&quot;forks&quot;:0,&quot;name&quot;:&quot;digitcode/digitcodeflazz&quot;,&quot;stars&quot;:0},{&quot;dep_count&quot;:1738,&quot;dependents&quot;:0,&quot;downloads_monthly&quot;:0,&quot;forks&quot;:0,&quot;name&quot;:&quot;bregananta/nicepaysnap&quot;,&quot;stars&quot;:0},{&quot;dep_count&quot;:1690,&quot;dependents&quot;:0,&quot;downloads_monthly&quot;:1,&quot;forks&quot;:0,&quot;name&quot;:&quot;azhar416/composer-test&quot;,&quot;stars&quot;:0},{&quot;dep_count&quot;:1690,&quot;dependents&quot;:0,&quot;downloads_monthly&quot;:0,&quot;forks&quot;:0,&quot;name&quot;:&quot;azdhebar/validate-ean&quot;,&quot;stars&quot;:0},{&quot;dep_count&quot;:1684,&quot;dependents&quot;:0,&quot;downloads_monthly&quot;:0,&quot;forks&quot;:0,&quot;name&quot;:&quot;avanahuda/avanatest&quot;,&quot;stars&quot;:0},{&quot;dep_count&quot;:1680,&quot;dependents&quot;:0,&quot;downloads_monthly&quot;:0,&quot;forks&quot;:1,&quot;name&quot;:&quot;amohamed/laravelmodelmaker&quot;,&quot;stars&quot;:4},{&quot;dep_count&quot;:1666,&quot;dependents&quot;:0,&quot;downloads_monthly&quot;:0,&quot;forks&quot;:0,&quot;name&quot;:&quot;alonso/miratio&quot;,&quot;stars&quot;:0}],&quot;top_dev_packages&quot;:[{&quot;count&quot;:&quot;201547&quot;,&quot;name&quot;:&quot;phpunit/phpunit&quot;},{&quot;count&quot;:&quot;33917&quot;,&quot;name&quot;:&quot;orchestra/testbench&quot;},{&quot;count&quot;:&quot;33796&quot;,&quot;name&quot;:&quot;squizlabs/php_codesniffer&quot;},{&quot;count&quot;:&quot;32283&quot;,&quot;name&quot;:&quot;phpstan/phpstan&quot;},{&quot;count&quot;:&quot;31787&quot;,&quot;name&quot;:&quot;mockery/mockery&quot;},{&quot;count&quot;:&quot;24353&quot;,&quot;name&quot;:&quot;friendsofphp/php-cs-fixer&quot;},{&quot;count&quot;:&quot;11541&quot;,&quot;name&quot;:&quot;pestphp/pest&quot;},{&quot;count&quot;:&quot;10510&quot;,&quot;name&quot;:&quot;vimeo/psalm&quot;},{&quot;count&quot;:&quot;9693&quot;,&quot;name&quot;:&quot;phpstan/phpstan-phpunit&quot;},{&quot;count&quot;:&quot;9592&quot;,&quot;name&quot;:&quot;laravel/pint&quot;},{&quot;count&quot;:&quot;9048&quot;,&quot;name&quot;:&quot;phpstan/extension-installer&quot;},{&quot;count&quot;:&quot;8875&quot;,&quot;name&quot;:&quot;symfony/var-dumper&quot;},{&quot;count&quot;:&quot;8386&quot;,&quot;name&quot;:&quot;nunomaduro/collision&quot;},{&quot;count&quot;:&quot;8239&quot;,&quot;name&quot;:&quot;roave/security-advisories&quot;},{&quot;count&quot;:&quot;6675&quot;,&quot;name&quot;:&quot;phpmd/phpmd&quot;},{&quot;count&quot;:&quot;6487&quot;,&quot;name&quot;:&quot;symfony/phpunit-bridge&quot;},{&quot;count&quot;:&quot;6356&quot;,&quot;name&quot;:&quot;phpstan/phpstan-deprecation-rules&quot;},{&quot;count&quot;:&quot;6227&quot;,&quot;name&quot;:&quot;pestphp/pest-plugin-laravel&quot;},{&quot;count&quot;:&quot;6046&quot;,&quot;name&quot;:&quot;satooshi/php-coveralls&quot;},{&quot;count&quot;:&quot;5794&quot;,&quot;name&quot;:&quot;php-coveralls/php-coveralls&quot;}],&quot;top_loc_packages&quot;:[{&quot;classes&quot;:363,&quot;cloc&quot;:949879,&quot;downloads_total&quot;:19,&quot;files&quot;:11353,&quot;lloc&quot;:1516517,&quot;loc&quot;:2843287,&quot;name&quot;:&quot;xnrcms/alipaysdk&quot;,&quot;stars&quot;:0},{&quot;classes&quot;:7,&quot;cloc&quot;:610,&quot;downloads_total&quot;:36,&quot;files&quot;:48,&quot;lloc&quot;:2198770,&quot;loc&quot;:2199718,&quot;name&quot;:&quot;zerossb/laravel-world&quot;,&quot;stars&quot;:0},{&quot;classes&quot;:10,&quot;cloc&quot;:1099995,&quot;downloads_total&quot;:124,&quot;files&quot;:19224,&quot;lloc&quot;:646364,&quot;loc&quot;:1948506,&quot;name&quot;:&quot;tencentcloud/tencentcloud-sdk-php-intl-en&quot;,&quot;stars&quot;:3},{&quot;classes&quot;:213,&quot;cloc&quot;:880671,&quot;downloads_total&quot;:253,&quot;files&quot;:15459,&quot;lloc&quot;:520034,&quot;loc&quot;:1563294,&quot;name&quot;:&quot;vccmas/tencentcloud-sdk-php-intl-en&quot;,&quot;stars&quot;:0},{&quot;classes&quot;:0,&quot;cloc&quot;:428732,&quot;downloads_total&quot;:13388,&quot;files&quot;:15683,&quot;lloc&quot;:825904,&quot;loc&quot;:1517347,&quot;name&quot;:&quot;zohocrm/php-sdk-6.0&quot;,&quot;stars&quot;:1},{&quot;classes&quot;:56,&quot;cloc&quot;:626946,&quot;downloads_total&quot;:1628,&quot;files&quot;:1062,&quot;lloc&quot;:607385,&quot;loc&quot;:1452841,&quot;name&quot;:&quot;udemy/googleads-php-lib&quot;,&quot;stars&quot;:0},{&quot;classes&quot;:41,&quot;cloc&quot;:554129,&quot;downloads_total&quot;:369,&quot;files&quot;:3136,&quot;lloc&quot;:592562,&quot;loc&quot;:1301637,&quot;name&quot;:&quot;volcengine/volcengine-php-sdk&quot;,&quot;stars&quot;:5},{&quot;classes&quot;:4556,&quot;cloc&quot;:149368,&quot;downloads_total&quot;:20,&quot;files&quot;:4352,&quot;lloc&quot;:962175,&quot;loc&quot;:1264440,&quot;name&quot;:&quot;vavajke/bxcore&quot;,&quot;stars&quot;:0},{&quot;classes&quot;:6346,&quot;cloc&quot;:427719,&quot;downloads_total&quot;:36,&quot;files&quot;:8671,&quot;lloc&quot;:655997,&quot;loc&quot;:1232142,&quot;name&quot;:&quot;sos-solution/other-framework&quot;,&quot;stars&quot;:0},{&quot;classes&quot;:1,&quot;cloc&quot;:56932,&quot;downloads_total&quot;:2,&quot;files&quot;:4164,&quot;lloc&quot;:1146878,&quot;loc&quot;:1221602,&quot;name&quot;:&quot;tombeachell/forza-magento&quot;,&quot;stars&quot;:0}],&quot;top_monthly_downloads&quot;:[{&quot;dependents&quot;:354,&quot;downloads_monthly&quot;:15689866,&quot;favers&quot;:2058,&quot;forks&quot;:8,&quot;name&quot;:&quot;symfony/deprecation-contracts&quot;,&quot;stars&quot;:2053},{&quot;dependents&quot;:462,&quot;downloads_monthly&quot;:15528737,&quot;favers&quot;:7862,&quot;forks&quot;:38,&quot;name&quot;:&quot;symfony/polyfill-mbstring&quot;,&quot;stars&quot;:7847},{&quot;dependents&quot;:8736,&quot;downloads_monthly&quot;:15188541,&quot;favers&quot;:10599,&quot;forks&quot;:182,&quot;name&quot;:&quot;psr/log&quot;,&quot;stars&quot;:10421},{&quot;dependents&quot;:3581,&quot;downloads_monthly&quot;:15042293,&quot;favers&quot;:10014,&quot;forks&quot;:51,&quot;name&quot;:&quot;psr/container&quot;,&quot;stars&quot;:9981},{&quot;dependents&quot;:11415,&quot;downloads_monthly&quot;:14851715,&quot;favers&quot;:9958,&quot;forks&quot;:259,&quot;name&quot;:&quot;symfony/console&quot;,&quot;stars&quot;:9785},{&quot;dependents&quot;:209,&quot;downloads_monthly&quot;:14653286,&quot;favers&quot;:2596,&quot;forks&quot;:9,&quot;name&quot;:&quot;symfony/service-contracts&quot;,&quot;stars&quot;:2595},{&quot;dependents&quot;:28,&quot;downloads_monthly&quot;:14595392,&quot;favers&quot;:2027,&quot;forks&quot;:4,&quot;name&quot;:&quot;symfony/polyfill-intl-normalizer&quot;,&quot;stars&quot;:2026},{&quot;dependents&quot;:5164,&quot;downloads_monthly&quot;:14537371,&quot;favers&quot;:7079,&quot;forks&quot;:186,&quot;name&quot;:&quot;psr/http-message&quot;,&quot;stars&quot;:6998},{&quot;dependents&quot;:2970,&quot;downloads_monthly&quot;:14212004,&quot;favers&quot;:7964,&quot;forks&quot;:295,&quot;name&quot;:&quot;guzzlehttp/psr7&quot;,&quot;stars&quot;:7905},{&quot;dependents&quot;:461,&quot;downloads_monthly&quot;:14089514,&quot;favers&quot;:7678,&quot;forks&quot;:117,&quot;name&quot;:&quot;guzzlehttp/promises&quot;,&quot;stars&quot;:7645},{&quot;dependents&quot;:5283,&quot;downloads_monthly&quot;:14087178,&quot;favers&quot;:7511,&quot;forks&quot;:104,&quot;name&quot;:&quot;symfony/process&quot;,&quot;stars&quot;:7453},{&quot;dependents&quot;:111,&quot;downloads_monthly&quot;:14082419,&quot;favers&quot;:4059,&quot;forks&quot;:7,&quot;name&quot;:&quot;symfony/polyfill-ctype&quot;,&quot;stars&quot;:4056},{&quot;dependents&quot;:3936,&quot;downloads_monthly&quot;:13973488,&quot;favers&quot;:8507,&quot;forks&quot;:55,&quot;name&quot;:&quot;symfony/finder&quot;,&quot;stars&quot;:8436},{&quot;dependents&quot;:33276,&quot;downloads_monthly&quot;:13951038,&quot;favers&quot;:24149,&quot;forks&quot;:2398,&quot;name&quot;:&quot;guzzlehttp/guzzle&quot;,&quot;stars&quot;:23313},{&quot;dependents&quot;:620,&quot;downloads_monthly&quot;:13899161,&quot;favers&quot;:1748,&quot;forks&quot;:21,&quot;name&quot;:&quot;symfony/string&quot;,&quot;stars&quot;:1741},{&quot;dependents&quot;:88,&quot;downloads_monthly&quot;:13865220,&quot;favers&quot;:3784,&quot;forks&quot;:34,&quot;name&quot;:&quot;ralouphie/getallheaders&quot;,&quot;stars&quot;:3778},{&quot;dependents&quot;:1627,&quot;downloads_monthly&quot;:13615876,&quot;favers&quot;:1833,&quot;forks&quot;:24,&quot;name&quot;:&quot;psr/http-factory&quot;,&quot;stars&quot;:1827},{&quot;dependents&quot;:1678,&quot;downloads_monthly&quot;:13603450,&quot;favers&quot;:17251,&quot;forks&quot;:1097,&quot;name&quot;:&quot;nikic/php-parser&quot;,&quot;stars&quot;:17179},{&quot;dependents&quot;:499,&quot;downloads_monthly&quot;:13565065,&quot;favers&quot;:1735,&quot;forks&quot;:26,&quot;name&quot;:&quot;symfony/polyfill-php80&quot;,&quot;stars&quot;:1731},{&quot;dependents&quot;:21,&quot;downloads_monthly&quot;:13553454,&quot;favers&quot;:1689,&quot;forks&quot;:4,&quot;name&quot;:&quot;symfony/polyfill-intl-grapheme&quot;,&quot;stars&quot;:1689},{&quot;dependents&quot;:1647,&quot;downloads_monthly&quot;:13335711,&quot;favers&quot;:1695,&quot;forks&quot;:17,&quot;name&quot;:&quot;psr/http-client&quot;,&quot;stars&quot;:1685},{&quot;dependents&quot;:3263,&quot;downloads_monthly&quot;:13306972,&quot;favers&quot;:8580,&quot;forks&quot;:67,&quot;name&quot;:&quot;symfony/event-dispatcher&quot;,&quot;stars&quot;:8534},{&quot;dependents&quot;:7517,&quot;downloads_monthly&quot;:13099994,&quot;favers&quot;:21986,&quot;forks&quot;:1899,&quot;name&quot;:&quot;monolog/monolog&quot;,&quot;stars&quot;:21132},{&quot;dependents&quot;:288,&quot;downloads_monthly&quot;:13070516,&quot;favers&quot;:3394,&quot;forks&quot;:3,&quot;name&quot;:&quot;symfony/event-dispatcher-contracts&quot;,&quot;stars&quot;:3394},{&quot;dependents&quot;:7637,&quot;downloads_monthly&quot;:12977531,&quot;favers&quot;:7558,&quot;forks&quot;:103,&quot;name&quot;:&quot;symfony/var-dumper&quot;,&quot;stars&quot;:7426},{&quot;dependents&quot;:163,&quot;downloads_monthly&quot;:12776353,&quot;favers&quot;:8831,&quot;forks&quot;:103,&quot;name&quot;:&quot;myclabs/deep-copy&quot;,&quot;stars&quot;:8807},{&quot;dependents&quot;:139853,&quot;downloads_monthly&quot;:12679593,&quot;favers&quot;:20294,&quot;forks&quot;:2206,&quot;name&quot;:&quot;phpunit/phpunit&quot;,&quot;stars&quot;:19791},{&quot;dependents&quot;:5080,&quot;downloads_monthly&quot;:12655313,&quot;favers&quot;:8726,&quot;forks&quot;:298,&quot;name&quot;:&quot;symfony/http-foundation&quot;,&quot;stars&quot;:8652},{&quot;dependents&quot;:237,&quot;downloads_monthly&quot;:12611304,&quot;favers&quot;:7648,&quot;forks&quot;:84,&quot;name&quot;:&quot;sebastian/diff&quot;,&quot;stars&quot;:7600},{&quot;dependents&quot;:102,&quot;downloads_monthly&quot;:12566777,&quot;favers&quot;:11119,&quot;forks&quot;:59,&quot;name&quot;:&quot;doctrine/lexer&quot;,&quot;stars&quot;:11102}],&quot;top_prod_packages&quot;:[{&quot;count&quot;:&quot;46437&quot;,&quot;name&quot;:&quot;illuminate/support&quot;},{&quot;count&quot;:&quot;39682&quot;,&quot;name&quot;:&quot;guzzlehttp/guzzle&quot;},{&quot;count&quot;:&quot;18948&quot;,&quot;name&quot;:&quot;laravel/framework&quot;},{&quot;count&quot;:&quot;16238&quot;,&quot;name&quot;:&quot;illuminate/contracts&quot;},{&quot;count&quot;:&quot;16190&quot;,&quot;name&quot;:&quot;yiisoft/yii2&quot;},{&quot;count&quot;:&quot;12982&quot;,&quot;name&quot;:&quot;symfony/console&quot;},{&quot;count&quot;:&quot;11680&quot;,&quot;name&quot;:&quot;psr/log&quot;},{&quot;count&quot;:&quot;11197&quot;,&quot;name&quot;:&quot;symfony/framework-bundle&quot;},{&quot;count&quot;:&quot;10241&quot;,&quot;name&quot;:&quot;illuminate/database&quot;},{&quot;count&quot;:&quot;9043&quot;,&quot;name&quot;:&quot;composer/installers&quot;},{&quot;count&quot;:&quot;8795&quot;,&quot;name&quot;:&quot;symfony/yaml&quot;},{&quot;count&quot;:&quot;8585&quot;,&quot;name&quot;:&quot;psr/http-message&quot;},{&quot;count&quot;:&quot;7986&quot;,&quot;name&quot;:&quot;monolog/monolog&quot;},{&quot;count&quot;:&quot;7696&quot;,&quot;name&quot;:&quot;symfony/dependency-injection&quot;},{&quot;count&quot;:&quot;7636&quot;,&quot;name&quot;:&quot;symfony/http-kernel&quot;},{&quot;count&quot;:&quot;7143&quot;,&quot;name&quot;:&quot;symfony/http-foundation&quot;},{&quot;count&quot;:&quot;7056&quot;,&quot;name&quot;:&quot;symfony/config&quot;},{&quot;count&quot;:&quot;6631&quot;,&quot;name&quot;:&quot;twig/twig&quot;},{&quot;count&quot;:&quot;6485&quot;,&quot;name&quot;:&quot;spatie/laravel-package-tools&quot;},{&quot;count&quot;:&quot;6397&quot;,&quot;name&quot;:&quot;doctrine/orm&quot;}],&quot;top_total_downloads&quot;:[{&quot;dependents&quot;:8736,&quot;downloads_total&quot;:963431048,&quot;favers&quot;:10599,&quot;forks&quot;:182,&quot;name&quot;:&quot;psr/log&quot;,&quot;stars&quot;:10421},{&quot;dependents&quot;:462,&quot;downloads_total&quot;:955254771,&quot;favers&quot;:7862,&quot;forks&quot;:38,&quot;name&quot;:&quot;symfony/polyfill-mbstring&quot;,&quot;stars&quot;:7847},{&quot;dependents&quot;:11415,&quot;downloads_total&quot;:885244463,&quot;favers&quot;:9958,&quot;forks&quot;:259,&quot;name&quot;:&quot;symfony/console&quot;,&quot;stars&quot;:9785},{&quot;dependents&quot;:5164,&quot;downloads_total&quot;:823272846,&quot;favers&quot;:7079,&quot;forks&quot;:186,&quot;name&quot;:&quot;psr/http-message&quot;,&quot;stars&quot;:6998},{&quot;dependents&quot;:2970,&quot;downloads_total&quot;:814214923,&quot;favers&quot;:7964,&quot;forks&quot;:295,&quot;name&quot;:&quot;guzzlehttp/psr7&quot;,&quot;stars&quot;:7905},{&quot;dependents&quot;:3936,&quot;downloads_total&quot;:807162519,&quot;favers&quot;:8507,&quot;forks&quot;:55,&quot;name&quot;:&quot;symfony/finder&quot;,&quot;stars&quot;:8436},{&quot;dependents&quot;:3581,&quot;downloads_total&quot;:803012501,&quot;favers&quot;:10014,&quot;forks&quot;:51,&quot;name&quot;:&quot;psr/container&quot;,&quot;stars&quot;:9981},{&quot;dependents&quot;:3263,&quot;downloads_total&quot;:801654417,&quot;favers&quot;:8580,&quot;forks&quot;:67,&quot;name&quot;:&quot;symfony/event-dispatcher&quot;,&quot;stars&quot;:8534},{&quot;dependents&quot;:33276,&quot;downloads_total&quot;:800504182,&quot;favers&quot;:24149,&quot;forks&quot;:2398,&quot;name&quot;:&quot;guzzlehttp/guzzle&quot;,&quot;stars&quot;:23313},{&quot;dependents&quot;:7517,&quot;downloads_total&quot;:788872755,&quot;favers&quot;:21986,&quot;forks&quot;:1899,&quot;name&quot;:&quot;monolog/monolog&quot;,&quot;stars&quot;:21132},{&quot;dependents&quot;:111,&quot;downloads_total&quot;:787938032,&quot;favers&quot;:4059,&quot;forks&quot;:7,&quot;name&quot;:&quot;symfony/polyfill-ctype&quot;,&quot;stars&quot;:4056},{&quot;dependents&quot;:5283,&quot;downloads_total&quot;:785182390,&quot;favers&quot;:7511,&quot;forks&quot;:104,&quot;name&quot;:&quot;symfony/process&quot;,&quot;stars&quot;:7453},{&quot;dependents&quot;:461,&quot;downloads_total&quot;:780413771,&quot;favers&quot;:7678,&quot;forks&quot;:117,&quot;name&quot;:&quot;guzzlehttp/promises&quot;,&quot;stars&quot;:7645},{&quot;dependents&quot;:102,&quot;downloads_total&quot;:740573713,&quot;favers&quot;:11119,&quot;forks&quot;:59,&quot;name&quot;:&quot;doctrine/lexer&quot;,&quot;stars&quot;:11102},{&quot;dependents&quot;:139853,&quot;downloads_total&quot;:738780466,&quot;favers&quot;:20294,&quot;forks&quot;:2206,&quot;name&quot;:&quot;phpunit/phpunit&quot;,&quot;stars&quot;:19791},{&quot;dependents&quot;:1141,&quot;downloads_total&quot;:738147462,&quot;favers&quot;:7659,&quot;forks&quot;:147,&quot;name&quot;:&quot;webmozart/assert&quot;,&quot;stars&quot;:7588},{&quot;dependents&quot;:349,&quot;downloads_total&quot;:734081448,&quot;favers&quot;:11001,&quot;forks&quot;:62,&quot;name&quot;:&quot;doctrine/instantiator&quot;,&quot;stars&quot;:10975},{&quot;dependents&quot;:237,&quot;downloads_total&quot;:729909604,&quot;favers&quot;:7648,&quot;forks&quot;:84,&quot;name&quot;:&quot;sebastian/diff&quot;,&quot;stars&quot;:7600},{&quot;dependents&quot;:1522,&quot;downloads_total&quot;:725085270,&quot;favers&quot;:8907,&quot;forks&quot;:376,&quot;name&quot;:&quot;phpunit/php-code-coverage&quot;,&quot;stars&quot;:8863},{&quot;dependents&quot;:75,&quot;downloads_total&quot;:715295250,&quot;favers&quot;:6810,&quot;forks&quot;:34,&quot;name&quot;:&quot;sebastian/exporter&quot;,&quot;stars&quot;:6801},{&quot;dependents&quot;:69,&quot;downloads_total&quot;:713273284,&quot;favers&quot;:7453,&quot;forks&quot;:46,&quot;name&quot;:&quot;phpunit/php-file-iterator&quot;,&quot;stars&quot;:7444},{&quot;dependents&quot;:1678,&quot;downloads_total&quot;:713118750,&quot;favers&quot;:17251,&quot;forks&quot;:1097,&quot;name&quot;:&quot;nikic/php-parser&quot;,&quot;stars&quot;:17179},{&quot;dependents&quot;:141,&quot;downloads_total&quot;:711827957,&quot;favers&quot;:7747,&quot;forks&quot;:65,&quot;name&quot;:&quot;phpunit/php-timer&quot;,&quot;stars&quot;:7684},{&quot;dependents&quot;:152,&quot;downloads_total&quot;:711533096,&quot;favers&quot;:7038,&quot;forks&quot;:69,&quot;name&quot;:&quot;sebastian/comparator&quot;,&quot;stars&quot;:7024},{&quot;dependents&quot;:18,&quot;downloads_total&quot;:706487222,&quot;favers&quot;:6560,&quot;forks&quot;:18,&quot;name&quot;:&quot;sebastian/recursion-context&quot;,&quot;stars&quot;:6556},{&quot;dependents&quot;:63,&quot;downloads_total&quot;:705815492,&quot;favers&quot;:6762,&quot;forks&quot;:35,&quot;name&quot;:&quot;sebastian/environment&quot;,&quot;stars&quot;:6752},{&quot;dependents&quot;:5080,&quot;downloads_total&quot;:704319494,&quot;favers&quot;:8726,&quot;forks&quot;:298,&quot;name&quot;:&quot;symfony/http-foundation&quot;,&quot;stars&quot;:8652},{&quot;dependents&quot;:73,&quot;downloads_total&quot;:703572581,&quot;favers&quot;:7402,&quot;forks&quot;:30,&quot;name&quot;:&quot;phpunit/php-text-template&quot;,&quot;stars&quot;:7389},{&quot;dependents&quot;:138,&quot;downloads_total&quot;:700309956,&quot;favers&quot;:6575,&quot;forks&quot;:32,&quot;name&quot;:&quot;sebastian/version&quot;,&quot;stars&quot;:6557},{&quot;dependents&quot;:787,&quot;downloads_total&quot;:699076879,&quot;favers&quot;:11323,&quot;forks&quot;:135,&quot;name&quot;:&quot;doctrine/inflector&quot;,&quot;stars&quot;:11290}],&quot;underrated_packages&quot;:[{&quot;download_star_ratio&quot;:46868,&quot;downloads_monthly&quot;:3562001,&quot;downloads_total&quot;:8234587,&quot;favers&quot;:76,&quot;name&quot;:&quot;staabm/side-effects-detector&quot;,&quot;stars&quot;:76},{&quot;download_star_ratio&quot;:58797,&quot;downloads_monthly&quot;:2469489,&quot;downloads_total&quot;:17813872,&quot;favers&quot;:42,&quot;name&quot;:&quot;spatie/error-solutions&quot;,&quot;stars&quot;:42},{&quot;download_star_ratio&quot;:53192,&quot;downloads_monthly&quot;:2287292,&quot;downloads_total&quot;:36569212,&quot;favers&quot;:43,&quot;name&quot;:&quot;google/longrunning&quot;,&quot;stars&quot;:43},{&quot;download_star_ratio&quot;:25029,&quot;downloads_monthly&quot;:1827182,&quot;downloads_total&quot;:48775694,&quot;favers&quot;:74,&quot;name&quot;:&quot;illuminate/macroable&quot;,&quot;stars&quot;:73},{&quot;download_star_ratio&quot;:43452,&quot;downloads_monthly&quot;:1607744,&quot;downloads_total&quot;:26682353,&quot;favers&quot;:39,&quot;name&quot;:&quot;pestphp/pest-plugin&quot;,&quot;stars&quot;:37},{&quot;download_star_ratio&quot;:20210,&quot;downloads_monthly&quot;:1596657,&quot;downloads_total&quot;:34137970,&quot;favers&quot;:79,&quot;name&quot;:&quot;php-http/guzzle7-adapter&quot;,&quot;stars&quot;:79},{&quot;download_star_ratio&quot;:15943,&quot;downloads_monthly&quot;:1450867,&quot;downloads_total&quot;:16432345,&quot;favers&quot;:91,&quot;name&quot;:&quot;ta-tikoma/phpunit-architecture-test&quot;,&quot;stars&quot;:91},{&quot;download_star_ratio&quot;:43885,&quot;downloads_monthly&quot;:1448220,&quot;downloads_total&quot;:16467245,&quot;favers&quot;:33,&quot;name&quot;:&quot;pestphp/pest-plugin-arch&quot;,&quot;stars&quot;:33},{&quot;download_star_ratio&quot;:25705,&quot;downloads_monthly&quot;:1413796,&quot;downloads_total&quot;:14926683,&quot;favers&quot;:55,&quot;name&quot;:&quot;symfony/polyfill-php82&quot;,&quot;stars&quot;:55},{&quot;download_star_ratio&quot;:17402,&quot;downloads_monthly&quot;:1409620,&quot;downloads_total&quot;:24231085,&quot;favers&quot;:82,&quot;name&quot;:&quot;illuminate/conditionable&quot;,&quot;stars&quot;:81},{&quot;download_star_ratio&quot;:15265,&quot;downloads_monthly&quot;:1404425,&quot;downloads_total&quot;:15044207,&quot;favers&quot;:95,&quot;name&quot;:&quot;kelunik/certificate&quot;,&quot;stars&quot;:92},{&quot;download_star_ratio&quot;:25076,&quot;downloads_monthly&quot;:1354147,&quot;downloads_total&quot;:10013761,&quot;favers&quot;:54,&quot;name&quot;:&quot;amphp/pipeline&quot;,&quot;stars&quot;:54},{&quot;download_star_ratio&quot;:91579,&quot;downloads_monthly&quot;:1282117,&quot;downloads_total&quot;:10878343,&quot;favers&quot;:16,&quot;name&quot;:&quot;open-telemetry/api&quot;,&quot;stars&quot;:14},{&quot;download_star_ratio&quot;:12999,&quot;downloads_monthly&quot;:1260932,&quot;downloads_total&quot;:63871523,&quot;favers&quot;:97,&quot;name&quot;:&quot;pear/pear_exception&quot;,&quot;stars&quot;:97},{&quot;download_star_ratio&quot;:111863,&quot;downloads_monthly&quot;:1230502,&quot;downloads_total&quot;:10718104,&quot;favers&quot;:11,&quot;name&quot;:&quot;open-telemetry/context&quot;,&quot;stars&quot;:11},{&quot;download_star_ratio&quot;:15580,&quot;downloads_monthly&quot;:1199700,&quot;downloads_total&quot;:57591646,&quot;favers&quot;:78,&quot;name&quot;:&quot;pear/pear-core-minimal&quot;,&quot;stars&quot;:77},{&quot;download_star_ratio&quot;:13689,&quot;downloads_monthly&quot;:1149937,&quot;downloads_total&quot;:56739652,&quot;favers&quot;:84,&quot;name&quot;:&quot;pear/console_getopt&quot;,&quot;stars&quot;:84},{&quot;download_star_ratio&quot;:19283,&quot;downloads_monthly&quot;:1060588,&quot;downloads_total&quot;:15810440,&quot;favers&quot;:55,&quot;name&quot;:&quot;phpcsstandards/phpcsutils&quot;,&quot;stars&quot;:55},{&quot;download_star_ratio&quot;:22802,&quot;downloads_monthly&quot;:1048935,&quot;downloads_total&quot;:17505202,&quot;favers&quot;:46,&quot;name&quot;:&quot;mpdf/psr-log-aware-trait&quot;,&quot;stars&quot;:46},{&quot;download_star_ratio&quot;:12565,&quot;downloads_monthly&quot;:1005223,&quot;downloads_total&quot;:37707351,&quot;favers&quot;:81,&quot;name&quot;:&quot;codeception/module-asserts&quot;,&quot;stars&quot;:80},{&quot;download_star_ratio&quot;:13526,&quot;downloads_monthly&quot;:1000954,&quot;downloads_total&quot;:48004786,&quot;favers&quot;:76,&quot;name&quot;:&quot;pear/archive_tar&quot;,&quot;stars&quot;:74},{&quot;download_star_ratio&quot;:30258,&quot;downloads_monthly&quot;:998531,&quot;downloads_total&quot;:13631481,&quot;favers&quot;:33,&quot;name&quot;:&quot;mpdf/psr-http-message-shim&quot;,&quot;stars&quot;:33},{&quot;download_star_ratio&quot;:9798,&quot;downloads_monthly&quot;:960247,&quot;downloads_total&quot;:47386371,&quot;favers&quot;:98,&quot;name&quot;:&quot;consolidation/self-update&quot;,&quot;stars&quot;:98},{&quot;download_star_ratio&quot;:11268,&quot;downloads_monthly&quot;:957795,&quot;downloads_total&quot;:24112218,&quot;favers&quot;:86,&quot;name&quot;:&quot;async-aws/core&quot;,&quot;stars&quot;:85},{&quot;download_star_ratio&quot;:15545,&quot;downloads_monthly&quot;:917195,&quot;downloads_total&quot;:37384405,&quot;favers&quot;:59,&quot;name&quot;:&quot;consolidation/site-alias&quot;,&quot;stars&quot;:59},{&quot;download_star_ratio&quot;:44598,&quot;downloads_monthly&quot;:891972,&quot;downloads_total&quot;:2767437,&quot;favers&quot;:21,&quot;name&quot;:&quot;symfony/polyfill-php84&quot;,&quot;stars&quot;:20},{&quot;download_star_ratio&quot;:19572,&quot;downloads_monthly&quot;:880764,&quot;downloads_total&quot;:35635569,&quot;favers&quot;:45,&quot;name&quot;:&quot;consolidation/filter-via-dot-access-data&quot;,&quot;stars&quot;:45},{&quot;download_star_ratio&quot;:10540,&quot;downloads_monthly&quot;:874850,&quot;downloads_total&quot;:30790296,&quot;favers&quot;:83,&quot;name&quot;:&quot;codeception/lib-innerbrowser&quot;,&quot;stars&quot;:83},{&quot;download_star_ratio&quot;:17333,&quot;downloads_monthly&quot;:866678,&quot;downloads_total&quot;:34679443,&quot;favers&quot;:51,&quot;name&quot;:&quot;consolidation/site-process&quot;,&quot;stars&quot;:50},{&quot;download_star_ratio&quot;:17526,&quot;downloads_monthly&quot;:858811,&quot;downloads_total&quot;:31379072,&quot;favers&quot;:50,&quot;name&quot;:&quot;drupal/core-composer-scaffold&quot;,&quot;stars&quot;:49}],&quot;zero_stars_packages&quot;:245070};
  const languages = [{&quot;Language&quot;:&quot;PHP&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:707353896,&quot;Comments&quot;:320719500,&quot;Blanks&quot;:149366159},{&quot;Language&quot;:&quot;JavaScript&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:216447233,&quot;Comments&quot;:42754373,&quot;Blanks&quot;:29489796},{&quot;Language&quot;:&quot;JSON&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:177571760,&quot;Comments&quot;:0,&quot;Blanks&quot;:126355},{&quot;Language&quot;:&quot;CSS&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:96900448,&quot;Comments&quot;:3466375,&quot;Blanks&quot;:13092368},{&quot;Language&quot;:&quot;HTML&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:70849158,&quot;Comments&quot;:1085498,&quot;Blanks&quot;:9760760},{&quot;Language&quot;:&quot;XML&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:49900719,&quot;Comments&quot;:1265421,&quot;Blanks&quot;:1113886},{&quot;Language&quot;:&quot;YAML&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:25322932,&quot;Comments&quot;:506971,&quot;Blanks&quot;:775201},{&quot;Language&quot;:&quot;SVG&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:25211075,&quot;Comments&quot;:172811,&quot;Blanks&quot;:246033},{&quot;Language&quot;:&quot;SQL&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:10374794,&quot;Comments&quot;:314469,&quot;Blanks&quot;:318288},{&quot;Language&quot;:&quot;Sass&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:9255298,&quot;Comments&quot;:984480,&quot;Blanks&quot;:1568503},{&quot;Language&quot;:&quot;LESS&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:5968904,&quot;Comments&quot;:811168,&quot;Blanks&quot;:983435},{&quot;Language&quot;:&quot;PO File&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:5622443,&quot;Comments&quot;:3443936,&quot;Blanks&quot;:2561674},{&quot;Language&quot;:&quot;Twig&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:4989336,&quot;Comments&quot;:508527,&quot;Blanks&quot;:589527},{&quot;Language&quot;:&quot;TypeScript&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:2764367,&quot;Comments&quot;:392051,&quot;Blanks&quot;:160355},{&quot;Language&quot;:&quot;Bitbake&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:2154509,&quot;Comments&quot;:16243,&quot;Blanks&quot;:240554},{&quot;Language&quot;:&quot;ReStructuredText&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:1874361,&quot;Comments&quot;:0,&quot;Blanks&quot;:825635},{&quot;Language&quot;:&quot;INI&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:1532443,&quot;Comments&quot;:601460,&quot;Blanks&quot;:328312},{&quot;Language&quot;:&quot;Ruby&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:1485300,&quot;Comments&quot;:81457,&quot;Blanks&quot;:233365},{&quot;Language&quot;:&quot;TeX&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:1482197,&quot;Comments&quot;:29578,&quot;Blanks&quot;:5352},{&quot;Language&quot;:&quot;Shell&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:1369745,&quot;Comments&quot;:205426,&quot;Blanks&quot;:253603},{&quot;Language&quot;:&quot;Pan&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:1147859,&quot;Comments&quot;:2087,&quot;Blanks&quot;:115262},{&quot;Language&quot;:&quot;C&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:993627,&quot;Comments&quot;:141078,&quot;Blanks&quot;:163269},{&quot;Language&quot;:&quot;Lua&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:678874,&quot;Comments&quot;:3670,&quot;Blanks&quot;:5019},{&quot;Language&quot;:&quot;Perl&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:606282,&quot;Comments&quot;:56995,&quot;Blanks&quot;:24951},{&quot;Language&quot;:&quot;XSL&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:603850,&quot;Comments&quot;:51833,&quot;Blanks&quot;:42468},{&quot;Language&quot;:&quot;Puppet&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:595345,&quot;Comments&quot;:172555,&quot;Blanks&quot;:77077},{&quot;Language&quot;:&quot;C Header&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:584315,&quot;Comments&quot;:127703,&quot;Blanks&quot;:102504},{&quot;Language&quot;:&quot;Vue&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:451881,&quot;Comments&quot;:7271,&quot;Blanks&quot;:72600},{&quot;Language&quot;:&quot;Python&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:423252,&quot;Comments&quot;:162342,&quot;Blanks&quot;:130145},{&quot;Language&quot;:&quot;Makefile&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:413986,&quot;Comments&quot;:44765,&quot;Blanks&quot;:129067},{&quot;Language&quot;:&quot;TSX&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:385366,&quot;Comments&quot;:14981,&quot;Blanks&quot;:46005},{&quot;Language&quot;:&quot;Modelica&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:361648,&quot;Comments&quot;:2525,&quot;Blanks&quot;:3308},{&quot;Language&quot;:&quot;BASH&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:300048,&quot;Comments&quot;:85183,&quot;Blanks&quot;:64664},{&quot;Language&quot;:&quot;JSX&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:297726,&quot;Comments&quot;:12951,&quot;Blanks&quot;:34822},{&quot;Language&quot;:&quot;C++&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:287595,&quot;Comments&quot;:35133,&quot;Blanks&quot;:47455},{&quot;Language&quot;:&quot;Java&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:278237,&quot;Comments&quot;:77305,&quot;Blanks&quot;:56027},{&quot;Language&quot;:&quot;Go&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:270660,&quot;Comments&quot;:22450,&quot;Blanks&quot;:42700},{&quot;Language&quot;:&quot;Scheme&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:224964,&quot;Comments&quot;:45,&quot;Blanks&quot;:20281},{&quot;Language&quot;:&quot;Dockerfile&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:210925,&quot;Comments&quot;:37307,&quot;Blanks&quot;:68526},{&quot;Language&quot;:&quot;C#&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:199645,&quot;Comments&quot;:29727,&quot;Blanks&quot;:27626},{&quot;Language&quot;:&quot;Mustache&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:198803,&quot;Comments&quot;:1459,&quot;Blanks&quot;:16273},{&quot;Language&quot;:&quot;Ruby HTML&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:178511,&quot;Comments&quot;:26,&quot;Blanks&quot;:15557},{&quot;Language&quot;:&quot;AsciiDoc&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:176814,&quot;Comments&quot;:4987,&quot;Blanks&quot;:42828},{&quot;Language&quot;:&quot;CoffeeScript&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:175124,&quot;Comments&quot;:22805,&quot;Blanks&quot;:42719},{&quot;Language&quot;:&quot;Rust&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:166004,&quot;Comments&quot;:4690,&quot;Blanks&quot;:24888},{&quot;Language&quot;:&quot;GraphQL&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:161623,&quot;Comments&quot;:2486,&quot;Blanks&quot;:18237},{&quot;Language&quot;:&quot;Batch&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:127339,&quot;Comments&quot;:8183,&quot;Blanks&quot;:26023},{&quot;Language&quot;:&quot;Stylus&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:112491,&quot;Comments&quot;:9679,&quot;Blanks&quot;:22731},{&quot;Language&quot;:&quot;Handlebars&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:89346,&quot;Comments&quot;:1042,&quot;Blanks&quot;:6281},{&quot;Language&quot;:&quot;Autoconf&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:87488,&quot;Comments&quot;:10441,&quot;Blanks&quot;:11100},{&quot;Language&quot;:&quot;Protocol Buffers&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:85283,&quot;Comments&quot;:29329,&quot;Blanks&quot;:19387},{&quot;Language&quot;:&quot;Happy&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:71591,&quot;Comments&quot;:0,&quot;Blanks&quot;:12069},{&quot;Language&quot;:&quot;ActionScript&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:64371,&quot;Comments&quot;:12476,&quot;Blanks&quot;:11795},{&quot;Language&quot;:&quot;Templ&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:60108,&quot;Comments&quot;:12247,&quot;Blanks&quot;:7373},{&quot;Language&quot;:&quot;M4&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:58949,&quot;Comments&quot;:13257,&quot;Blanks&quot;:6126},{&quot;Language&quot;:&quot;Dart&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:43563,&quot;Comments&quot;:3123,&quot;Blanks&quot;:9014},{&quot;Language&quot;:&quot;Pug&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:34256,&quot;Comments&quot;:991,&quot;Blanks&quot;:3679},{&quot;Language&quot;:&quot;ReScript&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:31988,&quot;Comments&quot;:48,&quot;Blanks&quot;:642},{&quot;Language&quot;:&quot;Jinja2&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:30395,&quot;Comments&quot;:87,&quot;Blanks&quot;:5239},{&quot;Language&quot;:&quot;D&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:28764,&quot;Comments&quot;:13629,&quot;Blanks&quot;:5994},{&quot;Language&quot;:&quot;Nix&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:28099,&quot;Comments&quot;:1004,&quot;Blanks&quot;:847},{&quot;Language&quot;:&quot;Forge Config&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:26217,&quot;Comments&quot;:20806,&quot;Blanks&quot;:10140},{&quot;Language&quot;:&quot;Svelte&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:23967,&quot;Comments&quot;:418,&quot;Blanks&quot;:2284},{&quot;Language&quot;:&quot;Thrift&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:22511,&quot;Comments&quot;:21628,&quot;Blanks&quot;:6347},{&quot;Language&quot;:&quot;Pascal&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:22165,&quot;Comments&quot;:3282,&quot;Blanks&quot;:5169},{&quot;Language&quot;:&quot;Objective-C&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:20434,&quot;Comments&quot;:2887,&quot;Blanks&quot;:5238},{&quot;Language&quot;:&quot;Org&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:20224,&quot;Comments&quot;:33,&quot;Blanks&quot;:4995},{&quot;Language&quot;:&quot;Dust.js&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:18852,&quot;Comments&quot;:47,&quot;Blanks&quot;:2436},{&quot;Language&quot;:&quot;Rakefile&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:17503,&quot;Comments&quot;:895,&quot;Blanks&quot;:3261},{&quot;Language&quot;:&quot;ASP&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:14903,&quot;Comments&quot;:4193,&quot;Blanks&quot;:2737},{&quot;Language&quot;:&quot;Haxe&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:13040,&quot;Comments&quot;:5035,&quot;Blanks&quot;:3248},{&quot;Language&quot;:&quot;PostCSS&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:12135,&quot;Comments&quot;:1902,&quot;Blanks&quot;:2601},{&quot;Language&quot;:&quot;PlantUML&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:11878,&quot;Comments&quot;:154,&quot;Blanks&quot;:1857},{&quot;Language&quot;:&quot;Erlang&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:10802,&quot;Comments&quot;:2449,&quot;Blanks&quot;:1955},{&quot;Language&quot;:&quot;Visual Studio Project&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:10801,&quot;Comments&quot;:0,&quot;Blanks&quot;:0},{&quot;Language&quot;:&quot;PowerShell&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:10432,&quot;Comments&quot;:1685,&quot;Blanks&quot;:1869},{&quot;Language&quot;:&quot;ASP.NET&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:10349,&quot;Comments&quot;:529,&quot;Blanks&quot;:126},{&quot;Language&quot;:&quot;TOML&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:10110,&quot;Comments&quot;:2704,&quot;Blanks&quot;:2007},{&quot;Language&quot;:&quot;GNU Style Assembly&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:9293,&quot;Comments&quot;:7189,&quot;Blanks&quot;:1917},{&quot;Language&quot;:&quot;RPM Specfile&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:8312,&quot;Comments&quot;:373,&quot;Blanks&quot;:1817},{&quot;Language&quot;:&quot;ColdFusion&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:8025,&quot;Comments&quot;:1928,&quot;Blanks&quot;:1267},{&quot;Language&quot;:&quot;MSBuild&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:8008,&quot;Comments&quot;:798,&quot;Blanks&quot;:298},{&quot;Language&quot;:&quot;TCL&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:7953,&quot;Comments&quot;:485,&quot;Blanks&quot;:1257},{&quot;Language&quot;:&quot;Swift&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:7848,&quot;Comments&quot;:1345,&quot;Blanks&quot;:1825},{&quot;Language&quot;:&quot;Haml&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:7723,&quot;Comments&quot;:161,&quot;Blanks&quot;:993},{&quot;Language&quot;:&quot;Automake&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:7358,&quot;Comments&quot;:3168,&quot;Blanks&quot;:1963},{&quot;Language&quot;:&quot;Bazel&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:6533,&quot;Comments&quot;:409,&quot;Blanks&quot;:1493},{&quot;Language&quot;:&quot;SRecode Template&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:6467,&quot;Comments&quot;:0,&quot;Blanks&quot;:1745},{&quot;Language&quot;:&quot;Solidity&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:6116,&quot;Comments&quot;:1280,&quot;Blanks&quot;:1777},{&quot;Language&quot;:&quot;Prolog&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:5696,&quot;Comments&quot;:33,&quot;Blanks&quot;:1468},{&quot;Language&quot;:&quot;HCL&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:5669,&quot;Comments&quot;:156,&quot;Blanks&quot;:1160},{&quot;Language&quot;:&quot;Assembly&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:5599,&quot;Comments&quot;:1930,&quot;Blanks&quot;:1704},{&quot;Language&quot;:&quot;LiveScript&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:5462,&quot;Comments&quot;:188,&quot;Blanks&quot;:930},{&quot;Language&quot;:&quot;Liquid&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:5319,&quot;Comments&quot;:440,&quot;Blanks&quot;:993},{&quot;Language&quot;:&quot;ColdFusion CFScript&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:4816,&quot;Comments&quot;:140,&quot;Blanks&quot;:748},{&quot;Language&quot;:&quot;Kotlin&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:4666,&quot;Comments&quot;:636,&quot;Blanks&quot;:862},{&quot;Language&quot;:&quot;Apache Velocity&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:4409,&quot;Comments&quot;:332,&quot;Blanks&quot;:231},{&quot;Language&quot;:&quot;VB6&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:4201,&quot;Comments&quot;:0,&quot;Blanks&quot;:123},{&quot;Language&quot;:&quot;CMake&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:4104,&quot;Comments&quot;:1552,&quot;Blanks&quot;:855},{&quot;Language&quot;:&quot;Visual Studio Solution&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:4016,&quot;Comments&quot;:0,&quot;Blanks&quot;:125},{&quot;Language&quot;:&quot;KV Language&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:3842,&quot;Comments&quot;:0,&quot;Blanks&quot;:0},{&quot;Language&quot;:&quot;FreeMarker&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:3144,&quot;Comments&quot;:20,&quot;Blanks&quot;:960},{&quot;Language&quot;:&quot;OCaml&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:3111,&quot;Comments&quot;:528,&quot;Blanks&quot;:364},{&quot;Language&quot;:&quot;Lex&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:2829,&quot;Comments&quot;:647,&quot;Blanks&quot;:887},{&quot;Language&quot;:&quot;Zsh&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:2822,&quot;Comments&quot;:2395,&quot;Blanks&quot;:667},{&quot;Language&quot;:&quot;R&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:2590,&quot;Comments&quot;:614,&quot;Blanks&quot;:902},{&quot;Language&quot;:&quot;Haskell&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:2409,&quot;Comments&quot;:615,&quot;Blanks&quot;:586},{&quot;Language&quot;:&quot;AWK&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:2157,&quot;Comments&quot;:903,&quot;Blanks&quot;:178},{&quot;Language&quot;:&quot;Scala&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:2124,&quot;Comments&quot;:621,&quot;Blanks&quot;:553},{&quot;Language&quot;:&quot;Vim Script&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:1969,&quot;Comments&quot;:196,&quot;Blanks&quot;:196},{&quot;Language&quot;:&quot;SWIG&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:1844,&quot;Comments&quot;:310,&quot;Blanks&quot;:240},{&quot;Language&quot;:&quot;COBOL&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:1739,&quot;Comments&quot;:20,&quot;Blanks&quot;:181},{&quot;Language&quot;:&quot;Visual Basic&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:1704,&quot;Comments&quot;:295,&quot;Blanks&quot;:604},{&quot;Language&quot;:&quot;Fish&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:1677,&quot;Comments&quot;:225,&quot;Blanks&quot;:284},{&quot;Language&quot;:&quot;Groovy&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:1631,&quot;Comments&quot;:689,&quot;Blanks&quot;:356},{&quot;Language&quot;:&quot;Arturo&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:1541,&quot;Comments&quot;:0,&quot;Blanks&quot;:64},{&quot;Language&quot;:&quot;Arduino C++&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:1428,&quot;Comments&quot;:459,&quot;Blanks&quot;:352},{&quot;Language&quot;:&quot;Jupyter Notebooks&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:1263,&quot;Comments&quot;:335,&quot;Blanks&quot;:192},{&quot;Language&quot;:&quot;VBScript&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:1239,&quot;Comments&quot;:275,&quot;Blanks&quot;:310},{&quot;Language&quot;:&quot;Elm&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:1221,&quot;Comments&quot;:13,&quot;Blanks&quot;:291},{&quot;Language&quot;:&quot;Objective-C++&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:1129,&quot;Comments&quot;:0,&quot;Blanks&quot;:39},{&quot;Language&quot;:&quot;Elixir&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:1128,&quot;Comments&quot;:35,&quot;Blanks&quot;:145},{&quot;Language&quot;:&quot;SystemVerilog&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:966,&quot;Comments&quot;:0,&quot;Blanks&quot;:240},{&quot;Language&quot;:&quot;LLVM&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:949,&quot;Comments&quot;:0,&quot;Blanks&quot;:103},{&quot;Language&quot;:&quot;jq&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:882,&quot;Comments&quot;:24,&quot;Blanks&quot;:9},{&quot;Language&quot;:&quot;Just&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:872,&quot;Comments&quot;:213,&quot;Blanks&quot;:314},{&quot;Language&quot;:&quot;XAML&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:746,&quot;Comments&quot;:3,&quot;Blanks&quot;:42},{&quot;Language&quot;:&quot;Common Lisp&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:702,&quot;Comments&quot;:172,&quot;Blanks&quot;:192},{&quot;Language&quot;:&quot;Edn&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:676,&quot;Comments&quot;:18,&quot;Blanks&quot;:12},{&quot;Language&quot;:&quot;FORTRAN Modern&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:625,&quot;Comments&quot;:4,&quot;Blanks&quot;:118},{&quot;Language&quot;:&quot;Coq&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:604,&quot;Comments&quot;:0,&quot;Blanks&quot;:123},{&quot;Language&quot;:&quot;Astro&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:567,&quot;Comments&quot;:9,&quot;Blanks&quot;:69},{&quot;Language&quot;:&quot;ABNF&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:363,&quot;Comments&quot;:24,&quot;Blanks&quot;:151},{&quot;Language&quot;:&quot;Forth&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:356,&quot;Comments&quot;:1,&quot;Blanks&quot;:77},{&quot;Language&quot;:&quot;Clojure&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:336,&quot;Comments&quot;:25,&quot;Blanks&quot;:48},{&quot;Language&quot;:&quot;Razor&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:323,&quot;Comments&quot;:32,&quot;Blanks&quot;:60},{&quot;Language&quot;:&quot;Nim&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:312,&quot;Comments&quot;:63,&quot;Blanks&quot;:78},{&quot;Language&quot;:&quot;Emacs Lisp&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:306,&quot;Comments&quot;:58,&quot;Blanks&quot;:52},{&quot;Language&quot;:&quot;GLSL&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:302,&quot;Comments&quot;:41,&quot;Blanks&quot;:84},{&quot;Language&quot;:&quot;C Shell&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:294,&quot;Comments&quot;:66,&quot;Blanks&quot;:50},{&quot;Language&quot;:&quot;AutoHotKey&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:288,&quot;Comments&quot;:68,&quot;Blanks&quot;:55},{&quot;Language&quot;:&quot;Cabal&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:243,&quot;Comments&quot;:45,&quot;Blanks&quot;:26},{&quot;Language&quot;:&quot;Cython&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:220,&quot;Comments&quot;:2,&quot;Blanks&quot;:63},{&quot;Language&quot;:&quot;Alex&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:176,&quot;Comments&quot;:0,&quot;Blanks&quot;:62},{&quot;Language&quot;:&quot;PSL Assertion&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:165,&quot;Comments&quot;:0,&quot;Blanks&quot;:28},{&quot;Language&quot;:&quot;.NET Resource&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:147,&quot;Comments&quot;:118,&quot;Blanks&quot;:3},{&quot;Language&quot;:&quot;Madlang&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:136,&quot;Comments&quot;:0,&quot;Blanks&quot;:26},{&quot;Language&quot;:&quot;GDScript&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:119,&quot;Comments&quot;:4,&quot;Blanks&quot;:39},{&quot;Language&quot;:&quot;Snakemake&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:109,&quot;Comments&quot;:37,&quot;Blanks&quot;:30},{&quot;Language&quot;:&quot;LOLCODE&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:104,&quot;Comments&quot;:17,&quot;Blanks&quot;:32},{&quot;Language&quot;:&quot;VHDL&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:100,&quot;Comments&quot;:0,&quot;Blanks&quot;:36},{&quot;Language&quot;:&quot;Pacman&apos;s makepkg&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:96,&quot;Comments&quot;:6,&quot;Blanks&quot;:15},{&quot;Language&quot;:&quot;OpenSCAD&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:95,&quot;Comments&quot;:6,&quot;Blanks&quot;:11},{&quot;Language&quot;:&quot;Nushell&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:92,&quot;Comments&quot;:0,&quot;Blanks&quot;:22},{&quot;Language&quot;:&quot;Gml&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:90,&quot;Comments&quot;:0,&quot;Blanks&quot;:2},{&quot;Language&quot;:&quot;NuGet Config&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:79,&quot;Comments&quot;:0,&quot;Blanks&quot;:0},{&quot;Language&quot;:&quot;ABAP&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:75,&quot;Comments&quot;:36,&quot;Blanks&quot;:38},{&quot;Language&quot;:&quot;Open Policy Agent&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:74,&quot;Comments&quot;:24,&quot;Blanks&quot;:42},{&quot;Language&quot;:&quot;Julia&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:73,&quot;Comments&quot;:1,&quot;Blanks&quot;:10},{&quot;Language&quot;:&quot;Monkey C&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:69,&quot;Comments&quot;:0,&quot;Blanks&quot;:0},{&quot;Language&quot;:&quot;Vala&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:64,&quot;Comments&quot;:0,&quot;Blanks&quot;:20},{&quot;Language&quot;:&quot;Phix&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:63,&quot;Comments&quot;:6,&quot;Blanks&quot;:24},{&quot;Language&quot;:&quot;Module-Definition&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:48,&quot;Comments&quot;:0,&quot;Blanks&quot;:22},{&quot;Language&quot;:&quot;Ada&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:47,&quot;Comments&quot;:0,&quot;Blanks&quot;:2},{&quot;Language&quot;:&quot;Jsonnet&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:38,&quot;Comments&quot;:1,&quot;Blanks&quot;:5},{&quot;Language&quot;:&quot;FlatBuffers Schema&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:36,&quot;Comments&quot;:3,&quot;Blanks&quot;:7},{&quot;Language&quot;:&quot;CUE&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:34,&quot;Comments&quot;:0,&quot;Blanks&quot;:0},{&quot;Language&quot;:&quot;Lean&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:24,&quot;Comments&quot;:0,&quot;Blanks&quot;:3},{&quot;Language&quot;:&quot;HEX&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:23,&quot;Comments&quot;:0,&quot;Blanks&quot;:0},{&quot;Language&quot;:&quot;Wolfram&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:20,&quot;Comments&quot;:0,&quot;Blanks&quot;:3},{&quot;Language&quot;:&quot;MoonScript&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:18,&quot;Comments&quot;:0,&quot;Blanks&quot;:12},{&quot;Language&quot;:&quot;Lingua Franca&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:16,&quot;Comments&quot;:0,&quot;Blanks&quot;:0},{&quot;Language&quot;:&quot;FORTRAN Legacy&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:13,&quot;Comments&quot;:0,&quot;Blanks&quot;:0},{&quot;Language&quot;:&quot;F#&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:9,&quot;Comments&quot;:2,&quot;Blanks&quot;:2},{&quot;Language&quot;:&quot;Mlatu&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:8,&quot;Comments&quot;:0,&quot;Blanks&quot;:0},{&quot;Language&quot;:&quot;Racket&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:6,&quot;Comments&quot;:1,&quot;Blanks&quot;:0},{&quot;Language&quot;:&quot;Xcode Config&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:4,&quot;Comments&quot;:0,&quot;Blanks&quot;:0},{&quot;Language&quot;:&quot;Raku&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:4,&quot;Comments&quot;:0,&quot;Blanks&quot;:1},{&quot;Language&quot;:&quot;Pyret&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:3,&quot;Comments&quot;:0,&quot;Blanks&quot;:0},{&quot;Language&quot;:&quot;MDX&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:0,&quot;Comments&quot;:49844,&quot;Blanks&quot;:12807},{&quot;Language&quot;:&quot;Plain Text&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:0,&quot;Comments&quot;:55756858,&quot;Blanks&quot;:2132456},{&quot;Language&quot;:&quot;Standard ML (SML)&quot;,&quot;Files&quot;:&quot;&quot;,&quot;Lines&quot;:0,&quot;Code&quot;:0,&quot;Comments&quot;:38,&quot;Blanks&quot;:0},{&quot;Language&quot;:&quot;Gherkin (Cucumber)&quot;,&quot;Files&quot;:&quot;&quot;,&quot;Lines&quot;:0,&quot;Code&quot;:0,&quot;Comments&quot;:523700,&quot;Blanks&quot;:8855},{&quot;Language&quot;:&quot;Bean&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:0,&quot;Comments&quot;:0,&quot;Blanks&quot;:0},{&quot;Language&quot;:&quot;Markdown&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:0,&quot;Comments&quot;:35203447,&quot;Blanks&quot;:16642677}]
    // Create charts
    createComplexityChart(complexity, fileAnalysis);
    createCodeQualityChart(codeMetrics, fileAnalysis);
    createPopularityBubbleChart(dependencies);
    createCyclomaticDistributionChart(fileAnalysis);
    createLOCDistributionChart(fileAnalysis);
    createDownloadDominanceChart(packageStats.download_dominance);
    createUnderratedPackagesChart(packageStats.underrated_packages);
    createLanguageGraph(languages);
}

// Complexity distribution chart
function createComplexityChart(complexity, fileAnalysis) {
  const ctx = document.getElementById(&apos;complexity-chart&apos;).getContext(&apos;2d&apos;);
  
  new Chart(ctx, {
    type: &apos;bar&apos;,
    data: {
      labels: [&apos;Cyclomatic Complexity&apos;, &apos;Lines of Code&apos;],
      datasets: [
        {
          label: &apos;Average&apos;,
          data: [fileAnalysis.avg_cyclomatic_per_file, complexity.avg_loc],
          backgroundColor: &apos;rgba(99, 102, 241, 0.6)&apos;,
          borderColor: &apos;rgb(99, 102, 241)&apos;,
          borderWidth: 1
        },
        {
          label: &apos;Median&apos;,
          data: [fileAnalysis.median_cyclomatic, fileAnalysis.median_loc],
          backgroundColor: &apos;rgba(139, 92, 246, 0.6)&apos;,
          borderColor: &apos;rgb(139, 92, 246)&apos;,
          borderWidth: 1
        }
      ]
    },
    options: {
      responsive: true,
      maintainAspectRatio: false,
      plugins: {
        title: {
          display: false
        },
        legend: {
          position: &apos;top&apos;
        },
        tooltip: {
          callbacks: {
            label: function(context) {
              return `${context.dataset.label}: ${context.raw.toFixed(1)}`;
            }
          }
        }
      },
      scales: {
        y: {
          beginAtZero: true
        }
      }
    }
  });
}

// Code quality chart
function createCodeQualityChart(codeMetrics, fileAnalysis) {
const ctx = document.getElementById(&apos;code-quality-chart&apos;).getContext(&apos;2d&apos;);

// Normalize data to improve visualization
const miNormalized = codeMetrics.avg_mi / 100 * 10; // Maintainability index normalized to 10
const acNormalized = Math.min(codeMetrics.avg_afferent_coupling / 5, 10); // Limited to 10
const ecNormalized = Math.min(codeMetrics.avg_efferent_coupling / 5, 10); // Limited to 10
const filesNormalized = Math.min(codeMetrics.avg_files / 5, 10); // Limited to 10
const ccNormalized = Math.min(fileAnalysis.avg_cyclomatic_per_file, 10); // Limited to 10

new Chart(ctx, {
    type: &apos;radar&apos;,
    data: {
    labels: [
        &apos;Maintainability Index&apos;, 
        &apos;Cyclomatic Complexity&apos;, 
        &apos;Afferent Coupling&apos;, 
        &apos;Efferent Coupling&apos;, 
        &apos;Files per Package&apos;
    ],
    datasets: [{
        label: &apos;Code Quality&apos;,
        data: [
        miNormalized,
        ccNormalized,
        acNormalized,
        ecNormalized,
        filesNormalized
        ],
        fill: true,
        backgroundColor: &apos;rgba(99, 102, 241, 0.3)&apos;,
        borderColor: &apos;rgb(99, 102, 241)&apos;,
        pointBackgroundColor: &apos;rgb(99, 102, 241)&apos;,
        pointBorderColor: &apos;#fff&apos;,
        pointHoverBackgroundColor: &apos;#fff&apos;,
        pointHoverBorderColor: &apos;rgb(99, 102, 241)&apos;
    },
    // {
    //     label: &apos;Ideal&apos;,
    //     data: [10, 5, acNormalized, ecNormalized, filesNormalized],
    //     fill: true,
    //     backgroundColor: &apos;rgba(16, 185, 129, 0.1)&apos;,
    //     borderColor: &apos;rgba(16, 185, 129, 0.8)&apos;,
    //     borderWidth: 2,
    //     borderDash: [5, 5],
    //     pointBackgroundColor: &apos;rgba(16, 185, 129, 0.8)&apos;,
    //     pointBorderColor: &apos;#fff&apos;,
    //     pointHoverBackgroundColor: &apos;#fff&apos;,
    //     pointHoverBorderColor: &apos;rgb(16, 185, 129)&apos;
    // }
    ]
    },
    options: {
    responsive: true,
    maintainAspectRatio: false,
    plugins: {
        legend: {
        display: true,
        position: &apos;top&apos;
        },
        tooltip: {
        callbacks: {
            label: function(context) {
            const labels = [
                `MI: ${codeMetrics.avg_mi.toFixed(1)}`,
                `CC: ${fileAnalysis.avg_cyclomatic_per_file.toFixed(1)}`,
                `AC: ${codeMetrics.avg_afferent_coupling.toFixed(1)}`,
                `EC: ${codeMetrics.avg_efferent_coupling.toFixed(1)}`,
                `Files: ${codeMetrics.avg_files.toFixed(1)}`
            ];
            if (context.datasetIndex === 0) {
                return labels[context.dataIndex];
            } else {
                // Reference values for ideal
                const idealLabels = [
                &apos;Ideal Maintainability Index: 85-100&apos;,
                &apos;Ideal Cyclomatic Complexity: &lt;= 5&apos;,
                &apos;There is not ideal value for Afferent Coupling&apos;,
                &apos;There is not ideal value for Efferent Coupling&apos;,
                &apos;There is not ideal value for Files per Package&apos;
                ];
                return idealLabels[context.dataIndex];
            }
            }
        }
        }
    },
    scales: {
        r: {
        min: 0,
        max: 10,
        ticks: {
            display: false
        },
        pointLabels: {
            font: {
            size: 12,
            weight: &apos;bold&apos;
            }
        }
        }
    }
    }
});
}






// Version corrigée de la fonction createPopularityBubbleChart pour éviter la superposition des bulles
function createPopularityBubbleChart(dependencies) {
const ctx = document.getElementById(&apos;popularity-bubble-chart&apos;).getContext(&apos;2d&apos;);

// Prendre les 30 premiers paquets par usage
const topDependencies = dependencies.slice(0, 30);

// Appliquer un algorithme pour éviter la superposition des bulles
const processedData = applyBubbleCollisionAvoidance(topDependencies);

// Créer le graphique
const chart = new Chart(ctx, {
type: &apos;bubble&apos;,
data: {
  datasets: [{
    label: &apos;Packages populaires&apos;,
    data: processedData,
    backgroundColor: function(context) {
      // Calcul d&apos;une couleur basée sur l&apos;usage et les étoiles
      const usage = context.raw.y;
      const stars = context.raw.x;
      
      // Coefficient de popularité normalisé (0-1)
      const popularity = Math.min(1, Math.log(usage + stars + 1) / 15);
      
      // Dégradé de couleur de bleu foncé à orange
      return `rgba(${Math.floor(25 + popularity * 230)}, ${Math.floor(100 + popularity * 105)}, ${Math.floor(230 - popularity * 190)}, 0.7)`;
    }
  }]
},
options: {
  responsive: true,
  maintainAspectRatio: false,
  scales: {
    x: {
      title: {
        display: true,
        text: &apos;GitHub Stars&apos;
      },
      type: &apos;logarithmic&apos;,
      min: 1
    },
    y: {
      title: {
        display: true,
        text: &apos;Number of dependencies&apos;
      },
      type: &apos;logarithmic&apos;,
      min: 1
    }
  },
  plugins: {
    tooltip: {
      callbacks: {
        label: function(context) {
          const packageName = context.raw.name;
          return [
            `Package: ${packageName}`,
            `Stars: ${context.raw.x.toLocaleString()}`,
            `Usage: ${context.raw.y.toLocaleString()}`,
            `Forks: ${Math.pow(context.raw.r / 3, 2).toLocaleString()}`
          ];
        }
      }
    },
    title: {
      display: true,
      text: &apos;Popularity of Packages Chart&apos;
    },
    legend: {
      display: false
    }
  }
}
});

return chart;
}

// Fonction pour éviter la superposition des bulles
function applyBubbleCollisionAvoidance(dependencies) {
// Créer les données pour le graphique à bulles
const bubbleData = dependencies.map(dep =&gt; ({
x: dep.stars || 1, // Utiliser au moins 1 pour l&apos;échelle logarithmique
y: dep.usage || 1, // Utiliser au moins 1 pour l&apos;échelle logarithmique
r: Math.min(30, Math.max(5, Math.sqrt(dep.forks || 1) * 2)), // Limiter la taille des bulles
name: dep.name,
// Ajouter les valeurs originales pour référence
originalX: dep.stars || 1,
originalY: dep.usage || 1,
originalR: Math.min(30, Math.max(5, Math.sqrt(dep.forks || 1) * 2))
}));

// Trier par taille de bulle (plus grandes d&apos;abord) pour une meilleure disposition
bubbleData.sort((a, b) =&gt; b.r - a.r);

// Fonction pour vérifier si deux bulles se chevauchent
function bubbleOverlap(b1, b2) {
const distance = Math.sqrt(
  Math.pow(Math.log10(b1.x) - Math.log10(b2.x), 2) + 
  Math.pow(Math.log10(b1.y) - Math.log10(b2.y), 2)
);
// Calculer en échelle logarithmique pour tenir compte de l&apos;échelle du graphique
const minDistance = (b1.r + b2.r) / 100; // Ajuster cette valeur selon vos besoins
return distance &lt; minDistance;
}

// Fonction pour ajuster la position d&apos;une bulle
function adjustBubblePosition(bubble, otherBubbles) {
const MAX_ATTEMPTS = 50;
let attempts = 0;

// Conserver les valeurs originales pour limiter la dérive
const originalLogX = Math.log10(bubble.originalX);
const originalLogY = Math.log10(bubble.originalY);

while (attempts &lt; MAX_ATTEMPTS) {
  let overlap = false;
  
  for (const other of otherBubbles) {
    if (bubbleOverlap(bubble, other)) {
      overlap = true;
      
      // Calculer un vecteur de répulsion (en échelle logarithmique)
      const logX1 = Math.log10(bubble.x);
      const logY1 = Math.log10(bubble.y);
      const logX2 = Math.log10(other.x);
      const logY2 = Math.log10(other.y);
      
      let dx = logX1 - logX2;
      let dy = logY1 - logY2;
      
      // Éviter division par zéro et normaliser
      const len = Math.sqrt(dx * dx + dy * dy) || 0.001;
      dx /= len;
      dy /= len;
      
      // Ajuster la position en échelle logarithmique
      const pushFactor = 0.05; // Ajuster cette valeur au besoin
      const currentLogX = Math.log10(bubble.x);
      const currentLogY = Math.log10(bubble.y);
      
      // Calculer nouvelle position
      const newLogX = currentLogX + dx * pushFactor;
      const newLogY = currentLogY + dy * pushFactor;
      
      // Limiter la dérive par rapport à la position originale
      const maxDrift = 0.5; // Dérive logarithmique maximale autorisée
      const adjustedLogX = Math.max(originalLogX - maxDrift, Math.min(originalLogX + maxDrift, newLogX));
      const adjustedLogY = Math.max(originalLogY - maxDrift, Math.min(originalLogY + maxDrift, newLogY));
      
      // Convertir en échelle linéaire et mettre à jour
      bubble.x = Math.pow(10, adjustedLogX);
      bubble.y = Math.pow(10, adjustedLogY);
      break;
    }
  }
  
  if (!overlap) {
    break;
  }
  
  attempts++;
}

return bubble;
}

// Ajuster les positions des bulles pour éviter les chevauchements
const processedBubbles = [];

for (const bubble of bubbleData) {
const adjustedBubble = adjustBubblePosition(bubble, processedBubbles);
processedBubbles.push(adjustedBubble);
}

return processedBubbles;
}

// Fonction pour créer le graphique de la concentration des téléchargements
function createDownloadDominanceChart(data) {
if (!data || !data.packages || data.packages.length === 0) {
console.warn(&quot;Aucune donnée disponible pour le graphique de concentration des téléchargements&quot;);
return;
}

const ctx = document.getElementById(&apos;download-dominance-chart&apos;).getContext(&apos;2d&apos;);

// Limiter aux 10 premiers packages pour une meilleure lisibilité
const topPackages = data.packages.slice(0, 10);

// Calculer le cumul des pourcentages
let cumulativePercentage = 0;
const cumulativeData = topPackages.map(pkg =&gt; {
cumulativePercentage += pkg.percentage;
return cumulativePercentage;
});

new Chart(ctx, {
type: &apos;bar&apos;,
data: {
  labels: topPackages.map(pkg =&gt; pkg.name),
  datasets: [
    {
      type: &apos;bar&apos;,
      label: &apos;Téléchargements (%)&apos;,
      data: topPackages.map(pkg =&gt; pkg.percentage),
      backgroundColor: &apos;rgba(79, 70, 229, 0.6)&apos;,
      borderColor: &apos;rgb(79, 70, 229)&apos;,
      borderWidth: 1,
      order: 2
    },
    {
      type: &apos;line&apos;,
      label: &apos;Pourcentage cumulé&apos;,
      data: cumulativeData,
      borderColor: &apos;rgba(239, 68, 68, 0.8)&apos;,
      backgroundColor: &apos;rgba(239, 68, 68, 0.1)&apos;,
      borderWidth: 2,
      fill: true,
      tension: 0.4,
      order: 1,
      yAxisID: &apos;percentage&apos;
    }
  ]
},
options: {
  responsive: true,
  maintainAspectRatio: false,
  plugins: {
    title: {
      display: true,
      text: &apos;Concentration des téléchargements mensuels&apos;
    },
    tooltip: {
      callbacks: {
        label: function(context) {
          if (context.datasetIndex === 0) {
            return `${context.dataset.label}: ${context.parsed.y.toFixed(2)}%`;
          } else {
            return `${context.dataset.label}: ${context.parsed.y.toFixed(2)}%`;
          }
        }
      }
    }
  },
  scales: {
    x: {
      ticks: {
        maxRotation: 45,
        minRotation: 45
      }
    },
    y: {
      beginAtZero: true,
      title: {
        display: true,
        text: &apos;% des téléchargements&apos;
      }
    },
    percentage: {
      position: &apos;right&apos;,
      beginAtZero: true,
      max: 100,
      title: {
        display: true,
        text: &apos;% cumulé&apos;
      }
    }
  }
}
});
}

// Fonction pour créer le graphique des packages sous-reconnus
function createUnderratedPackagesChart(data) {
if (!data || data.length === 0) {
console.warn(&quot;Aucune donnée disponible pour le graphique des packages sous-reconnus&quot;);
return;
}

const ctx = document.getElementById(&apos;underrated-packages-chart&apos;).getContext(&apos;2d&apos;);

// Limiter aux 15 premiers packages pour une meilleure lisibilité
const topPackages = data.slice(0, 15);

new Chart(ctx, {
type: &apos;scatter&apos;,
data: {
  datasets: [{
    label: &apos;Underrated Packages&apos;,
    data: topPackages.map(pkg =&gt; ({
      x: pkg.stars,
      y: pkg.downloads_monthly,
      r: Math.sqrt(pkg.download_star_ratio) / 2
    })),
    backgroundColor: function(context) {
      const value = context.raw.r;
      // Plus le ratio est élevé, plus la couleur est intense
      return `rgba(220, 38, 38, ${Math.min(0.8, 0.3 + value / 20)})`;
    },
    pointRadius: topPackages.map(pkg =&gt; Math.min(20, Math.max(5, Math.sqrt(pkg.download_star_ratio) / 2)))
  }]
},
options: {
  responsive: true,
  maintainAspectRatio: false,
  plugins: {
    title: {
      display: true,
      text: &apos;Packages with high downloads but few stars&apos;
    },
    tooltip: {
      callbacks: {
        label: function(context) {
          const package = topPackages[context.dataIndex];
          return [
            `Package: ${package.name}`,
            `Stars: ${formatNumber(package.stars)}`,
            `Monthly downloads: ${formatNumber(package.downloads_monthly)}`,
            `Ratio downloads./stars: ${formatNumber(package.download_star_ratio.toFixed(1))}`
          ];
        }
      }
    },
    legend: {
      display: false
    }
  },
  scales: {
    x: {
      type: &apos;logarithmic&apos;,
      title: {
        display: true,
        text: &apos;GitHub Stars (log. scale)&apos;
      },
      min: 1
    },
    y: {
      type: &apos;logarithmic&apos;,
      title: {
        display: true,
        text: &apos;Monthly downloads (log. scale)&apos;
      },
      min: 1000
    }
  }
}
});
}

function createLOCDistributionChart(dataset) {
const locDistribution = dataset.loc_distribution;
const ctx = document.getElementById(&apos;loc-distribution-chart&apos;).getContext(&apos;2d&apos;);

// Extraction des données du JSON
const labels = Object.keys(locDistribution);
const data = Object.values(locDistribution);

// Tri des étiquettes en ordre numérique
const sortedIndices = labels.map((label, index) =&gt; ({ label, index }))
  .sort((a, b) =&gt; {
    // Extraction des valeurs numériques des plages
    const getMinValue = range =&gt; parseInt(range.split(&apos;-&apos;)[0]);
    return getMinValue(a.label) - getMinValue(b.label);
  });

const sortedLabels = sortedIndices.map(item =&gt; item.label);
const sortedData = sortedIndices.map(item =&gt; data[item.index]);

// Création du graphique
new Chart(ctx, {
  type: &apos;bar&apos;,
  data: {
    labels: sortedLabels,
    datasets: [{
      label: &apos;Nombre de fichiers&apos;,
      data: sortedData,
      backgroundColor: &apos;rgba(79, 70, 229, 0.6)&apos;,
      borderColor: &apos;rgb(79, 70, 229)&apos;,
      borderWidth: 1
    }]
  },
  options: {
    responsive: true,
    maintainAspectRatio: false,
    plugins: {
      title: {
        display: true,
        text: &apos;Distribution of files by lines of code&apos;
      },
      legend: {
        display: false
      },
      tooltip: {
        callbacks: {
          label: function(context) {
            return `Nombre de fichiers: ${formatNumber(context.raw)}`;
          },
          title: function(tooltipItems) {
            return `Plage de LOC: ${tooltipItems[0].label}`;
          }
        }
      }
    },
    scales: {
      y: {
        beginAtZero: true,
        title: {
          display: true,
          text: &apos;Number of files&apos;
        },
        ticks: {
          callback: function(value) {
            return formatNumber(value);
          }
        }
      },
      x: {
        title: {
          display: true,
          text: &apos;Logical lines of code&apos;
        }
      }
    }
  }
});
}

// Fonction pour créer le graphique de distribution de la complexité cyclomatique
function createCyclomaticDistributionChart(dataset) {
const  cyclomaticDistribution  = dataset.cyclomatic_distribution;
const ctx = document.getElementById(&apos;cyclomatic-distribution-chart&apos;).getContext(&apos;2d&apos;);

// Extraction des données du JSON
const labels = Object.keys(cyclomaticDistribution);
const data = Object.values(cyclomaticDistribution);

// Tri des étiquettes en ordre numérique
const sortedIndices = labels.map((label, index) =&gt; ({ label, index }))
  .sort((a, b) =&gt; {
    // Extraction des valeurs numériques des plages
    const getMinValue = range =&gt; parseInt(range.split(&apos;-&apos;)[0]);
    return getMinValue(a.label) - getMinValue(b.label);
  });

const sortedLabels = sortedIndices.map(item =&gt; item.label);
const sortedData = sortedIndices.map(item =&gt; data[item.index]);

// Création du graphique
new Chart(ctx, {
  type: &apos;bar&apos;,
  data: {
    labels: sortedLabels,
    datasets: [{
      label: &apos;Nombre de fichiers&apos;,
      data: sortedData,
      backgroundColor: function(context) {
        // Gradient de couleurs selon la complexité
        // Plus la complexité est élevée, plus la couleur est rouge
        const index = context.dataIndex;
        const complexity = parseInt(sortedLabels[index].split(&apos;-&apos;)[0]);
        
        if (complexity &lt; 10) return &apos;rgba(16, 185, 129, 0.6)&apos;; // vert pour faible complexité
        if (complexity &lt; 30) return &apos;rgba(245, 158, 11, 0.6)&apos;; // jaune pour complexité moyenne
        if (complexity &lt; 60) return &apos;rgba(249, 115, 22, 0.6)&apos;; // orange pour complexité élevée
        return &apos;rgba(239, 68, 68, 0.6)&apos;; // rouge pour complexité très élevée
      },
      borderColor: function(context) {
        const index = context.dataIndex;
        const complexity = parseInt(sortedLabels[index].split(&apos;-&apos;)[0]);
        
        if (complexity &lt; 10) return &apos;rgb(16, 185, 129)&apos;;
        if (complexity &lt; 30) return &apos;rgb(245, 158, 11)&apos;;
        if (complexity &lt; 60) return &apos;rgb(249, 115, 22)&apos;;
        return &apos;rgb(239, 68, 68)&apos;;
      },
      borderWidth: 1
    }]
  },
  options: {
    responsive: true,
    maintainAspectRatio: false,
    plugins: {
      title: {
        display: true,
        textEn: &apos;Distribution of files by cyclomatic complexity&apos;
      },
      legend: {
        display: false
      },
      tooltip: {
        callbacks: {
          label: function(context) {
            return `Nombre de fichiers: ${formatNumber(context.raw)}`;
          },
          title: function(tooltipItems) {
            return `Complexité: ${tooltipItems[0].label}`;
          }
        }
      }
    },
    scales: {
      y: {
        beginAtZero: true,
        title: {
          display: true,
          text: &apos;Number of files&apos;
        },
        ticks: {
          callback: function(value) {
            return formatNumber(value);
          }
        }
      },
      x: {
        title: {
          display: true,
          text: &apos;Cyclomatic complexity&apos;
        }
      }
    }
  }
});
}

function createLanguageGraph(languages) {
    // Récupération du JSON depuis la page HTML

    // Trier les langages par nombre de lignes de code en ordre décroissant
    const sortedLanguages = languages
        .filter(lang =&gt; lang.Code &gt; 0) // Exclure les langages avec 0 lignes de code
        .sort((a, b) =&gt; b.Code - a.Code) // Trier du plus grand au plus petit
        .slice(0, 10);

    // Extraire les noms et les valeurs
    const labels = sortedLanguages.map(lang =&gt; lang.Language);
    const dataValues = sortedLanguages.map(lang =&gt; lang.Code);

    // Sélection de la div pour afficher le graphique
    const ctx = document.getElementById(&quot;chart-languages&quot;).getContext(&quot;2d&quot;);

    // Création du camembert avec Chart.js
    new Chart(ctx, {
        type: &quot;pie&quot;,
        data: {
            labels: labels,
            datasets: [{
                data: dataValues,
                // gradient of blue
                backgroundColor: labels.map((_, i) =&gt; `rgba(99, 102, 241, ${0.6 - i * 0.05})`),
                borderWidth: 1
            }]
        },
        options: {
            responsive: true,
            plugins: {
                legend: {
                    position: &quot;bottom&quot;
                }
            }
        }
    });
}

// Adapter le DOMContentLoaded pour notre nouvelle approche
document.addEventListener(&apos;DOMContentLoaded&apos;, function() {
loadData();
});


&lt;/script&gt;</content>
 </entry>
 
 <entry>
   <title>Plongée dans l'écosystème PHP : État des lieux de la qualité du code</title>
   <link href="https://blog.lepine.pro/fr/plongee-dans-ecosysteme-php/"/>
   <updated>2025-03-05T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/fr/plongee-dans-ecosysteme-php</id>
   <content type="html">&lt;!-- Chart.js --&gt;
&lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.min.js&quot;&gt;&lt;/script&gt;
&lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/heatmap.js/2.0.2/heatmap.min.js&quot;&gt;&lt;/script&gt;


&lt;div class=&quot;rounded-lg mb-8 mt-8&quot;&gt;
    &lt;h3 class=&quot;text-xl font-bold mb-3&quot;&gt;Un mot rapide sur l&apos;approche&lt;/h3&gt;
    
    &lt;div class=&quot;flex justify-between align-middle gap-8&quot;&gt;
      &lt;div&gt;
        &lt;p class=&quot;mb-3&quot;&gt;
          Bonjour ! Je suis Jean-François Lépine, et je m&apos;intéresse à la qualité logicielle depuis plusieurs années. J&apos;ai créé PhpMetrics, puis Ast-Metrics, et j&apos;ai aidé de nombreuses entreprises à industrialiser leurs processus de développement.
        &lt;/p&gt;
        &lt;p class=&quot;mb-3&quot;&gt;Je voulais avoir une vue d&apos;ensemble du code PHP existant, alors j&apos;ai tout analysé. 
          J&apos;ai téléchargé l&apos;intégralité de l&apos;écosystème PHP - pas moins de &lt;span class=&quot;font-bold text-indigo-600&quot;&gt;236 Go&lt;/span&gt; de dépôts Git contenant au total &lt;span class=&quot;font-bold text-indigo-600&quot;&gt;2,3 milliards de lignes de code&lt;/span&gt;.
        &lt;/p&gt;
        &lt;p class=&quot;mb-3&quot;&gt;Le script d&apos;analyse a tourné pendant trois jours complets, traitant chaque fichier. Je l&apos;ai développé en Go pour des performances optimales et un contrôle précis du CPU - l&apos;analyse PHP à cette échelle nécessite une optimisation sérieuse.&lt;/p&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;section class=&quot;mt-48&quot;&gt;
    &lt;h2 class=&quot;text-4xl mt-8 font-bold tracking-tight text-zinc-800 sm:text-5xl dark:text-zinc-100&quot;&gt;
      TL;DR - Trop long ? Pas lu ? Voici la version courte !
    &lt;/h2&gt;

    &lt;p class=&quot;text-gray-600 text-lg mt-4 mb-4&quot;&gt;
      Le PHP open-source est moderne, modulaire et maintenable. Mais attention à la dépendance excessive à quelques packages centraux et aux projets sous-maintenus.
    &lt;/p&gt;
    

    &lt;div&gt;
        &lt;div class=&quot;font-bold&quot;&gt;
          Le PHP moderne est modulaire et concis
        &lt;/div&gt;
        &lt;ul class=&quot;ml-8 list-disc list-inside&quot;&gt;
          &lt;li&gt;36% des fichiers ont moins de 100 lignes de code&lt;/li&gt;
          &lt;li&gt;L&apos;utilisation intensive des interfaces rend l&apos;écosystème hautement modulaire.&lt;/li&gt;
        &lt;/ul&gt;

        &lt;div class=&quot;font-bold mt-4&quot;&gt;La complexité est globalement maîtrisée, mais...&lt;/div&gt;
        &lt;ul class=&quot;ml-8 list-disc list-inside&quot;&gt;
          &lt;li&gt;76,5% des fichiers ont une faible complexité cyclomatique (0-10).&lt;/li&gt;
          &lt;li&gt;Cependant, 23,5% du code est complexe, posant un défi de maintenabilité&lt;/li&gt;
        &lt;/ul&gt;

        &lt;div class=&quot;font-bold mt-4&quot;&gt;Dépendance extrême à un petit ensemble de packages&lt;/div&gt;
        &lt;ul class=&quot;ml-8 list-disc list-inside&quot;&gt;
          &lt;li&gt;Seulement 136 packages (0,05%) représentent 50% de tous les téléchargements.&lt;/li&gt;
          &lt;li&gt;Ces bibliothèques centrales sont bien maintenues, mais créent des risques de dépendance importants.&lt;/li&gt;
        &lt;/ul&gt;

        &lt;div class=&quot;font-bold mt-4&quot;&gt;Le problème de &quot;l&apos;infrastructure fantôme&quot;&lt;/div&gt;
        &lt;ul class=&quot;ml-8 list-disc list-inside&quot;&gt;
          &lt;li&gt;Certains packages très utilisés reçoivent très peu de reconnaissance (peu d&apos;étoiles GitHub).&lt;/li&gt;
          &lt;li&gt;Des dépendances critiques peuvent être maintenues par de très petites équipes.&lt;/li&gt;
        &lt;/ul&gt;

        &lt;div class=&quot;font-bold mt-4&quot;&gt;L&apos;écosystème PHP est sain mais vieillissant&lt;/div&gt;
        &lt;ul class=&quot;ml-8 list-disc list-inside&quot;&gt;
          &lt;li&gt;70% des packages montrent une activité minimale.&lt;/li&gt;
          &lt;li&gt;23% semblent abandonnés, rendant l&apos;audit des dépendances essentiel.&lt;/li&gt;
        &lt;/ul&gt;

    &lt;/div&gt;
    
&lt;/section&gt;

&lt;section class=&quot;mt-48&quot;&gt;
    &lt;h2 class=&quot;text-4xl mt-8 font-bold tracking-tight text-zinc-800 sm:text-5xl dark:text-zinc-100&quot;&gt;Radiographie de l&apos;écosystème Open Source PHP&lt;/h2&gt;

    &lt;div class=&quot;mt-6 mb-6 bg-yellow-50 p-5 rounded-lg border-l-4 border-yellow-500&quot;&gt;
        &lt;h4 class=&quot;font-bold text-lg mb-2&quot;&gt;Open Source vs Contexte Entreprise :&lt;/h4&gt;
        &lt;p&gt;Il est important de noter que ces métriques proviennent de projets open-source, qui diffèrent significativement des bases de code typiques d&apos;entreprise. Les bibliothèques open-source privilégient souvent l&apos;extensibilité et l&apos;abstraction (ce qui explique le ratio élevé interfaces/classes), tandis que les projets d&apos;entreprise se concentrent davantage sur l&apos;implémentation de la logique métier et des cas d&apos;utilisation spécifiques. Lors de l&apos;évaluation de votre propre base de code, considérez si votre contexte s&apos;aligne davantage avec les modèles de conception de bibliothèques ou les implémentations spécifiques aux applications.&lt;/p&gt;
    &lt;/div&gt;

    &lt;div class=&quot;bg-white rounded-lg shadow p-6&quot;&gt;
        &lt;p class=&quot;mb-5 text-lg&quot;&gt;
            J&apos;ai analysé &lt;span class=&quot;font-bold text-indigo-600&quot;&gt;11,1 millions&lt;/span&gt; de fichiers PHP,
            totalisant plus de &lt;span class=&quot;font-bold text-indigo-600&quot;&gt;707,6 millions&lt;/span&gt; de lignes de code PHP — faisant partie d&apos;un
            impressionnant total de &lt;span class=&quot;font-bold text-indigo-600&quot;&gt;2,3 milliards de lignes de code&lt;/span&gt; toutes langues confondues dans l&apos;écosystème Packagist.
            Ces métriques offrent un aperçu réel de la structure du PHP dans la nature, au-delà des bonnes pratiques théoriques.
        &lt;/p&gt;

        &lt;div class=&quot;grid grid-cols-1 md:grid-cols-2 gap-8 mb-8&quot;&gt;
            &lt;div class=&quot;border-l-4 border-indigo-500 pl-5 py-2&quot;&gt;
                &lt;h3 class=&quot;font-bold text-xl mb-3 text-gray-800&quot;&gt;Distribution de la taille des fichiers&lt;/h3&gt;
                &lt;p class=&quot;mb-3&quot;&gt;Une écrasante majorité de &lt;span class=&quot;font-bold&quot;&gt;36%&lt;/span&gt;
                    des fichiers contiennent moins de 100 lignes de code logique, avec une médiane de seulement &lt;span class=&quot;font-bold&quot;&gt;&lt;span&gt;4,0&lt;/span&gt; lignes&lt;/span&gt;.&lt;/p&gt;
                &lt;p class=&quot;text-gray-600&quot;&gt;Cela révèle une forte préférence à l&apos;échelle de l&apos;écosystème pour des implémentations petites et ciblées - bien plus petites que ce que la plupart des guides de style suggèrent comme maximum. Cela reflète également l&apos;utilisation massive des interfaces dans l&apos;écosystème, avec
                    &lt;span class=&quot;font-bold&quot;&gt;3,7M d&apos;interfaces&lt;/span&gt; comparé à
                    &lt;span class=&quot;font-bold&quot;&gt;7,4M de classes&lt;/span&gt; - beaucoup de ces interfaces ne contenant aucun code logique, uniquement des signatures de méthodes.&lt;/p&gt;
            &lt;/div&gt;

            &lt;div class=&quot;border-l-4 border-indigo-500 pl-5 py-2&quot;&gt;
                &lt;h3 class=&quot;font-bold text-xl mb-3 text-gray-800&quot;&gt;Complexité Cyclomatique&lt;/h3&gt;
                &lt;p class=&quot;mb-3&quot;&gt;&lt;span class=&quot;font-bold&quot;&gt;76,5%&lt;/span&gt; des fichiers maintiennent une faible complexité (0-10), tandis que &lt;span class=&quot;font-bold&quot;&gt;23,5%&lt;/span&gt; s&apos;aventurent dans un territoire de plus haute complexité.&lt;/p&gt;
                &lt;p class=&quot;text-gray-600&quot;&gt;Même avec un accent mis sur les petits fichiers, près d&apos;un quart de la base de code montre encore une complexité significative – mettant en évidence des zones où la maintenabilité pourrait être problématique.&lt;/p&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/section&gt;

&lt;!-- Statistiques générales --&gt;
&lt;section class=&quot;mt-4&quot;&gt;
    &lt;div class=&quot;grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6&quot;&gt;
        &lt;!-- Total des Packages --&gt;
        &lt;div class=&quot;bg-white rounded-lg shadow p-6&quot;&gt;
            &lt;div class=&quot;flex items-center justify-between mb-4&quot;&gt;
                &lt;h3 class=&quot;text-lg font-medium text-gray-700&quot;&gt;Packages Analysés&lt;/h3&gt;
                &lt;span class=&quot;text-indigo-600 bg-indigo-100 p-2 rounded-full&quot;&gt;
            &lt;i class=&quot;fas fa-box-open&quot;&gt;&lt;/i&gt;
          &lt;/span&gt;
            &lt;/div&gt;
            &lt;p class=&quot;text-3xl font-bold mb-2&quot;&gt;411,6K&lt;/p&gt;
            &lt;p class=&quot;text-sm text-gray-500&quot;&gt;Packages PHP de l&apos;écosystème PHP&lt;/p&gt;
        &lt;/div&gt;

        &lt;!-- Total des Fichiers --&gt;
        &lt;div class=&quot;bg-white rounded-lg shadow p-6&quot;&gt;
            &lt;div class=&quot;flex items-center justify-between mb-4&quot;&gt;
                &lt;h3 class=&quot;text-lg font-medium text-gray-700&quot;&gt;Fichiers&lt;/h3&gt;
                &lt;span class=&quot;text-green-600 bg-green-100 p-2 rounded-full&quot;&gt;
            &lt;i class=&quot;fas fa-cubes&quot;&gt;&lt;/i&gt;
          &lt;/span&gt;
            &lt;/div&gt;
            &lt;p class=&quot;text-3xl font-bold mb-2&quot;&gt;11,1M&lt;/p&gt;
            &lt;p class=&quot;text-sm text-gray-500&quot;&gt;Fichiers PHP détectés&lt;/p&gt;
        &lt;/div&gt;

        &lt;!-- Total des Méthodes --&gt;
        &lt;div class=&quot;bg-white rounded-lg shadow p-6&quot;&gt;
            &lt;div class=&quot;flex items-center justify-between mb-4&quot;&gt;
                &lt;h3 class=&quot;text-lg font-medium text-gray-700&quot;&gt;Méthodes&lt;/h3&gt;
                &lt;span class=&quot;text-blue-600 bg-blue-100 p-2 rounded-full&quot;&gt;
            &lt;i class=&quot;fas fa-code&quot;&gt;&lt;/i&gt;
          &lt;/span&gt;
            &lt;/div&gt;
            &lt;p class=&quot;text-3xl font-bold mb-2&quot;&gt;53,4M&lt;/p&gt;
            &lt;p class=&quot;text-sm text-gray-500&quot;&gt;Méthodes PHP analysées&lt;/p&gt;
        &lt;/div&gt;

        &lt;!-- Total des lignes de code --&gt;
        &lt;div class=&quot;bg-white rounded-lg shadow p-6&quot;&gt;
            &lt;div class=&quot;flex items-center justify-between mb-4&quot;&gt;
                &lt;h3 class=&quot;text-lg font-medium text-gray-700&quot;&gt;Lignes de Code&lt;/h3&gt;
                &lt;span class=&quot;text-orange-600 bg-orange-100 p-2 rounded-full&quot;&gt;
            &lt;i class=&quot;fas fa-file-code&quot;&gt;&lt;/i&gt;
          &lt;/span&gt;
            &lt;/div&gt;
            &lt;p class=&quot;text-3xl font-bold mb-2&quot;&gt;707,6M&lt;/p&gt;
            &lt;div class=&quot;text-sm text-gray-500&quot;&gt;
                Total des lignes de code
                &lt;div class=&quot;text-gray-400&quot;&gt;Dont &lt;span class=&quot;font-bold&quot;&gt;362,9M&lt;/span&gt; lignes de code logique&lt;/span&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
&lt;/section&gt;

&lt;!-- Section de distribution des métriques --&gt;
&lt;section class=&quot;mt-4 grid grid-cols-1 lg:grid-cols-2 gap-8 mb-12&quot;&gt;
    &lt;!-- Distribution des lignes de code --&gt;
    &lt;div class=&quot;bg-white rounded-lg shadow p-6&quot;&gt;
        &lt;h3 class=&quot;text-xl font-bold mb-4 text-gray-700&quot;&gt;Distribution des lignes de code logique&lt;/h3&gt;
        &lt;div class=&quot;h-64&quot;&gt;
            &lt;canvas id=&quot;loc-distribution-chart&quot;&gt;&lt;/canvas&gt;
        &lt;/div&gt;
    &lt;/div&gt;

    &lt;!-- Distribution de la complexité cyclomatique --&gt;
    &lt;div class=&quot;bg-white rounded-lg shadow p-6&quot;&gt;
        &lt;h3 class=&quot;text-xl font-bold mb-4 text-gray-700&quot;&gt;Distribution de la complexité cyclomatique&lt;/h3&gt;
        &lt;div class=&quot;h-64&quot;&gt;
            &lt;canvas id=&quot;cyclomatic-distribution-chart&quot;&gt;&lt;/canvas&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/section&gt;


&lt;section class=&quot;mt-48&quot;&gt;
    &lt;h2 class=&quot;text-4xl mt-8 font-bold tracking-tight text-zinc-800 sm:text-5xl dark:text-zinc-100&quot;&gt;Concentration des téléchargements : Le fondement de PHP&lt;/h2&gt;

    &lt;p class=&quot;text-gray-600 text-lg mt-4 mb-4&quot;&gt;
        L&apos;un des aspects les plus révélateurs de l&apos;écosystème PHP est l&apos;extrême concentration des téléchargements.
    &lt;/p&gt;

    &lt;div class=&quot;bg-white rounded-lg shadow p-6 mt-4&quot;&gt;
        &lt;p class=&quot;text-gray-800 mb-4&quot;&gt;
            Les données montrent que &lt;span class=&quot;font-bold text-indigo-600&quot;&gt;seulement 136 packages
          (0,05 % de tous les packages)&lt;/span&gt; représentent plus de la moitié de tous les téléchargements mensuels dans l&apos;ensemble de l&apos;écosystème.
        &lt;/p&gt;

        &lt;p class=&quot;text-gray-800 mb-4&quot;&gt;
            Cette concentration reflète plusieurs dynamiques importantes dans le monde PHP :
        &lt;/p&gt;

        &lt;div class=&quot;grid grid-cols-1 md:grid-cols-2 gap-6 my-6&quot;&gt;
            &lt;div class=&quot;border-l-4 border-green-500 pl-4 py-2&quot;&gt;
                &lt;h3 class=&quot;font-bold text-lg mb-2&quot;&gt;Une base consolidée&lt;/h3&gt;
                &lt;p class=&quot;text-gray-700&quot;&gt;L&apos;écosystème PHP a mûri pour s&apos;appuyer sur un ensemble relativement restreint de bibliothèques centrales qui fournissent des fonctionnalités essentielles. Cette base stable signifie que les développeurs n&apos;ont pas besoin d&apos;évaluer constamment des implémentations concurrentes pour les besoins fondamentaux.&lt;/p&gt;
            &lt;/div&gt;

            &lt;div class=&quot;border-l-4 border-amber-500 pl-4 py-2&quot;&gt;
                &lt;h3 class=&quot;font-bold text-lg mb-2&quot;&gt;Effets de chaîne des dépendances&lt;/h3&gt;
                &lt;p class=&quot;text-gray-700&quot;&gt;De nombreux packages populaires sont des éléments fondamentaux qui sont inclus comme dépendances transitives dans des milliers de projets. Cet effet en cascade amplifie le nombre de téléchargements pour ces packages fondamentaux.&lt;/p&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;

    &lt;div class=&quot;rounded-lg shadow p-6 mt-4 bg-green-100 text-green-800&quot;&gt;
        &lt;h3 class=&quot;font-bold text-lg mb-3&quot;&gt;🎉 Les bonnes nouvelles&lt;/h3&gt;
        &lt;p class=&quot;text-gray-800 mb-3&quot;&gt;
            La découverte encourageante est que ces packages à fort impact sont très majoritairement bien maintenus et sains :
        &lt;/p&gt;
        &lt;ul class=&quot;space-y-2&quot;&gt;
            &lt;li class=&quot;flex items-start&quot;&gt;
                &lt;span class=&quot;text-green-500 mr-2&quot;&gt;✓&lt;/span&gt;
                &lt;span&gt;Presque tous les packages populaires ont des communautés de contributeurs actives&lt;/span&gt;
            &lt;/li&gt;
            &lt;li class=&quot;flex items-start&quot;&gt;
                &lt;span class=&quot;text-green-500 mr-2&quot;&gt;✓&lt;/span&gt;
                &lt;span&gt;La plupart disposent de suites de tests robustes et de pipelines CI/CD&lt;/span&gt;
            &lt;/li&gt;
            &lt;li class=&quot;flex items-start&quot;&gt;
                &lt;span class=&quot;text-green-500 mr-2&quot;&gt;✓&lt;/span&gt;
                &lt;span&gt;Beaucoup sont soutenus par des entreprises ou des fondations assurant leur stabilité à long terme&lt;/span&gt;
            &lt;/li&gt;
        &lt;/ul&gt;
    &lt;/div&gt;
&lt;/section&gt;

&lt;!-- Section pour la dominance des téléchargements --&gt;
&lt;section class=&quot;bg-white rounded-lg shadow p-6 mb-12 mt-4&quot;&gt;
    &lt;h3 class=&quot;text-xl font-bold mb-4 text-gray-700&quot;&gt;Dominance des téléchargements&lt;/h3&gt;
    &lt;p class=&quot;text-gray-600 mb-4&quot;&gt;Packages qui représentent collectivement 50% ou plus des téléchargements mensuels&lt;/p&gt;

    &lt;div class=&quot;grid grid-cols-1 md:grid-cols-3 gap-4 mb-6&quot;&gt;
        &lt;div class=&quot;rounded p-4 bg-indigo-50&quot;&gt;
            &lt;p class=&quot;text-sm text-gray-500&quot;&gt;Nombre de packages dominants&lt;/p&gt;
            &lt;p class=&quot;font-bold text-2xl text-indigo-600&quot;&gt;136&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;rounded p-4 bg-indigo-50&quot;&gt;
            &lt;p class=&quot;text-sm text-gray-500&quot;&gt;% du total des téléchargements&lt;/p&gt;
            &lt;p class=&quot;font-bold text-2xl text-indigo-600&quot;&gt;0,05&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;rounded p-4 bg-indigo-50&quot;&gt;
            &lt;p class=&quot;text-sm text-gray-500&quot;&gt;Total des téléchargements mensuels&lt;/p&gt;
            &lt;p class=&quot;font-bold text-2xl text-indigo-600&quot;&gt;2808,6M&lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;

&lt;!-- convert xxx to xxxK, xxxM --&gt;

2808.6M

&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;h-64 mb-4&quot;&gt;
&lt;canvas id=&quot;download-dominance-chart&quot;&gt;&lt;/canvas&gt;
&lt;/div&gt;

&lt;div class=&quot;overflow-x-auto mt-6&quot; style=&quot;height:500px; overflow:auto;&quot;&gt;
&lt;table class=&quot;min-w-full divide-y divide-gray-200&quot;&gt;
  &lt;thead class=&quot;bg-gray-50&quot;&gt;
    &lt;tr&gt;
      &lt;th class=&quot;px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider&quot;&gt;Package&lt;/th&gt;
      &lt;th class=&quot;px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider&quot;&gt;Monthly downloads&lt;/th&gt;
      &lt;th class=&quot;px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider&quot;&gt;%&lt;/th&gt;
      &lt;th class=&quot;px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider&quot;&gt;Stars&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody class=&quot;bg-white divide-y divide-gray-200&quot;&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/deprecation-contracts&quot; target=_&quot;blank&quot;&gt;symfony/deprecation-contracts&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

15.7M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.55&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 2053.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/polyfill-mbstring&quot; target=_&quot;blank&quot;&gt;symfony/polyfill-mbstring&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

15.5M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.55&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 7847.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/psr/log&quot; target=_&quot;blank&quot;&gt;psr/log&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

15.2M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.55&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 10421.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/psr/container&quot; target=_&quot;blank&quot;&gt;psr/container&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

15.0M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.55&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 9981.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/console&quot; target=_&quot;blank&quot;&gt;symfony/console&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

14.9M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.55&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 9785.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/service-contracts&quot; target=_&quot;blank&quot;&gt;symfony/service-contracts&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

14.7M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.5&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 2595.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/polyfill-intl-normalizer&quot; target=_&quot;blank&quot;&gt;symfony/polyfill-intl-normalizer&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

14.6M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.5&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 2026.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/psr/http-message&quot; target=_&quot;blank&quot;&gt;psr/http-message&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

14.5M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.5&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 6998.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/guzzlehttp/psr7&quot; target=_&quot;blank&quot;&gt;guzzlehttp/psr7&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

14.2M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.5&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 7905.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/guzzlehttp/promises&quot; target=_&quot;blank&quot;&gt;guzzlehttp/promises&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

14.1M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.5&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 7645.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/process&quot; target=_&quot;blank&quot;&gt;symfony/process&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

14.1M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.5&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 7453.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/polyfill-ctype&quot; target=_&quot;blank&quot;&gt;symfony/polyfill-ctype&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

14.1M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.5&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 4056.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/finder&quot; target=_&quot;blank&quot;&gt;symfony/finder&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

14.0M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.5&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 8436.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/guzzlehttp/guzzle&quot; target=_&quot;blank&quot;&gt;guzzlehttp/guzzle&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

14.0M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.5&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 23313.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/string&quot; target=_&quot;blank&quot;&gt;symfony/string&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

13.9M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.5&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 1741.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/ralouphie/getallheaders&quot; target=_&quot;blank&quot;&gt;ralouphie/getallheaders&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

13.9M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.5&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 3778.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/psr/http-factory&quot; target=_&quot;blank&quot;&gt;psr/http-factory&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

13.6M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.5&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 1827.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/nikic/php-parser&quot; target=_&quot;blank&quot;&gt;nikic/php-parser&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

13.6M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.5&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 17179.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/polyfill-php80&quot; target=_&quot;blank&quot;&gt;symfony/polyfill-php80&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

13.6M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.5&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 1731.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/polyfill-intl-grapheme&quot; target=_&quot;blank&quot;&gt;symfony/polyfill-intl-grapheme&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

13.6M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.5&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 1689.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/psr/http-client&quot; target=_&quot;blank&quot;&gt;psr/http-client&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

13.3M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 1685.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/event-dispatcher&quot; target=_&quot;blank&quot;&gt;symfony/event-dispatcher&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

13.3M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 8534.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/monolog/monolog&quot; target=_&quot;blank&quot;&gt;monolog/monolog&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

13.1M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 21132.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/event-dispatcher-contracts&quot; target=_&quot;blank&quot;&gt;symfony/event-dispatcher-contracts&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

13.1M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 3394.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/var-dumper&quot; target=_&quot;blank&quot;&gt;symfony/var-dumper&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

13.0M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 7426.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/myclabs/deep-copy&quot; target=_&quot;blank&quot;&gt;myclabs/deep-copy&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.8M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 8807.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/phpunit/phpunit&quot; target=_&quot;blank&quot;&gt;phpunit/phpunit&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.7M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 19791.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/http-foundation&quot; target=_&quot;blank&quot;&gt;symfony/http-foundation&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.7M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 8652.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/sebastian/diff&quot; target=_&quot;blank&quot;&gt;sebastian/diff&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.6M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 7600.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/doctrine/lexer&quot; target=_&quot;blank&quot;&gt;doctrine/lexer&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.6M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 11102.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/translation-contracts&quot; target=_&quot;blank&quot;&gt;symfony/translation-contracts&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.4M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 2576.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/phpunit/php-code-coverage&quot; target=_&quot;blank&quot;&gt;phpunit/php-code-coverage&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.3M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 8863.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/polyfill-intl-idn&quot; target=_&quot;blank&quot;&gt;symfony/polyfill-intl-idn&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.3M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 3339.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/sebastian/comparator&quot; target=_&quot;blank&quot;&gt;sebastian/comparator&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.3M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 7024.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/sebastian/exporter&quot; target=_&quot;blank&quot;&gt;sebastian/exporter&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.3M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 6801.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/psr/event-dispatcher&quot; target=_&quot;blank&quot;&gt;psr/event-dispatcher&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.2M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 2209.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/phpunit/php-file-iterator&quot; target=_&quot;blank&quot;&gt;phpunit/php-file-iterator&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.2M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 7444.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/sebastian/recursion-context&quot; target=_&quot;blank&quot;&gt;sebastian/recursion-context&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.2M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 6556.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/sebastian/global-state&quot; target=_&quot;blank&quot;&gt;sebastian/global-state&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.1M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 6584.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/sebastian/version&quot; target=_&quot;blank&quot;&gt;sebastian/version&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.1M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 6557.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/phpunit/php-timer&quot; target=_&quot;blank&quot;&gt;phpunit/php-timer&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.1M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 7684.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/sebastian/environment&quot; target=_&quot;blank&quot;&gt;sebastian/environment&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.1M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 6752.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/webmozart/assert&quot; target=_&quot;blank&quot;&gt;webmozart/assert&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.1M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 7588.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/phpunit/php-text-template&quot; target=_&quot;blank&quot;&gt;phpunit/php-text-template&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.1M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 7389.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/http-kernel&quot; target=_&quot;blank&quot;&gt;symfony/http-kernel&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.0M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 8126.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/sebastian/object-enumerator&quot; target=_&quot;blank&quot;&gt;sebastian/object-enumerator&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

12.0M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.45&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 6507.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/sebastian/code-unit-reverse-lookup&quot; target=_&quot;blank&quot;&gt;sebastian/code-unit-reverse-lookup&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.9M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 6690.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/yaml&quot; target=_&quot;blank&quot;&gt;symfony/yaml&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.9M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 3826.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/error-handler&quot; target=_&quot;blank&quot;&gt;symfony/error-handler&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.9M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 2623.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/theseer/tokenizer&quot; target=_&quot;blank&quot;&gt;theseer/tokenizer&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.9M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 5177.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/phar-io/manifest&quot; target=_&quot;blank&quot;&gt;phar-io/manifest&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.9M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 7431.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/translation&quot; target=_&quot;blank&quot;&gt;symfony/translation&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.9M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 6620.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/sebastian/object-reflector&quot; target=_&quot;blank&quot;&gt;sebastian/object-reflector&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.8M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 6257.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/mime&quot; target=_&quot;blank&quot;&gt;symfony/mime&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.8M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 2794.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/phar-io/version&quot; target=_&quot;blank&quot;&gt;phar-io/version&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.8M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 7429.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/routing&quot; target=_&quot;blank&quot;&gt;symfony/routing&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.8M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 7623.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/psr/simple-cache&quot; target=_&quot;blank&quot;&gt;psr/simple-cache&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.5M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 8104.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/sebastian/type&quot; target=_&quot;blank&quot;&gt;sebastian/type&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.5M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 1434.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/doctrine/inflector&quot; target=_&quot;blank&quot;&gt;doctrine/inflector&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.5M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 11290.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/psr/cache&quot; target=_&quot;blank&quot;&gt;psr/cache&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.2M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 5138.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/egulias/email-validator&quot; target=_&quot;blank&quot;&gt;egulias/email-validator&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.2M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 11517.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/sebastian/cli-parser&quot; target=_&quot;blank&quot;&gt;sebastian/cli-parser&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.1M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 1132.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/css-selector&quot; target=_&quot;blank&quot;&gt;symfony/css-selector&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.1M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 7440.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/sebastian/complexity&quot; target=_&quot;blank&quot;&gt;sebastian/complexity&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.1M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 1183.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/sebastian/lines-of-code&quot; target=_&quot;blank&quot;&gt;sebastian/lines-of-code&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.0M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 1101.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/phpunit/php-invoker&quot; target=_&quot;blank&quot;&gt;phpunit/php-invoker&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.0M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 1256.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/ramsey/uuid&quot; target=_&quot;blank&quot;&gt;ramsey/uuid&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.0M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 12515.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/sebastian/code-unit&quot; target=_&quot;blank&quot;&gt;sebastian/code-unit&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.0M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 1131.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/psr/clock&quot; target=_&quot;blank&quot;&gt;psr/clock&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

10.9M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 526.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/doctrine/deprecations&quot; target=_&quot;blank&quot;&gt;doctrine/deprecations&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

10.7M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 1707.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/brick/math&quot; target=_&quot;blank&quot;&gt;brick/math&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

10.6M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.4&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 1907.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/ramsey/collection&quot; target=_&quot;blank&quot;&gt;ramsey/collection&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

10.4M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.35&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 1153.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/nesbot/carbon&quot; target=_&quot;blank&quot;&gt;nesbot/carbon&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

10.1M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.35&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 48.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/polyfill-php83&quot; target=_&quot;blank&quot;&gt;symfony/polyfill-php83&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

9.8M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.35&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 357.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/league/flysystem&quot; target=_&quot;blank&quot;&gt;league/flysystem&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

9.7M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.35&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 13410.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/league/mime-type-detection&quot; target=_&quot;blank&quot;&gt;league/mime-type-detection&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

9.6M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.35&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 1300.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/mailer&quot; target=_&quot;blank&quot;&gt;symfony/mailer&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

9.6M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.35&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 1527.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/doctrine/instantiator&quot; target=_&quot;blank&quot;&gt;doctrine/instantiator&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

9.4M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.35&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 10975.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/filesystem&quot; target=_&quot;blank&quot;&gt;symfony/filesystem&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

9.2M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.35&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 4621.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/vlucas/phpdotenv&quot; target=_&quot;blank&quot;&gt;vlucas/phpdotenv&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

9.0M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 13265.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/phpoption/phpoption&quot; target=_&quot;blank&quot;&gt;phpoption/phpoption&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.9M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 2644.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/psy/psysh&quot; target=_&quot;blank&quot;&gt;psy/psysh&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.7M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 9767.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/dragonmantank/cron-expression&quot; target=_&quot;blank&quot;&gt;dragonmantank/cron-expression&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.6M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 4594.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/nette/utils&quot; target=_&quot;blank&quot;&gt;nette/utils&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.5M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 2034.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/carbonphp/carbon-doctrine-types&quot; target=_&quot;blank&quot;&gt;carbonphp/carbon-doctrine-types&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.4M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 156.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/tijsverkoyen/css-to-inline-styles&quot; target=_&quot;blank&quot;&gt;tijsverkoyen/css-to-inline-styles&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.4M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 5826.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/voku/portable-ascii&quot; target=_&quot;blank&quot;&gt;voku/portable-ascii&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.4M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 555.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/composer/semver&quot; target=_&quot;blank&quot;&gt;composer/semver&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.4M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 3195.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/fakerphp/faker&quot; target=_&quot;blank&quot;&gt;fakerphp/faker&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.3M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 3696.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/graham-campbell/result-type&quot; target=_&quot;blank&quot;&gt;graham-campbell/result-type&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.3M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 504.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/paragonie/random_compat&quot; target=_&quot;blank&quot;&gt;paragonie/random_compat&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.2M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 8178.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/laravel/serializable-closure&quot; target=_&quot;blank&quot;&gt;laravel/serializable-closure&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.2M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 550.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/dflydev/dot-access-data&quot; target=_&quot;blank&quot;&gt;dflydev/dot-access-data&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.2M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 666.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/options-resolver&quot; target=_&quot;blank&quot;&gt;symfony/options-resolver&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.1M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 3222.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/phpdocumentor/type-resolver&quot; target=_&quot;blank&quot;&gt;phpdocumentor/type-resolver&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.1M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 9161.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/phpstan/phpdoc-parser&quot; target=_&quot;blank&quot;&gt;phpstan/phpdoc-parser&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.0M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 1398.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/polyfill-php81&quot; target=_&quot;blank&quot;&gt;symfony/polyfill-php81&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.0M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 877.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/polyfill-uuid&quot; target=_&quot;blank&quot;&gt;symfony/polyfill-uuid&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.0M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 657.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/doctrine/dbal&quot; target=_&quot;blank&quot;&gt;doctrine/dbal&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.0M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 9554.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/phpdocumentor/reflection-common&quot; target=_&quot;blank&quot;&gt;phpdocumentor/reflection-common&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.9M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 9045.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/league/commonmark&quot; target=_&quot;blank&quot;&gt;league/commonmark&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.9M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 2807.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/uid&quot; target=_&quot;blank&quot;&gt;symfony/uid&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.9M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 568.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/laravel/framework&quot; target=_&quot;blank&quot;&gt;laravel/framework&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.9M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 33237.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/nette/schema&quot; target=_&quot;blank&quot;&gt;nette/schema&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.8M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 939.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/composer/pcre&quot; target=_&quot;blank&quot;&gt;composer/pcre&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.8M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 597.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/mockery/mockery&quot; target=_&quot;blank&quot;&gt;mockery/mockery&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.8M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 10661.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/doctrine/event-manager&quot; target=_&quot;blank&quot;&gt;doctrine/event-manager&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.8M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 5964.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/firebase/php-jwt&quot; target=_&quot;blank&quot;&gt;firebase/php-jwt&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.7M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 9526.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/hamcrest/hamcrest-php&quot; target=_&quot;blank&quot;&gt;hamcrest/hamcrest-php&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.7M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 6970.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/aws/aws-sdk-php&quot; target=_&quot;blank&quot;&gt;aws/aws-sdk-php&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.7M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.3&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 6082.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/var-exporter&quot; target=_&quot;blank&quot;&gt;symfony/var-exporter&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.7M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 2065.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/mtdowling/jmespath.php&quot; target=_&quot;blank&quot;&gt;mtdowling/jmespath.php&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.7M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 1954.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/sebastian/resource-operations&quot; target=_&quot;blank&quot;&gt;sebastian/resource-operations&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.5M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 6281.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/league/flysystem-local&quot; target=_&quot;blank&quot;&gt;league/flysystem-local&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.5M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 180.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/laravel/tinker&quot; target=_&quot;blank&quot;&gt;laravel/tinker&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.4M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 7363.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/phpdocumentor/reflection-docblock&quot; target=_&quot;blank&quot;&gt;phpdocumentor/reflection-docblock&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.4M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 9360.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/league/config&quot; target=_&quot;blank&quot;&gt;league/config&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.4M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 519.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/doctrine/cache&quot; target=_&quot;blank&quot;&gt;doctrine/cache&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.4M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 7868.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/paragonie/constant_time_encoding&quot; target=_&quot;blank&quot;&gt;paragonie/constant_time_encoding&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.1M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 836.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/phpseclib/phpseclib&quot; target=_&quot;blank&quot;&gt;phpseclib/phpseclib&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.1M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 5434.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/aws/aws-crt-php&quot; target=_&quot;blank&quot;&gt;aws/aws-crt-php&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.1M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 367.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/filp/whoops&quot; target=_&quot;blank&quot;&gt;filp/whoops&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.0M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 13218.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/phpstan/phpstan&quot; target=_&quot;blank&quot;&gt;phpstan/phpstan&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.0M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 13240.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/guzzlehttp/uri-template&quot; target=_&quot;blank&quot;&gt;guzzlehttp/uri-template&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.9M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 154.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/nunomaduro/termwind&quot; target=_&quot;blank&quot;&gt;nunomaduro/termwind&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.9M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 2377.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/config&quot; target=_&quot;blank&quot;&gt;symfony/config&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.7M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 4248.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/laravel/prompts&quot; target=_&quot;blank&quot;&gt;laravel/prompts&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.7M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 562.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/fruitcake/php-cors&quot; target=_&quot;blank&quot;&gt;fruitcake/php-cors&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.7M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 261.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/dependency-injection&quot; target=_&quot;blank&quot;&gt;symfony/dependency-injection&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.7M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 4130.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/http-client-contracts&quot; target=_&quot;blank&quot;&gt;symfony/http-client-contracts&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.6M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 1952.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/polyfill-php73&quot; target=_&quot;blank&quot;&gt;symfony/polyfill-php73&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.6M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 2399.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/clock&quot; target=_&quot;blank&quot;&gt;symfony/clock&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.5M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 353.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/league/uri-interfaces&quot; target=_&quot;blank&quot;&gt;league/uri-interfaces&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.5M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 482.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/symfony/cache&quot; target=_&quot;blank&quot;&gt;symfony/cache&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.5M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 4125.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/league/uri&quot; target=_&quot;blank&quot;&gt;league/uri&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.5M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 1060.0&lt;/td&gt;
    &lt;/tr&gt;
    
    &lt;tr&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
        &lt;a href=&quot;https://packagist.org/packages/doctrine/annotations&quot; target=_&quot;blank&quot;&gt;doctrine/annotations&lt;/a&gt;
      &lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.4M

&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;0.25&lt;/td&gt;
      &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt; 6742.0&lt;/td&gt;
    &lt;/tr&gt;
    
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/section&gt;


&lt;!-- Section pour les packages sous-reconnus --&gt;
&lt;!-- Section pour les packages sous-reconnus --&gt;
&lt;section class=&quot;mt-48&quot;&gt;
    &lt;h2 class=&quot;text-4xl mt-8 font-bold tracking-tight text-zinc-800 sm:text-5xl dark:text-zinc-100&quot;&gt;
        L&apos;Infrastructure Invisible : Les Packages à Fort Impact mais Peu Reconnus
    &lt;/h2&gt;
    &lt;p class=&quot;text-gray-600 text-lg mt-4 mb-4&quot;&gt;
        En analysant la relation entre les étoiles GitHub et le nombre de téléchargements, j&apos;ai découvert un sous-ensemble fascinant de packages PHP : ceux qui ont des nombres de téléchargements massifs mais étonnamment peu d&apos;étoiles. Ces packages forment ce qu&apos;on pourrait appeler &quot;l&apos;infrastructure invisible&quot; de PHP — des composants d&apos;une importance cruciale qui opèrent silencieusement sous la surface d&apos;innombrables applications.
    &lt;/p&gt;

    &lt;div class=&quot;bg-white rounded-lg shadow p-6 mt-4&quot;&gt;

        &lt;p class=&quot;text-gray-600 mb-4&quot;&gt;
            Ce &quot;déficit de reconnaissance&quot; a plusieurs implications importantes :
        &lt;/p&gt;

        &lt;ul class=&quot;space-y-2&quot;&gt;
            &lt;li class=&quot;flex items-start&quot;&gt;
                &lt;span class=&quot;text-red-500 mr-2&quot;&gt;⚠&lt;/span&gt;
                &lt;span class=&quot;text-red-700 mr-2&quot;&gt;Les packages à fort impact mais à faible visibilité peuvent avoir du mal à attirer des contributeurs&lt;/span&gt;
            &lt;/li&gt;
            &lt;li class=&quot;flex items-start&quot;&gt;
                &lt;span class=&quot;text-red-500 mr-2&quot;&gt;⚠&lt;/span&gt;
                &lt;span class=&quot;text-red-700 mr-2&quot;&gt;Des dépendances critiques peuvent être maintenues par de petites équipes malgré leur importance démesurée dans l&apos;écosystème&lt;/span&gt;
            &lt;/li&gt;
            &lt;li class=&quot;flex items-start&quot;&gt;
                &lt;span class=&quot;text-green-500 mr-2&quot;&gt;✓&lt;/span&gt;
                &lt;span class=&quot;text-green-700 mr-2&quot;&gt;Cependant, beaucoup représentent des opportunités pour les développeurs cherchant à apporter des contributions significatives à l&apos;écosystème PHP&lt;/span&gt;
            &lt;/li&gt;
        &lt;/ul&gt;

        &lt;div class=&quot;h-64 mt-4 mb-4&quot;&gt;
            &lt;canvas id=&quot;underrated-packages-chart&quot;&gt;&lt;/canvas&gt;
        &lt;/div&gt;

&lt;div class=&quot;overflow-x-auto mt-6&quot; style=&quot;height: 500px; overflow: scroll;&quot;&gt;
  &lt;table class=&quot;min-w-full divide-y divide-gray-200&quot;&gt;
    &lt;thead class=&quot;bg-gray-50&quot;&gt;
      &lt;tr&gt;
        &lt;th class=&quot;px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider&quot;&gt;Package&lt;/th&gt;
        &lt;th class=&quot;px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider&quot;&gt;Monthly download&lt;/th&gt;
        &lt;th class=&quot;px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider&quot;&gt;Stars&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody class=&quot;bg-white divide-y divide-gray-200&quot;&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/staabm/side-effects-detector&quot; target=_&quot;blank&quot;&gt;staabm/side-effects-detector&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

3.6M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;76.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/spatie/error-solutions&quot; target=_&quot;blank&quot;&gt;spatie/error-solutions&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

2.5M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;42.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/google/longrunning&quot; target=_&quot;blank&quot;&gt;google/longrunning&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

2.3M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;43.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/illuminate/macroable&quot; target=_&quot;blank&quot;&gt;illuminate/macroable&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.8M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;73.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/pestphp/pest-plugin&quot; target=_&quot;blank&quot;&gt;pestphp/pest-plugin&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.6M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;37.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/php-http/guzzle7-adapter&quot; target=_&quot;blank&quot;&gt;php-http/guzzle7-adapter&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.6M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;79.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/ta-tikoma/phpunit-architecture-test&quot; target=_&quot;blank&quot;&gt;ta-tikoma/phpunit-architecture-test&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.5M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;91.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/pestphp/pest-plugin-arch&quot; target=_&quot;blank&quot;&gt;pestphp/pest-plugin-arch&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.4M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;33.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/symfony/polyfill-php82&quot; target=_&quot;blank&quot;&gt;symfony/polyfill-php82&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.4M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;55.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/illuminate/conditionable&quot; target=_&quot;blank&quot;&gt;illuminate/conditionable&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.4M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;81.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/kelunik/certificate&quot; target=_&quot;blank&quot;&gt;kelunik/certificate&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.4M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;92.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/amphp/pipeline&quot; target=_&quot;blank&quot;&gt;amphp/pipeline&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.4M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;54.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/open-telemetry/api&quot; target=_&quot;blank&quot;&gt;open-telemetry/api&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.3M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;14.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/pear/pear_exception&quot; target=_&quot;blank&quot;&gt;pear/pear_exception&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.3M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;97.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/open-telemetry/context&quot; target=_&quot;blank&quot;&gt;open-telemetry/context&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.2M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;11.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/pear/pear-core-minimal&quot; target=_&quot;blank&quot;&gt;pear/pear-core-minimal&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.2M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;77.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/pear/console_getopt&quot; target=_&quot;blank&quot;&gt;pear/console_getopt&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.1M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;84.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/phpcsstandards/phpcsutils&quot; target=_&quot;blank&quot;&gt;phpcsstandards/phpcsutils&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.1M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;55.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/mpdf/psr-log-aware-trait&quot; target=_&quot;blank&quot;&gt;mpdf/psr-log-aware-trait&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.0M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;46.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/codeception/module-asserts&quot; target=_&quot;blank&quot;&gt;codeception/module-asserts&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.0M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;80.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/pear/archive_tar&quot; target=_&quot;blank&quot;&gt;pear/archive_tar&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.0M

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;74.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/mpdf/psr-http-message-shim&quot; target=_&quot;blank&quot;&gt;mpdf/psr-http-message-shim&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

998.5K

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;33.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/consolidation/self-update&quot; target=_&quot;blank&quot;&gt;consolidation/self-update&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

960.2K

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;98.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/async-aws/core&quot; target=_&quot;blank&quot;&gt;async-aws/core&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

957.8K

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;85.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/consolidation/site-alias&quot; target=_&quot;blank&quot;&gt;consolidation/site-alias&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

917.2K

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;59.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/symfony/polyfill-php84&quot; target=_&quot;blank&quot;&gt;symfony/polyfill-php84&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

892.0K

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;20.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/consolidation/filter-via-dot-access-data&quot; target=_&quot;blank&quot;&gt;consolidation/filter-via-dot-access-data&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

880.8K

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;45.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/codeception/lib-innerbrowser&quot; target=_&quot;blank&quot;&gt;codeception/lib-innerbrowser&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

874.9K

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;83.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/consolidation/site-process&quot; target=_&quot;blank&quot;&gt;consolidation/site-process&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

866.7K

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;50.0&lt;/td&gt;
      &lt;/tr&gt;
      
      &lt;tr&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
          &lt;a href=&quot;https://packagist.org/packages/drupal/core-composer-scaffold&quot; target=_&quot;blank&quot;&gt;drupal/core-composer-scaffold&lt;/a&gt;
        &lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

858.8K

&lt;/td&gt;
        &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;&lt;i class=&quot;fas fa-star text-yellow-400 mr-2&quot;&gt;&lt;/i&gt;49.0&lt;/td&gt;
      &lt;/tr&gt;
      
    &lt;/tbody&gt;
  &lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;

&lt;!-- Graphique à bulles de popularité --&gt;
&lt;section class=&quot;bg-white rounded-lg shadow p-6 mt-4&quot;&gt;
    &lt;h3 class=&quot;text-xl font-bold mb-4 text-gray-700&quot;&gt;Packages populaires&lt;/h3&gt;
    &lt;p&gt;
        En contraste, ces packages sont à la fois très populaires et largement utilisés. Ils constituent la pierre angulaire de PHP.
    &lt;/p&gt;
    &lt;div class=&quot;h-80 mt-4&quot;&gt;
        &lt;canvas id=&quot;popularity-bubble-chart&quot;&gt;&lt;/canvas&gt;
    &lt;/div&gt;
    &lt;p class=&quot;text-sm text-right text-xs text-gray-500 mt-4&quot;&gt;taille des bulles proportionnelle aux forks GitHub&lt;/p&gt;
&lt;/section&gt;

&lt;!-- Statistiques additionnelles --&gt;
&lt;section class=&quot;mb-12 mt-48&quot;&gt;
    &lt;h2 class=&quot;text-4xl mt-8 font-bold tracking-tight text-zinc-800 sm:text-5xl dark:text-zinc-100&quot;&gt;
        État de santé de l&apos;écosystème Packagist
    &lt;/h2&gt;

    &lt;p class=&quot;text-gray-600 text-lg mt-4 mb-4&quot;&gt;
        L&apos;analyse des packages PHP révèle des préoccupations importantes concernant la santé de l&apos;écosystème Packagist :
        &lt;span class=&quot;font-bold text-indigo-600&quot;&gt;70% montrent une activité minimale&lt;/span&gt; avec peu de téléchargements et d&apos;interactions GitHub, tandis que
        &lt;span class=&quot;font-bold text-indigo-600&quot;&gt;23% semblent abandonnés&lt;/span&gt; sans mises à jour récentes. Avec une moyenne de seulement 60 étoiles par package (fortement influencée par les cas exceptionnels populaires), ces métriques mettent en évidence la distribution typique en &quot;longue traîne&quot; des écosystèmes open source. Ces données soulignent l&apos;importance d&apos;évaluer soigneusement la santé des dépendances à travers des métriques comme le facteur bus, la fréquence des mises à jour et l&apos;engagement de la communauté lors de l&apos;intégration de packages dans des projets de production.
    &lt;/p&gt;

    &lt;div class=&quot;grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6&quot;&gt;
        &lt;!-- Packages vides --&gt;
        &lt;div class=&quot;bg-white rounded-lg shadow p-6&quot;&gt;
            &lt;div class=&quot;flex items-center justify-between mb-4&quot;&gt;
                &lt;h3 class=&quot;text-lg font-medium text-gray-700&quot;&gt;Packages vides&lt;/h3&gt;
                &lt;span class=&quot;text-red-600 bg-red-100 p-2 rounded-full&quot;&gt;
                    &lt;i class=&quot;far fa-file&quot;&gt;&lt;/i&gt;
                &lt;/span&gt;
            &lt;/div&gt;
            &lt;p class=&quot;text-3xl font-bold mb-2&quot;&gt;94,9K&lt;/p&gt;
            &lt;p class=&quot;text-sm text-gray-500&quot;&gt;Packages abandonnés&lt;/p&gt;
        &lt;/div&gt;

        &lt;!-- Packages sans étoiles --&gt;
        &lt;div class=&quot;bg-white rounded-lg shadow p-6&quot;&gt;
            &lt;div class=&quot;flex items-center justify-between mb-4&quot;&gt;
                &lt;h3 class=&quot;text-lg font-medium text-gray-700&quot;&gt;Packages fantômes&lt;/h3&gt;
                &lt;span class=&quot;text-yellow-600 bg-yellow-100 p-2 rounded-full&quot;&gt;
                    &lt;i class=&quot;far fa-star&quot;&gt;&lt;/i&gt;
                &lt;/span&gt;
            &lt;/div&gt;
            &lt;p class=&quot;text-3xl font-bold mb-2&quot;&gt;288,5K&lt;/p&gt;
            &lt;p class=&quot;text-sm text-gray-500&quot;&gt;Packages avec peu de téléchargements, peu d&apos;étoiles ou de forks GitHub&lt;/p&gt;
        &lt;/div&gt;

        &lt;!-- Popularité moyenne --&gt;
        &lt;div class=&quot;bg-white rounded-lg shadow p-6&quot;&gt;
            &lt;div class=&quot;flex items-center justify-between mb-4&quot;&gt;
                &lt;h3 class=&quot;text-lg font-medium text-gray-700&quot;&gt;Popularité moyenne&lt;/h3&gt;
                &lt;span class=&quot;text-purple-600 bg-purple-100 p-2 rounded-full&quot;&gt;
                    &lt;i class=&quot;fas fa-star&quot;&gt;&lt;/i&gt;
                &lt;/span&gt;
            &lt;/div&gt;
            &lt;p class=&quot;text-3xl font-bold mb-2&quot;&gt;60&lt;/p&gt;
            &lt;div class=&quot;text-sm text-gray-500&quot;&gt;
                Moyenne d&apos;étoiles par package
                &lt;div class=&quot;text-xs&quot;&gt;Excluant les packages sans étoiles&lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;


    &lt;div class=&quot;grid grid-cols-1 md:grid-cols-2 gap-6 mt-4&quot;&gt;
        &lt;div class=&quot;bg-white rounded-lg shadow p-6&quot;&gt;
            &lt;h3 class=&quot;text-xl font-bold mb-4 text-gray-700&quot;&gt;Packages les plus requis pour le développement&lt;/h3&gt;
            &lt;div style=&quot;max-height: 400px; overflow-y: auto;&quot;&gt;
                &lt;table class=&quot;w-full&quot;&gt;
                    &lt;thead&gt;
                    &lt;tr&gt;
                        &lt;th class=&quot;text-left&quot;&gt;Package&lt;/th&gt;
                        &lt;th class=&quot;text-right&quot;&gt;Nombre de packages l&apos;utilisant&lt;/th&gt;
                    &lt;/tr&gt;
                    &lt;/thead&gt;
                    &lt;tbody&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/phpunit/phpunit&quot; target=_&quot;blank&quot;&gt;phpunit/phpunit&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

201.5K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/orchestra/testbench&quot; target=_&quot;blank&quot;&gt;orchestra/testbench&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

33.9K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/squizlabs/php_codesniffer&quot; target=_&quot;blank&quot;&gt;squizlabs/php_codesniffer&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

33.8K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/phpstan/phpstan&quot; target=_&quot;blank&quot;&gt;phpstan/phpstan&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

32.3K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/mockery/mockery&quot; target=_&quot;blank&quot;&gt;mockery/mockery&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

31.8K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/friendsofphp/php-cs-fixer&quot; target=_&quot;blank&quot;&gt;friendsofphp/php-cs-fixer&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

24.4K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/pestphp/pest&quot; target=_&quot;blank&quot;&gt;pestphp/pest&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.5K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/vimeo/psalm&quot; target=_&quot;blank&quot;&gt;vimeo/psalm&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

10.5K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/phpstan/phpstan-phpunit&quot; target=_&quot;blank&quot;&gt;phpstan/phpstan-phpunit&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

9.7K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/laravel/pint&quot; target=_&quot;blank&quot;&gt;laravel/pint&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

9.6K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/phpstan/extension-installer&quot; target=_&quot;blank&quot;&gt;phpstan/extension-installer&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

9.0K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/symfony/var-dumper&quot; target=_&quot;blank&quot;&gt;symfony/var-dumper&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.9K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/nunomaduro/collision&quot; target=_&quot;blank&quot;&gt;nunomaduro/collision&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.4K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/roave/security-advisories&quot; target=_&quot;blank&quot;&gt;roave/security-advisories&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.2K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/phpmd/phpmd&quot; target=_&quot;blank&quot;&gt;phpmd/phpmd&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.7K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/symfony/phpunit-bridge&quot; target=_&quot;blank&quot;&gt;symfony/phpunit-bridge&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.5K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/phpstan/phpstan-deprecation-rules&quot; target=_&quot;blank&quot;&gt;phpstan/phpstan-deprecation-rules&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.4K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/pestphp/pest-plugin-laravel&quot; target=_&quot;blank&quot;&gt;pestphp/pest-plugin-laravel&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.2K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/satooshi/php-coveralls&quot; target=_&quot;blank&quot;&gt;satooshi/php-coveralls&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.0K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/php-coveralls/php-coveralls&quot; target=_&quot;blank&quot;&gt;php-coveralls/php-coveralls&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

5.8K

&lt;/td&gt;
              &lt;/tr&gt;
              
            &lt;/tbody&gt;
          &lt;/table&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class=&quot;bg-white rounded-lg shadow p-6&quot;&gt;
        &lt;h3 class=&quot;text-xl font-bold mb-4 text-gray-700&quot;&gt;Top required packages for production&lt;/h3&gt;
        &lt;div style=&quot;max-height: 400px; overflow-y: auto;&quot;&gt;
          &lt;table class=&quot;w-full&quot;&gt;
            &lt;thead&gt;
              &lt;tr&gt;
                &lt;th class=&quot;text-left&quot;&gt;Package&lt;/th&gt;
                &lt;th class=&quot;text-right&quot;&gt;Number of packages using it&lt;/th&gt;
              &lt;/tr&gt;
            &lt;/thead&gt;
            &lt;tbody&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/illuminate/support&quot; target=_&quot;blank&quot;&gt;illuminate/support&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

46.4K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/guzzlehttp/guzzle&quot; target=_&quot;blank&quot;&gt;guzzlehttp/guzzle&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

39.7K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/laravel/framework&quot; target=_&quot;blank&quot;&gt;laravel/framework&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

18.9K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/illuminate/contracts&quot; target=_&quot;blank&quot;&gt;illuminate/contracts&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

16.2K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/yiisoft/yii2&quot; target=_&quot;blank&quot;&gt;yiisoft/yii2&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

16.2K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/symfony/console&quot; target=_&quot;blank&quot;&gt;symfony/console&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

13.0K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/psr/log&quot; target=_&quot;blank&quot;&gt;psr/log&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.7K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/symfony/framework-bundle&quot; target=_&quot;blank&quot;&gt;symfony/framework-bundle&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.2K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/illuminate/database&quot; target=_&quot;blank&quot;&gt;illuminate/database&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

10.2K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/composer/installers&quot; target=_&quot;blank&quot;&gt;composer/installers&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

9.0K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/symfony/yaml&quot; target=_&quot;blank&quot;&gt;symfony/yaml&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.8K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/psr/http-message&quot; target=_&quot;blank&quot;&gt;psr/http-message&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.6K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/monolog/monolog&quot; target=_&quot;blank&quot;&gt;monolog/monolog&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.0K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/symfony/dependency-injection&quot; target=_&quot;blank&quot;&gt;symfony/dependency-injection&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.7K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/symfony/http-kernel&quot; target=_&quot;blank&quot;&gt;symfony/http-kernel&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.6K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/symfony/http-foundation&quot; target=_&quot;blank&quot;&gt;symfony/http-foundation&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.1K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/symfony/config&quot; target=_&quot;blank&quot;&gt;symfony/config&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

7.1K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/twig/twig&quot; target=_&quot;blank&quot;&gt;twig/twig&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.6K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/spatie/laravel-package-tools&quot; target=_&quot;blank&quot;&gt;spatie/laravel-package-tools&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.5K

&lt;/td&gt;
              &lt;/tr&gt;
              
              &lt;tr&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-left&quot;&gt;
                  &lt;a href=&quot;https://packagist.org/packages/doctrine/orm&quot; target=_&quot;blank&quot;&gt;doctrine/orm&lt;/a&gt;
                &lt;/td&gt;
                &lt;td class=&quot;px-6 py-4 whitespace-nowrap text-right&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

6.4K

&lt;/td&gt;
              &lt;/tr&gt;
              
            &lt;/tbody&gt;
          &lt;/table&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/section&gt;



&lt;section class=&quot;mt-48&quot;&gt;
    &lt;h2 class=&quot;text-4xl mt-8 font-bold tracking-tight text-zinc-800 sm:text-5xl dark:text-zinc-100&quot;&gt;
        Et qu&apos;en est-il de la qualité ?
    &lt;/h2&gt;
    &lt;p class=&quot;text-gray-600 text-lg mt-4 mb-4&quot;&gt;
        Malgré la réputation parfois critiquée de PHP, les données racontent une histoire différente.
        &lt;span class=&quot;font-bold text-indigo-600&quot;&gt;Les packages PHP montrent un code étonnamment propre, facile à maintenir et à comprendre.&lt;/span&gt;
        Les fonctions sont remarquablement courtes, les différentes parties du code s&apos;harmonisent bien et les packages gardent une taille raisonnable. Il s&apos;avère que les développeurs PHP n&apos;écrivent pas du code spaghetti après tout - ils élaborent des plats bien structurés qui feraient hocher la tête d&apos;approbation même aux chefs de code les plus exigeants.
    &lt;/p&gt;
&lt;/section&gt;

&lt;!-- Rangée de graphiques 1 --&gt;
&lt;section class=&quot;grid grid-cols-1 lg:grid-cols-2 gap-8&quot;&gt;
    &lt;!-- Distribution de la complexité --&gt;
    &lt;div class=&quot;bg-white rounded-lg shadow p-6&quot;&gt;
        &lt;h3 class=&quot;text-xl font-bold mb-4 text-gray-700&quot;&gt;Distribution de la complexité&lt;/h3&gt;
        &lt;div class=&quot;h-64&quot;&gt;
            &lt;canvas id=&quot;complexity-chart&quot;&gt;&lt;/canvas&gt;
        &lt;/div&gt;
        &lt;div class=&quot;grid grid-cols-2 gap-4 mt-4&quot;&gt;
            &lt;div class=&quot;rounded p-3 bg-blue-50&quot;&gt;
                &lt;p class=&quot;text-sm text-gray-500&quot;&gt;Complexité cyclomatique moyenne&lt;/p&gt;
                &lt;p class=&quot;font-bold text-xl&quot;&gt;8,8&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;rounded p-3 bg-blue-50&quot;&gt;
                &lt;p class=&quot;text-sm text-gray-500&quot;&gt;Complexité cyclomatique médiane&lt;/p&gt;
                &lt;p class=&quot;font-bold text-xl&quot;&gt;4,0&lt;/p&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;

    &lt;!-- Métriques de code --&gt;
    &lt;div class=&quot;bg-white rounded-lg shadow p-6&quot;&gt;
        &lt;h3 class=&quot;text-xl font-bold mb-4 text-gray-700&quot;&gt;Qualité du code&lt;/h3&gt;
        &lt;div class=&quot;h-64&quot;&gt;
            &lt;canvas id=&quot;code-quality-chart&quot;&gt;&lt;/canvas&gt;
        &lt;/div&gt;
        &lt;div class=&quot;grid grid-cols-2 gap-4 mt-4&quot;&gt;
            &lt;div class=&quot;rounded p-3 bg-blue-50&quot;&gt;
                &lt;p class=&quot;text-sm text-gray-500&quot;&gt;Indice de maintenabilité&lt;/p&gt;
                &lt;p class=&quot;font-bold text-xl&quot;&gt;108&lt;/p&gt;
            &lt;/div&gt;
            &lt;div class=&quot;rounded p-3 bg-blue-50&quot;&gt;
                &lt;p class=&quot;text-sm text-gray-500&quot;&gt;Lignes par méthode&lt;/p&gt;
                &lt;p class=&quot;font-bold text-xl&quot;&gt;3,0&lt;/p&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/section&gt;

&lt;div class=&quot;mt-4 bg-white rounded-lg shadow p-6&quot;&gt;
    &lt;h3 class=&quot;text-xl font-bold text-gray-700&quot;&gt;Packages avec le plus de lignes de code&lt;/h3&gt;
    &lt;p class=&quot;text-gray-600 text-lg mt-4 mb-4&quot;&gt;
        Si vous êtes curieux des packages ayant le plus de lignes de code, vous trouverez les 100 premiers packages ci-dessous.
        Notez que les plus gros packages concernent les données géographiques et intègrent beaucoup de données dans leur code.
    &lt;/p&gt;
    &lt;div class=&quot;overflow-x-auto&quot;&gt;
        &lt;table class=&quot;min-w-full divide-y divide-gray-200&quot;&gt;
            &lt;thead class=&quot;bg-gray-50&quot;&gt;
            &lt;tr&gt;
                &lt;th class=&quot;px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider&quot;&gt;Package&lt;/th&gt;
                &lt;th class=&quot;px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider&quot;&gt;Lignes de code&lt;/th&gt;
                &lt;th class=&quot;px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider&quot;&gt;Fichiers&lt;/th&gt;
            &lt;/tr&gt;
            &lt;/thead&gt;
        &lt;tbody class=&quot;bg-white divide-y divide-gray-200&quot;&gt;
          
          &lt;tr&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
              &lt;a href=&quot;https://packagist.org/packages/xnrcms/alipaysdk&quot; target=_&quot;blank&quot;&gt;xnrcms/alipaysdk&lt;/a&gt;
            &lt;/td&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

2.8M

&lt;/td&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

11.4K

&lt;/td&gt;
          &lt;/tr&gt;
          
          &lt;tr&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
              &lt;a href=&quot;https://packagist.org/packages/zerossb/laravel-world&quot; target=_&quot;blank&quot;&gt;zerossb/laravel-world&lt;/a&gt;
            &lt;/td&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

2.2M

&lt;/td&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

48.0

&lt;/td&gt;
          &lt;/tr&gt;
          
          &lt;tr&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
              &lt;a href=&quot;https://packagist.org/packages/tencentcloud/tencentcloud-sdk-php-intl-en&quot; target=_&quot;blank&quot;&gt;tencentcloud/tencentcloud-sdk-php-intl-en&lt;/a&gt;
            &lt;/td&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.9M

&lt;/td&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

19.2K

&lt;/td&gt;
          &lt;/tr&gt;
          
          &lt;tr&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
              &lt;a href=&quot;https://packagist.org/packages/vccmas/tencentcloud-sdk-php-intl-en&quot; target=_&quot;blank&quot;&gt;vccmas/tencentcloud-sdk-php-intl-en&lt;/a&gt;
            &lt;/td&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.6M

&lt;/td&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

15.5K

&lt;/td&gt;
          &lt;/tr&gt;
          
          &lt;tr&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
              &lt;a href=&quot;https://packagist.org/packages/zohocrm/php-sdk-6.0&quot; target=_&quot;blank&quot;&gt;zohocrm/php-sdk-6.0&lt;/a&gt;
            &lt;/td&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.5M

&lt;/td&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

15.7K

&lt;/td&gt;
          &lt;/tr&gt;
          
          &lt;tr&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
              &lt;a href=&quot;https://packagist.org/packages/udemy/googleads-php-lib&quot; target=_&quot;blank&quot;&gt;udemy/googleads-php-lib&lt;/a&gt;
            &lt;/td&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.5M

&lt;/td&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.1K

&lt;/td&gt;
          &lt;/tr&gt;
          
          &lt;tr&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
              &lt;a href=&quot;https://packagist.org/packages/volcengine/volcengine-php-sdk&quot; target=_&quot;blank&quot;&gt;volcengine/volcengine-php-sdk&lt;/a&gt;
            &lt;/td&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.3M

&lt;/td&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

3.1K

&lt;/td&gt;
          &lt;/tr&gt;
          
          &lt;tr&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
              &lt;a href=&quot;https://packagist.org/packages/vavajke/bxcore&quot; target=_&quot;blank&quot;&gt;vavajke/bxcore&lt;/a&gt;
            &lt;/td&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.3M

&lt;/td&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

4.4K

&lt;/td&gt;
          &lt;/tr&gt;
          
          &lt;tr&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
              &lt;a href=&quot;https://packagist.org/packages/sos-solution/other-framework&quot; target=_&quot;blank&quot;&gt;sos-solution/other-framework&lt;/a&gt;
            &lt;/td&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.2M

&lt;/td&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

8.7K

&lt;/td&gt;
          &lt;/tr&gt;
          
          &lt;tr&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
              &lt;a href=&quot;https://packagist.org/packages/tombeachell/forza-magento&quot; target=_&quot;blank&quot;&gt;tombeachell/forza-magento&lt;/a&gt;
            &lt;/td&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

1.2M

&lt;/td&gt;
            &lt;td class=&quot;px-6 py-4 whitespace-nowrap&quot;&gt;
&lt;!-- convert xxx to xxxK, xxxM --&gt;

4.2K

&lt;/td&gt;
          &lt;/tr&gt;
          
        &lt;/tbody&gt;
      &lt;/table&gt;
    &lt;/div&gt;
  &lt;/div&gt;


&lt;section class=&quot;mt-48&quot;&gt;
    &lt;h2 class=&quot;text-4xl mt-8 font-bold tracking-tight text-zinc-800 sm:text-5xl dark:text-zinc-100&quot;&gt;
        Que cache-t-on d&apos;autre dans les dépôts PHP ?
    &lt;/h2&gt;

    &lt;div class=&quot;text-gray-600 text-lg mt-4 mb-4&quot;&gt;
        Analyser PHP sur Packagist est une chose, mais qu&apos;en est-il de tout ce qui entoure PHP ? En réalité, les projets PHP modernes s&apos;appuient sur une pile technologique diverse.
    &lt;/div&gt;

    &lt;div class=&quot;mt-4 grid grid-cols-1 md:grid-cols-2 gap-8&quot;&gt;
        &lt;div&gt;
            &lt;div&gt;🔍 Voici ce que j&apos;ai trouvé dans les dépôts :&lt;/div&gt;
            &lt;ul class=&quot;list-disc list-inside text-gray-700 mb-4 ml-8&quot;&gt;
                &lt;li&gt;&lt;strong&gt;PHP domine&lt;/strong&gt; (plus de 707M lignes de code), mais il est loin d&apos;être seul.&lt;/li&gt;
                &lt;li&gt;&lt;strong&gt;JavaScript&lt;/strong&gt; est le compagnon le plus courant (plus de 216M lignes), prouvant que l&apos;intégration front-end et back-end est essentielle.&lt;/li&gt;
                &lt;li&gt;&lt;strong&gt;JSON &amp; YAML&lt;/strong&gt; sont omniprésents (plus de 200M lignes combinées), soulignant l&apos;essor des architectures pilotées par la configuration.&lt;/li&gt;
                &lt;li&gt;&lt;strong&gt;CSS &amp; HTML&lt;/strong&gt; (plus de 167M lignes au total) démontrent la nature full-stack de nombreuses applications PHP.&lt;/li&gt;
                &lt;li&gt;&lt;strong&gt;SQL, XML et Markdown&lt;/strong&gt; reflètent l&apos;importance des données structurées et de la documentation dans les projets PHP modernes.&lt;/li&gt;
            &lt;/ul&gt;

            &lt;div class=&quot;mb-2&quot;&gt;💡 Découvertes surprenantes :&lt;/div&gt;
            &lt;ul class=&quot;list-disc list-inside text-gray-700 mb-4 ml-8&quot;&gt;
                &lt;li&gt;&lt;strong&gt;TypeScript est en croissance&lt;/strong&gt; (2,7M lignes) – les projets PHP deviennent-ils plus structurés côté front-end ?&lt;/li&gt;
                &lt;li&gt;&lt;strong&gt;C, Go et Rust&lt;/strong&gt; apparaissent en petites quantités – un signe d&apos;extensions critiques en performance ?&lt;/li&gt;
                &lt;li&gt;&lt;strong&gt;Même Lua, Perl et Scheme&lt;/strong&gt; font leur apparition – des intégrations héritées ou des outils de script au sein des projets PHP ?&lt;/li&gt;
            &lt;/ul&gt;

            &lt;p class=&quot;text-gray-700 mb-4&quot;&gt;👉 &lt;strong class=&quot;font-bold text-purple-600&quot;&gt;Le PHP moderne est plus que du PHP.&lt;/strong&gt; C&apos;est un écosystème qui se mélange avec diverses technologies, rendant les compétences multi-langages de plus en plus précieuses pour les développeurs PHP.&lt;/p&gt;
        &lt;/div&gt;
        &lt;div class=&quot;bg-white rounded-lg shadow-md p-6&quot;&gt;
            &lt;div class=&quot;text-center text-gray-700 mb-4&quot;&gt;
                Répartition des lignes de code par langage (top 10)
            &lt;/div&gt;
            &lt;canvas id=&quot;chart-languages&quot;&gt;&lt;/canvas&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/section&gt;



&lt;section class=&quot;mt-48&quot;&gt;
    &lt;h2 class=&quot;text-4xl mt-8 font-bold tracking-tight text-zinc-800 sm:text-5xl dark:text-zinc-100&quot;&gt;
        Et après ?
    &lt;/h2&gt;

    &lt;div class=&quot;text-gray-600 text-lg mt-4 mb-4&quot;&gt;
        &lt;p class=&quot;text-gray-600 text-lg mt-4 mb-4&quot;&gt;
            Maintenant que la majorité du processus est automatisée, il me reste encore quelques nettoyages de code à effectuer, mais je pense qu&apos;il serait utile de réaliser cette analyse au moins deux fois par an pour maintenir une vue actualisée de l&apos;écosystème PHP. Jusqu&apos;à présent, je me suis concentré exclusivement sur Packagist, mais ce n&apos;est pas le seul gestionnaire de packages - l&apos;ajout de nouvelles sources de code fournirait une image plus complète.
        &lt;/p&gt;
        &lt;p class=&quot;text-gray-600 text-lg mt-4 mb-4&quot;&gt;
            J&apos;envisage également d&apos;implémenter des métriques de code plus avancées pour approfondir l&apos;analyse. Les métriques actuelles nous donnent une bonne base, mais il y a toujours plus à apprendre sur la qualité du code et la santé de l&apos;écosystème.
        &lt;/p&gt;
        &lt;p class=&quot;text-gray-600 text-lg mt-4 mb-4&quot;&gt;
            Si vous avez des idées de métriques ou de fonctionnalités supplémentaires qui pourraient améliorer ce tableau de bord, n&apos;hésitez pas à me contacter - vos suggestions pourraient aider à façonner l&apos;orientation future de ce projet !
        &lt;/p&gt;
    &lt;/div&gt;
&lt;/section&gt;


  

&lt;script&gt;
// Function to format large numbers
function formatNumber(num) {
  if (num &gt; 1000000) {
    return (num / 1000000).toFixed(1) + &apos;M&apos;;
  } else if (num &gt; 1000) {
    return (num / 1000).toFixed(1) + &apos;k&apos;;
  }
  return num.toLocaleString();
}

// Load data
async function loadData() {

  // sync
  const fileAnalysis = {&quot;avg_cyclomatic_per_file&quot;:8.778060316311837,&quot;avg_files_per_package&quot;:32.58677030922509,&quot;cyclomatic_distribution&quot;:{&quot;0-10&quot;:8485264,&quot;10-20&quot;:1487838,&quot;100+&quot;:65804,&quot;20-30&quot;:526004,&quot;30-40&quot;:239622,&quot;40-50&quot;:122259,&quot;50-60&quot;:70031,&quot;60-70&quot;:43466,&quot;70-80&quot;:27005,&quot;80-90&quot;:20079,&quot;90-100&quot;:13572},&quot;loc_distribution&quot;:{&quot;0-25&quot;:10335731,&quot;100-150&quot;:28654,&quot;1000+&quot;:824,&quot;150-300&quot;:14845,&quot;25-50&quot;:556632,&quot;300-500&quot;:3117,&quot;50-75&quot;:118018,&quot;500-750&quot;:1125,&quot;75-100&quot;:41645,&quot;750-1000&quot;:353},&quot;median_cyclomatic&quot;:3,&quot;median_loc&quot;:4,&quot;total_classes&quot;:7393743,&quot;total_cyclomatic&quot;:97444756,&quot;total_interfaces&quot;:3707201,&quot;total_loc&quot;:107877627,&quot;total_methods&quot;:0,&quot;under_median_cyclomatic&quot;:4715701,&quot;under_median_loc&quot;:4084482};
  const statsOverview = {&quot;avg_bus_factor&quot;:0.14041291161262826,&quot;avg_commits&quot;:3.033670453019579,&quot;total_packages&quot;:411610};
  const dependencies = [{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:12679593,&quot;downloads_total&quot;:738780466,&quot;files&quot;:0,&quot;forks&quot;:2206,&quot;name&quot;:&quot;phpunit/phpunit&quot;,&quot;stars&quot;:19791,&quot;usage&quot;:139853},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:1543959,&quot;downloads_total&quot;:86284806,&quot;files&quot;:0,&quot;forks&quot;:162,&quot;name&quot;:&quot;illuminate/support&quot;,&quot;stars&quot;:569,&quot;usage&quot;:33345},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:13951038,&quot;downloads_total&quot;:800504182,&quot;files&quot;:0,&quot;forks&quot;:2398,&quot;name&quot;:&quot;guzzlehttp/guzzle&quot;,&quot;stars&quot;:23313,&quot;usage&quot;:33276},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:5588465,&quot;downloads_total&quot;:293188100,&quot;files&quot;:0,&quot;forks&quot;:68,&quot;name&quot;:&quot;squizlabs/php_codesniffer&quot;,&quot;stars&quot;:1112,&quot;usage&quot;:26759},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:559298,&quot;downloads_total&quot;:25513726,&quot;files&quot;:21,&quot;forks&quot;:137,&quot;name&quot;:&quot;orchestra/testbench&quot;,&quot;stars&quot;:2147,&quot;usage&quot;:26435},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:7824504,&quot;downloads_total&quot;:392501786,&quot;files&quot;:0,&quot;forks&quot;:457,&quot;name&quot;:&quot;mockery/mockery&quot;,&quot;stars&quot;:10661,&quot;usage&quot;:24273},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:6957511,&quot;downloads_total&quot;:245482397,&quot;files&quot;:0,&quot;forks&quot;:910,&quot;name&quot;:&quot;phpstan/phpstan&quot;,&quot;stars&quot;:13240,&quot;usage&quot;:23015},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:3995039,&quot;downloads_total&quot;:178240286,&quot;files&quot;:0,&quot;forks&quot;:1591,&quot;name&quot;:&quot;friendsofphp/php-cs-fixer&quot;,&quot;stars&quot;:13031,&quot;usage&quot;:18882},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:7860661,&quot;downloads_total&quot;:399349443,&quot;files&quot;:0,&quot;forks&quot;:11237,&quot;name&quot;:&quot;laravel/framework&quot;,&quot;stars&quot;:33237,&quot;usage&quot;:17310},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:300838,&quot;downloads_total&quot;:24674549,&quot;files&quot;:0,&quot;forks&quot;:134,&quot;name&quot;:&quot;yiisoft/yii2&quot;,&quot;stars&quot;:236,&quot;usage&quot;:13078},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:14851715,&quot;downloads_total&quot;:885244463,&quot;files&quot;:0,&quot;forks&quot;:259,&quot;name&quot;:&quot;symfony/console&quot;,&quot;stars&quot;:9785,&quot;usage&quot;:11415},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:4032056,&quot;downloads_total&quot;:182066185,&quot;files&quot;:0,&quot;forks&quot;:120,&quot;name&quot;:&quot;symfony/framework-bundle&quot;,&quot;stars&quot;:3539,&quot;usage&quot;:10312},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:11919219,&quot;downloads_total&quot;:627915019,&quot;files&quot;:10,&quot;forks&quot;:130,&quot;name&quot;:&quot;symfony/yaml&quot;,&quot;stars&quot;:3826,&quot;usage&quot;:9438},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:659077,&quot;downloads_total&quot;:43011212,&quot;files&quot;:0,&quot;forks&quot;:597,&quot;name&quot;:&quot;illuminate/database&quot;,&quot;stars&quot;:2705,&quot;usage&quot;:8836},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:1631363,&quot;downloads_total&quot;:27459868,&quot;files&quot;:0,&quot;forks&quot;:372,&quot;name&quot;:&quot;pestphp/pest&quot;,&quot;stars&quot;:9891,&quot;usage&quot;:8744},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:15188541,&quot;downloads_total&quot;:963431048,&quot;files&quot;:20,&quot;forks&quot;:182,&quot;name&quot;:&quot;psr/log&quot;,&quot;stars&quot;:10421,&quot;usage&quot;:8736},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:2126980,&quot;downloads_total&quot;:96035049,&quot;files&quot;:7,&quot;forks&quot;:82,&quot;name&quot;:&quot;illuminate/contracts&quot;,&quot;stars&quot;:682,&quot;usage&quot;:8216},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:2126980,&quot;downloads_total&quot;:96035049,&quot;files&quot;:146,&quot;forks&quot;:82,&quot;name&quot;:&quot;illuminate/contracts&quot;,&quot;stars&quot;:682,&quot;usage&quot;:8216},{&quot;bus_factor&quot;:2,&quot;downloads_monthly&quot;:12977531,&quot;downloads_total&quot;:678659761,&quot;files&quot;:68,&quot;forks&quot;:103,&quot;name&quot;:&quot;symfony/var-dumper&quot;,&quot;stars&quot;:7426,&quot;usage&quot;:7637},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:13099994,&quot;downloads_total&quot;:788872755,&quot;files&quot;:0,&quot;forks&quot;:1899,&quot;name&quot;:&quot;monolog/monolog&quot;,&quot;stars&quot;:21132,&quot;usage&quot;:7517},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:4547568,&quot;downloads_total&quot;:82720391,&quot;files&quot;:0,&quot;forks&quot;:152,&quot;name&quot;:&quot;laravel/pint&quot;,&quot;stars&quot;:2852,&quot;usage&quot;:7455},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:1896963,&quot;downloads_total&quot;:109882055,&quot;files&quot;:298,&quot;forks&quot;:412,&quot;name&quot;:&quot;composer/installers&quot;,&quot;stars&quot;:1437,&quot;usage&quot;:7264},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:1896963,&quot;downloads_total&quot;:109882055,&quot;files&quot;:33,&quot;forks&quot;:412,&quot;name&quot;:&quot;composer/installers&quot;,&quot;stars&quot;:1437,&quot;usage&quot;:7264},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:1084020,&quot;downloads_total&quot;:57812172,&quot;files&quot;:0,&quot;forks&quot;:672,&quot;name&quot;:&quot;vimeo/psalm&quot;,&quot;stars&quot;:5628,&quot;usage&quot;:7055},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:6657245,&quot;downloads_total&quot;:345448677,&quot;files&quot;:0,&quot;forks&quot;:94,&quot;name&quot;:&quot;symfony/dependency-injection&quot;,&quot;stars&quot;:4130,&quot;usage&quot;:6775},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:3900391,&quot;downloads_total&quot;:234366172,&quot;files&quot;:0,&quot;forks&quot;:2531,&quot;name&quot;:&quot;doctrine/orm&quot;,&quot;stars&quot;:10005,&quot;usage&quot;:6735},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:6432473,&quot;downloads_total&quot;:244614019,&quot;files&quot;:71,&quot;forks&quot;:164,&quot;name&quot;:&quot;nunomaduro/collision&quot;,&quot;stars&quot;:4552,&quot;usage&quot;:6700},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:2205651,&quot;downloads_total&quot;:67589115,&quot;files&quot;:24,&quot;forks&quot;:48,&quot;name&quot;:&quot;phpstan/phpstan-phpunit&quot;,&quot;stars&quot;:480,&quot;usage&quot;:6550},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:6129049,&quot;downloads_total&quot;:367534605,&quot;files&quot;:0,&quot;forks&quot;:1266,&quot;name&quot;:&quot;twig/twig&quot;,&quot;stars&quot;:8256,&quot;usage&quot;:6479},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:2355242,&quot;downloads_total&quot;:62115058,&quot;files&quot;:11,&quot;forks&quot;:27,&quot;name&quot;:&quot;phpstan/extension-installer&quot;,&quot;stars&quot;:446,&quot;usage&quot;:6404},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:1486528,&quot;downloads_total&quot;:75758927,&quot;files&quot;:0,&quot;forks&quot;:106,&quot;name&quot;:&quot;roave/security-advisories&quot;,&quot;stars&quot;:2756,&quot;usage&quot;:6377},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:6704982,&quot;downloads_total&quot;:368966055,&quot;files&quot;:89,&quot;forks&quot;:42,&quot;name&quot;:&quot;symfony/config&quot;,&quot;stars&quot;:4248,&quot;usage&quot;:6033},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:12047419,&quot;downloads_total&quot;:658708043,&quot;files&quot;:0,&quot;forks&quot;:92,&quot;name&quot;:&quot;symfony/http-kernel&quot;,&quot;stars&quot;:8126,&quot;usage&quot;:6033},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:7955368,&quot;downloads_total&quot;:478582482,&quot;files&quot;:0,&quot;forks&quot;:1353,&quot;name&quot;:&quot;doctrine/dbal&quot;,&quot;stars&quot;:9554,&quot;usage&quot;:5761},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:8979714,&quot;downloads_total&quot;:474766398,&quot;files&quot;:41,&quot;forks&quot;:638,&quot;name&quot;:&quot;vlucas/phpdotenv&quot;,&quot;stars&quot;:13265,&quot;usage&quot;:5597},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:1434302,&quot;downloads_total&quot;:89017087,&quot;files&quot;:0,&quot;forks&quot;:348,&quot;name&quot;:&quot;phpmd/phpmd&quot;,&quot;stars&quot;:2348,&quot;usage&quot;:5307},{&quot;bus_factor&quot;:2,&quot;downloads_monthly&quot;:14087178,&quot;downloads_total&quot;:785182390,&quot;files&quot;:22,&quot;forks&quot;:104,&quot;name&quot;:&quot;symfony/process&quot;,&quot;stars&quot;:7453,&quot;usage&quot;:5283},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:14537371,&quot;downloads_total&quot;:823272846,&quot;files&quot;:10,&quot;forks&quot;:186,&quot;name&quot;:&quot;psr/http-message&quot;,&quot;stars&quot;:6998,&quot;usage&quot;:5164},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:12655313,&quot;downloads_total&quot;:704319494,&quot;files&quot;:0,&quot;forks&quot;:298,&quot;name&quot;:&quot;symfony/http-foundation&quot;,&quot;stars&quot;:8652,&quot;usage&quot;:5080},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:2008908,&quot;downloads_total&quot;:49058927,&quot;files&quot;:22,&quot;forks&quot;:19,&quot;name&quot;:&quot;phpstan/phpstan-deprecation-rules&quot;,&quot;stars&quot;:389,&quot;usage&quot;:4862},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:10090649,&quot;downloads_total&quot;:524194808,&quot;files&quot;:0,&quot;forks&quot;:6,&quot;name&quot;:&quot;nesbot/carbon&quot;,&quot;stars&quot;:48,&quot;usage&quot;:4857},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:3369301,&quot;downloads_total&quot;:71591818,&quot;files&quot;:19,&quot;forks&quot;:147,&quot;name&quot;:&quot;spatie/laravel-package-tools&quot;,&quot;stars&quot;:828,&quot;usage&quot;:4850},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:1203781,&quot;downloads_total&quot;:18818296,&quot;files&quot;:19,&quot;forks&quot;:42,&quot;name&quot;:&quot;pestphp/pest-plugin-laravel&quot;,&quot;stars&quot;:191,&quot;usage&quot;:4724},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:11080,&quot;downloads_total&quot;:5004008,&quot;files&quot;:1,&quot;forks&quot;:101,&quot;name&quot;:&quot;satooshi/php-coveralls&quot;,&quot;stars&quot;:455,&quot;usage&quot;:4558},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:555231,&quot;downloads_total&quot;:36257490,&quot;files&quot;:2,&quot;forks&quot;:26,&quot;name&quot;:&quot;illuminate/console&quot;,&quot;stars&quot;:124,&quot;usage&quot;:4445},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:555231,&quot;downloads_total&quot;:36257490,&quot;files&quot;:104,&quot;forks&quot;:26,&quot;name&quot;:&quot;illuminate/console&quot;,&quot;stars&quot;:124,&quot;usage&quot;:4445},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:2876824,&quot;downloads_total&quot;:163123088,&quot;files&quot;:24,&quot;forks&quot;:47,&quot;name&quot;:&quot;symfony/phpunit-bridge&quot;,&quot;stars&quot;:2460,&quot;usage&quot;:4237},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:950380,&quot;downloads_total&quot;:220790619,&quot;files&quot;:0,&quot;forks&quot;:3333,&quot;name&quot;:&quot;fzaninotto/faker&quot;,&quot;stars&quot;:26398,&quot;usage&quot;:4057},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:433617,&quot;downloads_total&quot;:29675737,&quot;files&quot;:0,&quot;forks&quot;:37,&quot;name&quot;:&quot;illuminate/http&quot;,&quot;stars&quot;:114,&quot;usage&quot;:3962},{&quot;bus_factor&quot;:3,&quot;downloads_monthly&quot;:433617,&quot;downloads_total&quot;:29675737,&quot;files&quot;:20,&quot;forks&quot;:37,&quot;name&quot;:&quot;illuminate/http&quot;,&quot;stars&quot;:114,&quot;usage&quot;:3962},{&quot;bus_factor&quot;:2,&quot;downloads_monthly&quot;:13973488,&quot;downloads_total&quot;:807162519,&quot;files&quot;:23,&quot;forks&quot;:55,&quot;name&quot;:&quot;symfony/finder&quot;,&quot;stars&quot;:8436,&quot;usage&quot;:3936},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:264604,&quot;downloads_total&quot;:33391615,&quot;files&quot;:282,&quot;forks&quot;:280,&quot;name&quot;:&quot;phpspec/phpspec&quot;,&quot;stars&quot;:1888,&quot;usage&quot;:3851},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:159614,&quot;downloads_total&quot;:10636365,&quot;files&quot;:0,&quot;forks&quot;:125,&quot;name&quot;:&quot;php-coveralls/php-coveralls&quot;,&quot;stars&quot;:517,&quot;usage&quot;:3812},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:3096800,&quot;downloads_total&quot;:198345829,&quot;files&quot;:0,&quot;forks&quot;:454,&quot;name&quot;:&quot;doctrine/doctrine-bundle&quot;,&quot;stars&quot;:4736,&quot;usage&quot;:3594},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:3096800,&quot;downloads_total&quot;:198345829,&quot;files&quot;:67,&quot;forks&quot;:454,&quot;name&quot;:&quot;doctrine/doctrine-bundle&quot;,&quot;stars&quot;:4736,&quot;usage&quot;:3594},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:15042293,&quot;downloads_total&quot;:803012501,&quot;files&quot;:3,&quot;forks&quot;:51,&quot;name&quot;:&quot;psr/container&quot;,&quot;stars&quot;:9981,&quot;usage&quot;:3581},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:1207757,&quot;downloads_total&quot;:70626039,&quot;files&quot;:0,&quot;forks&quot;:1304,&quot;name&quot;:&quot;codeception/codeception&quot;,&quot;stars&quot;:4797,&quot;usage&quot;:3527},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:3235616,&quot;downloads_total&quot;:75693058,&quot;files&quot;:0,&quot;forks&quot;:708,&quot;name&quot;:&quot;rector/rector&quot;,&quot;stars&quot;:9397,&quot;usage&quot;:3526},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:210114,&quot;downloads_total&quot;:9086682,&quot;files&quot;:0,&quot;forks&quot;:18,&quot;name&quot;:&quot;typo3/cms-core&quot;,&quot;stars&quot;:31,&quot;usage&quot;:3502},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:10973720,&quot;downloads_total&quot;:545844010,&quot;files&quot;:114,&quot;forks&quot;:502,&quot;name&quot;:&quot;ramsey/uuid&quot;,&quot;stars&quot;:12515,&quot;usage&quot;:3372},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:293035,&quot;downloads_total&quot;:82846617,&quot;files&quot;:0,&quot;forks&quot;:9562,&quot;name&quot;:&quot;symfony/symfony&quot;,&quot;stars&quot;:30071,&quot;usage&quot;:3311},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:4867081,&quot;downloads_total&quot;:224448440,&quot;files&quot;:0,&quot;forks&quot;:104,&quot;name&quot;:&quot;symfony/validator&quot;,&quot;stars&quot;:2657,&quot;usage&quot;:3300},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:1084250,&quot;downloads_total&quot;:39273399,&quot;files&quot;:44,&quot;forks&quot;:49,&quot;name&quot;:&quot;phpstan/phpstan-strict-rules&quot;,&quot;stars&quot;:632,&quot;usage&quot;:3263},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:13306972,&quot;downloads_total&quot;:801654417,&quot;files&quot;:30,&quot;forks&quot;:67,&quot;name&quot;:&quot;symfony/event-dispatcher&quot;,&quot;stars&quot;:8534,&quot;usage&quot;:3263},{&quot;bus_factor&quot;:2,&quot;downloads_monthly&quot;:9249313,&quot;downloads_total&quot;:540730584,&quot;files&quot;:8,&quot;forks&quot;:50,&quot;name&quot;:&quot;symfony/filesystem&quot;,&quot;stars&quot;:4621,&quot;usage&quot;:3125},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:1521225,&quot;downloads_total&quot;:85396918,&quot;files&quot;:53,&quot;forks&quot;:102,&quot;name&quot;:&quot;mikey179/vfsstream&quot;,&quot;stars&quot;:1423,&quot;usage&quot;:3010},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:14212004,&quot;downloads_total&quot;:814214923,&quot;files&quot;:35,&quot;forks&quot;:295,&quot;name&quot;:&quot;guzzlehttp/psr7&quot;,&quot;stars&quot;:7905,&quot;usage&quot;:2970},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:14212004,&quot;downloads_total&quot;:814214923,&quot;files&quot;:32,&quot;forks&quot;:295,&quot;name&quot;:&quot;guzzlehttp/psr7&quot;,&quot;stars&quot;:7905,&quot;usage&quot;:2970},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:2335890,&quot;downloads_total&quot;:118912046,&quot;files&quot;:0,&quot;forks&quot;:111,&quot;name&quot;:&quot;symfony/form&quot;,&quot;stars&quot;:2753,&quot;usage&quot;:2938},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:36940,&quot;downloads_total&quot;:2933435,&quot;files&quot;:0,&quot;forks&quot;:824,&quot;name&quot;:&quot;silverstripe/framework&quot;,&quot;stars&quot;:720,&quot;usage&quot;:2919},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:8333861,&quot;downloads_total&quot;:249334538,&quot;files&quot;:0,&quot;forks&quot;:3584,&quot;name&quot;:&quot;fakerphp/faker&quot;,&quot;stars&quot;:3696,&quot;usage&quot;:2886},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:2511894,&quot;downloads_total&quot;:154249705,&quot;files&quot;:0,&quot;forks&quot;:4596,&quot;name&quot;:&quot;composer/composer&quot;,&quot;stars&quot;:28836,&quot;usage&quot;:2847},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:2511894,&quot;downloads_total&quot;:154249705,&quot;files&quot;:280,&quot;forks&quot;:4596,&quot;name&quot;:&quot;composer/composer&quot;,&quot;stars&quot;:28836,&quot;usage&quot;:2847},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:6330192,&quot;downloads_total&quot;:232554721,&quot;files&quot;:0,&quot;forks&quot;:50,&quot;name&quot;:&quot;symfony/http-client&quot;,&quot;stars&quot;:1979,&quot;usage&quot;:2631},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:11103163,&quot;downloads_total&quot;:605685717,&quot;files&quot;:50,&quot;forks&quot;:46,&quot;name&quot;:&quot;symfony/css-selector&quot;,&quot;stars&quot;:7440,&quot;usage&quot;:2628},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:4473774,&quot;downloads_total&quot;:243106523,&quot;files&quot;:0,&quot;forks&quot;:990,&quot;name&quot;:&quot;predis/predis&quot;,&quot;stars&quot;:7648,&quot;usage&quot;:2600},{&quot;bus_factor&quot;:2,&quot;downloads_monthly&quot;:4267067,&quot;downloads_total&quot;:226331863,&quot;files&quot;:14,&quot;forks&quot;:59,&quot;name&quot;:&quot;symfony/browser-kit&quot;,&quot;stars&quot;:2977,&quot;usage&quot;:2568},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:444890,&quot;downloads_total&quot;:38950806,&quot;files&quot;:0,&quot;forks&quot;:439,&quot;name&quot;:&quot;nunomaduro/larastan&quot;,&quot;stars&quot;:5756,&quot;usage&quot;:2484},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:2814721,&quot;downloads_total&quot;:153068840,&quot;files&quot;:331,&quot;forks&quot;:1495,&quot;name&quot;:&quot;intervention/image&quot;,&quot;stars&quot;:14082,&quot;usage&quot;:2445},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:2814721,&quot;downloads_total&quot;:153068840,&quot;files&quot;:331,&quot;forks&quot;:1495,&quot;name&quot;:&quot;intervention/image&quot;,&quot;stars&quot;:14082,&quot;usage&quot;:2445},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:915936,&quot;downloads_total&quot;:49510052,&quot;files&quot;:0,&quot;forks&quot;:24,&quot;name&quot;:&quot;illuminate/filesystem&quot;,&quot;stars&quot;:149,&quot;usage&quot;:2439},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:915936,&quot;downloads_total&quot;:49510052,&quot;files&quot;:12,&quot;forks&quot;:24,&quot;name&quot;:&quot;illuminate/filesystem&quot;,&quot;stars&quot;:149,&quot;usage&quot;:2439},{&quot;bus_factor&quot;:2,&quot;downloads_monthly&quot;:5434878,&quot;downloads_total&quot;:309910053,&quot;files&quot;:23,&quot;forks&quot;:125,&quot;name&quot;:&quot;symfony/dom-crawler&quot;,&quot;stars&quot;:3986,&quot;usage&quot;:2412},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:28791,&quot;downloads_total&quot;:3176111,&quot;files&quot;:0,&quot;forks&quot;:645,&quot;name&quot;:&quot;craftcms/cms&quot;,&quot;stars&quot;:3328,&quot;usage&quot;:2373},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:6438522,&quot;downloads_total&quot;:475446441,&quot;files&quot;:0,&quot;forks&quot;:235,&quot;name&quot;:&quot;doctrine/annotations&quot;,&quot;stars&quot;:6742,&quot;usage&quot;:2361},{&quot;bus_factor&quot;:3,&quot;downloads_monthly&quot;:5332557,&quot;downloads_total&quot;:234580355,&quot;files&quot;:130,&quot;forks&quot;:77,&quot;name&quot;:&quot;symfony/serializer&quot;,&quot;stars&quot;:2518,&quot;usage&quot;:2341},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:514788,&quot;downloads_total&quot;:35489214,&quot;files&quot;:20,&quot;forks&quot;:11,&quot;name&quot;:&quot;illuminate/config&quot;,&quot;stars&quot;:106,&quot;usage&quot;:2330},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:514788,&quot;downloads_total&quot;:35489214,&quot;files&quot;:31,&quot;forks&quot;:11,&quot;name&quot;:&quot;illuminate/config&quot;,&quot;stars&quot;:106,&quot;usage&quot;:2330},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:1448220,&quot;downloads_total&quot;:16467245,&quot;files&quot;:0,&quot;forks&quot;:19,&quot;name&quot;:&quot;pestphp/pest-plugin-arch&quot;,&quot;stars&quot;:33,&quot;usage&quot;:2328},{&quot;bus_factor&quot;:2,&quot;downloads_monthly&quot;:4248293,&quot;downloads_total&quot;:173669521,&quot;files&quot;:7,&quot;forks&quot;:29,&quot;name&quot;:&quot;symfony/dotenv&quot;,&quot;stars&quot;:3766,&quot;usage&quot;:2316},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:41564,&quot;downloads_total&quot;:3593448,&quot;files&quot;:118,&quot;forks&quot;:12,&quot;name&quot;:&quot;spryker/code-sniffer&quot;,&quot;stars&quot;:36,&quot;usage&quot;:2310},{&quot;bus_factor&quot;:2,&quot;downloads_monthly&quot;:3347591,&quot;downloads_total&quot;:148387034,&quot;files&quot;:45,&quot;forks&quot;:24,&quot;name&quot;:&quot;symfony/twig-bundle&quot;,&quot;stars&quot;:2494,&quot;usage&quot;:2303},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:632848,&quot;downloads_total&quot;:27234750,&quot;files&quot;:46,&quot;forks&quot;:62,&quot;name&quot;:&quot;spatie/laravel-ray&quot;,&quot;stars&quot;:295,&quot;usage&quot;:2301},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:7724250,&quot;downloads_total&quot;:408080867,&quot;files&quot;:0,&quot;forks&quot;:1228,&quot;name&quot;:&quot;aws/aws-sdk-php&quot;,&quot;stars&quot;:6082,&quot;usage&quot;:2259},{&quot;bus_factor&quot;:2,&quot;downloads_monthly&quot;:9684327,&quot;downloads_total&quot;:503869950,&quot;files&quot;:182,&quot;forks&quot;:833,&quot;name&quot;:&quot;league/flysystem&quot;,&quot;stars&quot;:13410,&quot;usage&quot;:2226},{&quot;bus_factor&quot;:2,&quot;downloads_monthly&quot;:6484555,&quot;downloads_total&quot;:264779554,&quot;files&quot;:72,&quot;forks&quot;:60,&quot;name&quot;:&quot;symfony/cache&quot;,&quot;stars&quot;:4125,&quot;usage&quot;:2217},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:405232,&quot;downloads_total&quot;:42225537,&quot;files&quot;:0,&quot;forks&quot;:179,&quot;name&quot;:&quot;sebastian/phpcpd&quot;,&quot;stars&quot;:2221,&quot;usage&quot;:2214},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:18799,&quot;downloads_total&quot;:1317733,&quot;files&quot;:0,&quot;forks&quot;:57,&quot;name&quot;:&quot;contao/core-bundle&quot;,&quot;stars&quot;:123,&quot;usage&quot;:2185},{&quot;bus_factor&quot;:0,&quot;downloads_monthly&quot;:1188011,&quot;downloads_total&quot;:79495397,&quot;files&quot;:523,&quot;forks&quot;:612,&quot;name&quot;:&quot;behat/behat&quot;,&quot;stars&quot;:3919,&quot;usage&quot;:2173},{&quot;bus_factor&quot;:1,&quot;downloads_monthly&quot;:1188011,&quot;downloads_total&quot;:79495397,&quot;files&quot;:531,&quot;forks&quot;:612,&quot;name&quot;:&quot;behat/behat&quot;,&quot;stars&quot;:3919,&quot;usage&quot;:2173}];
  const complexity = {&quot;avg_cyclomatic&quot;:8.778060316311837,&quot;avg_loc&quot;:9.717878677705247};
  const codeMetrics = {&quot;avg_afferent_coupling&quot;:4.8363979774115275,&quot;avg_efferent_coupling&quot;:2.1615589376608653,&quot;avg_files&quot;:21.60292529331748,&quot;avg_mi&quot;:108.70067995046874,&quot;total_classes&quot;:8563791,&quot;total_files&quot;:11100944,&quot;total_methods&quot;:45794037};
  const packageStats = {&quot;avg_stars&quot;:60.12450462351387,&quot;download_dominance&quot;:{&quot;cumulative_percentage&quot;:50.1130958244871,&quot;dominant_package_count&quot;:136,&quot;packages&quot;:[{&quot;dependents&quot;:354,&quot;downloads_monthly&quot;:15689866,&quot;forks&quot;:8,&quot;name&quot;:&quot;symfony/deprecation-contracts&quot;,&quot;percentage&quot;:0.5586445166114565,&quot;stars&quot;:2053},{&quot;dependents&quot;:462,&quot;downloads_monthly&quot;:15528737,&quot;forks&quot;:38,&quot;name&quot;:&quot;symfony/polyfill-mbstring&quot;,&quot;percentage&quot;:0.5529074483460495,&quot;stars&quot;:7847},{&quot;dependents&quot;:8736,&quot;downloads_monthly&quot;:15188541,&quot;forks&quot;:182,&quot;name&quot;:&quot;psr/log&quot;,&quot;percentage&quot;:0.540794621507812,&quot;stars&quot;:10421},{&quot;dependents&quot;:3581,&quot;downloads_monthly&quot;:15042293,&quot;forks&quot;:51,&quot;name&quot;:&quot;psr/container&quot;,&quot;percentage&quot;:0.5355873977325808,&quot;stars&quot;:9981},{&quot;dependents&quot;:11415,&quot;downloads_monthly&quot;:14851715,&quot;forks&quot;:259,&quot;name&quot;:&quot;symfony/console&quot;,&quot;percentage&quot;:0.5288017849882286,&quot;stars&quot;:9785},{&quot;dependents&quot;:209,&quot;downloads_monthly&quot;:14653286,&quot;forks&quot;:9,&quot;name&quot;:&quot;symfony/service-contracts&quot;,&quot;percentage&quot;:0.5217366339673916,&quot;stars&quot;:2595},{&quot;dependents&quot;:28,&quot;downloads_monthly&quot;:14595392,&quot;forks&quot;:4,&quot;name&quot;:&quot;symfony/polyfill-intl-normalizer&quot;,&quot;percentage&quot;:0.5196752928670467,&quot;stars&quot;:2026},{&quot;dependents&quot;:5164,&quot;downloads_monthly&quot;:14537371,&quot;forks&quot;:186,&quot;name&quot;:&quot;psr/http-message&quot;,&quot;percentage&quot;:0.5176094298763549,&quot;stars&quot;:6998},{&quot;dependents&quot;:2970,&quot;downloads_monthly&quot;:14212004,&quot;forks&quot;:295,&quot;name&quot;:&quot;guzzlehttp/psr7&quot;,&quot;percentage&quot;:0.5060245960456314,&quot;stars&quot;:7905},{&quot;dependents&quot;:461,&quot;downloads_monthly&quot;:14089514,&quot;forks&quot;:117,&quot;name&quot;:&quot;guzzlehttp/promises&quot;,&quot;percentage&quot;:0.5016632862141939,&quot;stars&quot;:7645},{&quot;dependents&quot;:5283,&quot;downloads_monthly&quot;:14087178,&quot;forks&quot;:104,&quot;name&quot;:&quot;symfony/process&quot;,&quot;percentage&quot;:0.5015801119161594,&quot;stars&quot;:7453},{&quot;dependents&quot;:111,&quot;downloads_monthly&quot;:14082419,&quot;forks&quot;:7,&quot;name&quot;:&quot;symfony/polyfill-ctype&quot;,&quot;percentage&quot;:0.5014106656471757,&quot;stars&quot;:4056},{&quot;dependents&quot;:3936,&quot;downloads_monthly&quot;:13973488,&quot;forks&quot;:55,&quot;name&quot;:&quot;symfony/finder&quot;,&quot;percentage&quot;:0.49753212991978313,&quot;stars&quot;:8436},{&quot;dependents&quot;:33276,&quot;downloads_monthly&quot;:13951038,&quot;forks&quot;:2398,&quot;name&quot;:&quot;guzzlehttp/guzzle&quot;,&quot;percentage&quot;:0.496732787886019,&quot;stars&quot;:23313},{&quot;dependents&quot;:620,&quot;downloads_monthly&quot;:13899161,&quot;forks&quot;:21,&quot;name&quot;:&quot;symfony/string&quot;,&quot;percentage&quot;:0.4948856846928972,&quot;stars&quot;:1741},{&quot;dependents&quot;:88,&quot;downloads_monthly&quot;:13865220,&quot;forks&quot;:34,&quot;name&quot;:&quot;ralouphie/getallheaders&quot;,&quot;percentage&quot;:0.49367720059632747,&quot;stars&quot;:3778},{&quot;dependents&quot;:1627,&quot;downloads_monthly&quot;:13615876,&quot;forks&quot;:24,&quot;name&quot;:&quot;psr/http-factory&quot;,&quot;percentage&quot;:0.48479919881161077,&quot;stars&quot;:1827},{&quot;dependents&quot;:1678,&quot;downloads_monthly&quot;:13603450,&quot;forks&quot;:1097,&quot;name&quot;:&quot;nikic/php-parser&quot;,&quot;percentage&quot;:0.48435676566633,&quot;stars&quot;:17179},{&quot;dependents&quot;:499,&quot;downloads_monthly&quot;:13565065,&quot;forks&quot;:26,&quot;name&quot;:&quot;symfony/polyfill-php80&quot;,&quot;percentage&quot;:0.48299005101305437,&quot;stars&quot;:1731},{&quot;dependents&quot;:21,&quot;downloads_monthly&quot;:13553454,&quot;forks&quot;:4,&quot;name&quot;:&quot;symfony/polyfill-intl-grapheme&quot;,&quot;percentage&quot;:0.48257663629795255,&quot;stars&quot;:1689},{&quot;dependents&quot;:1647,&quot;downloads_monthly&quot;:13335711,&quot;forks&quot;:17,&quot;name&quot;:&quot;psr/http-client&quot;,&quot;percentage&quot;:0.4748238018900278,&quot;stars&quot;:1685},{&quot;dependents&quot;:3263,&quot;downloads_monthly&quot;:13306972,&quot;forks&quot;:67,&quot;name&quot;:&quot;symfony/event-dispatcher&quot;,&quot;percentage&quot;:0.4738005372705023,&quot;stars&quot;:8534},{&quot;dependents&quot;:7517,&quot;downloads_monthly&quot;:13099994,&quot;forks&quot;:1899,&quot;name&quot;:&quot;monolog/monolog&quot;,&quot;percentage&quot;:0.46643099537899047,&quot;stars&quot;:21132},{&quot;dependents&quot;:288,&quot;downloads_monthly&quot;:13070516,&quot;forks&quot;:3,&quot;name&quot;:&quot;symfony/event-dispatcher-contracts&quot;,&quot;percentage&quot;:0.46538141834240704,&quot;stars&quot;:3394},{&quot;dependents&quot;:7637,&quot;downloads_monthly&quot;:12977531,&quot;forks&quot;:103,&quot;name&quot;:&quot;symfony/var-dumper&quot;,&quot;percentage&quot;:0.46207064689431965,&quot;stars&quot;:7426},{&quot;dependents&quot;:163,&quot;downloads_monthly&quot;:12776353,&quot;forks&quot;:103,&quot;name&quot;:&quot;myclabs/deep-copy&quot;,&quot;percentage&quot;:0.4549076165304619,&quot;stars&quot;:8807},{&quot;dependents&quot;:139853,&quot;downloads_monthly&quot;:12679593,&quot;forks&quot;:2206,&quot;name&quot;:&quot;phpunit/phpunit&quot;,&quot;percentage&quot;:0.45146243456222046,&quot;stars&quot;:19791},{&quot;dependents&quot;:5080,&quot;downloads_monthly&quot;:12655313,&quot;forks&quot;:298,&quot;name&quot;:&quot;symfony/http-foundation&quot;,&quot;percentage&quot;:0.4505979345809379,&quot;stars&quot;:8652},{&quot;dependents&quot;:237,&quot;downloads_monthly&quot;:12611304,&quot;forks&quot;:84,&quot;name&quot;:&quot;sebastian/diff&quot;,&quot;percentage&quot;:0.44903097495670946,&quot;stars&quot;:7600},{&quot;dependents&quot;:102,&quot;downloads_monthly&quot;:12566777,&quot;forks&quot;:59,&quot;name&quot;:&quot;doctrine/lexer&quot;,&quot;percentage&quot;:0.44744557171673544,&quot;stars&quot;:11102},{&quot;dependents&quot;:296,&quot;downloads_monthly&quot;:12438223,&quot;forks&quot;:3,&quot;name&quot;:&quot;symfony/translation-contracts&quot;,&quot;percentage&quot;:0.4428683505225921,&quot;stars&quot;:2576},{&quot;dependents&quot;:1522,&quot;downloads_monthly&quot;:12325214,&quot;forks&quot;:376,&quot;name&quot;:&quot;phpunit/php-code-coverage&quot;,&quot;percentage&quot;:0.43884461582799733,&quot;stars&quot;:8863},{&quot;dependents&quot;:70,&quot;downloads_monthly&quot;:12285878,&quot;forks&quot;:6,&quot;name&quot;:&quot;symfony/polyfill-intl-idn&quot;,&quot;percentage&quot;:0.4374440404052736,&quot;stars&quot;:3339},{&quot;dependents&quot;:152,&quot;downloads_monthly&quot;:12268127,&quot;forks&quot;:69,&quot;name&quot;:&quot;sebastian/comparator&quot;,&quot;percentage&quot;:0.4368120083143449,&quot;stars&quot;:7024},{&quot;dependents&quot;:75,&quot;downloads_monthly&quot;:12257054,&quot;forks&quot;:34,&quot;name&quot;:&quot;sebastian/exporter&quot;,&quot;percentage&quot;:0.43641774932370486,&quot;stars&quot;:6801},{&quot;dependents&quot;:740,&quot;downloads_monthly&quot;:12221849,&quot;forks&quot;:21,&quot;name&quot;:&quot;psr/event-dispatcher&quot;,&quot;percentage&quot;:0.43516425995628094,&quot;stars&quot;:2209},{&quot;dependents&quot;:69,&quot;downloads_monthly&quot;:12168364,&quot;forks&quot;:46,&quot;name&quot;:&quot;phpunit/php-file-iterator&quot;,&quot;percentage&quot;:0.4332599032223889,&quot;stars&quot;:7444},{&quot;dependents&quot;:18,&quot;downloads_monthly&quot;:12155378,&quot;forks&quot;:18,&quot;name&quot;:&quot;sebastian/recursion-context&quot;,&quot;percentage&quot;:0.43279753103305874,&quot;stars&quot;:6556},{&quot;dependents&quot;:35,&quot;downloads_monthly&quot;:12144945,&quot;forks&quot;:19,&quot;name&quot;:&quot;sebastian/global-state&quot;,&quot;percentage&quot;:0.4324260595213321,&quot;stars&quot;:6584},{&quot;dependents&quot;:138,&quot;downloads_monthly&quot;:12138063,&quot;forks&quot;:32,&quot;name&quot;:&quot;sebastian/version&quot;,&quot;percentage&quot;:0.43218102291213994,&quot;stars&quot;:6557},{&quot;dependents&quot;:141,&quot;downloads_monthly&quot;:12136356,&quot;forks&quot;:65,&quot;name&quot;:&quot;phpunit/php-timer&quot;,&quot;percentage&quot;:0.4321202444332252,&quot;stars&quot;:7684},{&quot;dependents&quot;:63,&quot;downloads_monthly&quot;:12118159,&quot;forks&quot;:35,&quot;name&quot;:&quot;sebastian/environment&quot;,&quot;percentage&quot;:0.43147233231792875,&quot;stars&quot;:6752},{&quot;dependents&quot;:1141,&quot;downloads_monthly&quot;:12103984,&quot;forks&quot;:147,&quot;name&quot;:&quot;webmozart/assert&quot;,&quot;percentage&quot;:0.43096762526542953,&quot;stars&quot;:7588},{&quot;dependents&quot;:73,&quot;downloads_monthly&quot;:12076509,&quot;forks&quot;:30,&quot;name&quot;:&quot;phpunit/php-text-template&quot;,&quot;percentage&quot;:0.42998936591675824,&quot;stars&quot;:7389},{&quot;dependents&quot;:6033,&quot;downloads_monthly&quot;:12047419,&quot;forks&quot;:92,&quot;name&quot;:&quot;symfony/http-kernel&quot;,&quot;percentage&quot;:0.42895360378926606,&quot;stars&quot;:8126},{&quot;dependents&quot;:20,&quot;downloads_monthly&quot;:12008759,&quot;forks&quot;:9,&quot;name&quot;:&quot;sebastian/object-enumerator&quot;,&quot;percentage&quot;:0.4275770976411448,&quot;stars&quot;:6507},{&quot;dependents&quot;:8,&quot;downloads_monthly&quot;:11935574,&quot;forks&quot;:6,&quot;name&quot;:&quot;sebastian/code-unit-reverse-lookup&quot;,&quot;percentage&quot;:0.4249713138219452,&quot;stars&quot;:6690},{&quot;dependents&quot;:9438,&quot;downloads_monthly&quot;:11919219,&quot;forks&quot;:130,&quot;name&quot;:&quot;symfony/yaml&quot;,&quot;percentage&quot;:0.42438898691939675,&quot;stars&quot;:3826},{&quot;dependents&quot;:456,&quot;downloads_monthly&quot;:11910831,&quot;forks&quot;:19,&quot;name&quot;:&quot;symfony/error-handler&quot;,&quot;percentage&quot;:0.4240903285238861,&quot;stars&quot;:2623},{&quot;dependents&quot;:12,&quot;downloads_monthly&quot;:11885178,&quot;forks&quot;:21,&quot;name&quot;:&quot;theseer/tokenizer&quot;,&quot;percentage&quot;:0.42317694227924685,&quot;stars&quot;:5177},{&quot;dependents&quot;:22,&quot;downloads_monthly&quot;:11867838,&quot;forks&quot;:14,&quot;name&quot;:&quot;phar-io/manifest&quot;,&quot;percentage&quot;:0.42255954402243295,&quot;stars&quot;:7431},{&quot;dependents&quot;:1954,&quot;downloads_monthly&quot;:11857545,&quot;forks&quot;:88,&quot;name&quot;:&quot;symfony/translation&quot;,&quot;percentage&quot;:0.42219305727171874,&quot;stars&quot;:6620},{&quot;dependents&quot;:10,&quot;downloads_monthly&quot;:11849916,&quot;forks&quot;:3,&quot;name&quot;:&quot;sebastian/object-reflector&quot;,&quot;percentage&quot;:0.42192142340198213,&quot;stars&quot;:6257},{&quot;dependents&quot;:728,&quot;downloads_monthly&quot;:11845211,&quot;forks&quot;:38,&quot;name&quot;:&quot;symfony/mime&quot;,&quot;percentage&quot;:0.42175389982653183,&quot;stars&quot;:2794},{&quot;dependents&quot;:38,&quot;downloads_monthly&quot;:11771140,&quot;forks&quot;:16,&quot;name&quot;:&quot;phar-io/version&quot;,&quot;percentage&quot;:0.4191165695912113,&quot;stars&quot;:7429},{&quot;dependents&quot;:1585,&quot;downloads_monthly&quot;:11769804,&quot;forks&quot;:96,&quot;name&quot;:&quot;symfony/routing&quot;,&quot;percentage&quot;:0.41906900072897924,&quot;stars&quot;:7623},{&quot;dependents&quot;:1891,&quot;downloads_monthly&quot;:11525601,&quot;forks&quot;:52,&quot;name&quot;:&quot;psr/simple-cache&quot;,&quot;percentage&quot;:0.41037404648972275,&quot;stars&quot;:8104},{&quot;dependents&quot;:17,&quot;downloads_monthly&quot;:11466895,&quot;forks&quot;:13,&quot;name&quot;:&quot;sebastian/type&quot;,&quot;percentage&quot;:0.4082837937755063,&quot;stars&quot;:1434},{&quot;dependents&quot;:787,&quot;downloads_monthly&quot;:11455405,&quot;forks&quot;:135,&quot;name&quot;:&quot;doctrine/inflector&quot;,&quot;percentage&quot;:0.40787468731813653,&quot;stars&quot;:11290},{&quot;dependents&quot;:1250,&quot;downloads_monthly&quot;:11178404,&quot;forks&quot;:45,&quot;name&quot;:&quot;psr/cache&quot;,&quot;percentage&quot;:0.39801194599543244,&quot;stars&quot;:5138},{&quot;dependents&quot;:287,&quot;downloads_monthly&quot;:11165847,&quot;forks&quot;:217,&quot;name&quot;:&quot;egulias/email-validator&quot;,&quot;percentage&quot;:0.3975648485380616,&quot;stars&quot;:11517},{&quot;dependents&quot;:29,&quot;downloads_monthly&quot;:11116948,&quot;forks&quot;:5,&quot;name&quot;:&quot;sebastian/cli-parser&quot;,&quot;percentage&quot;:0.3958237783327594,&quot;stars&quot;:1132},{&quot;dependents&quot;:2628,&quot;downloads_monthly&quot;:11103163,&quot;forks&quot;:46,&quot;name&quot;:&quot;symfony/css-selector&quot;,&quot;percentage&quot;:0.3953329574002231,&quot;stars&quot;:7440},{&quot;dependents&quot;:9,&quot;downloads_monthly&quot;:11054904,&quot;forks&quot;:9,&quot;name&quot;:&quot;sebastian/complexity&quot;,&quot;percentage&quot;:0.39361467467383454,&quot;stars&quot;:1183},{&quot;dependents&quot;:11,&quot;downloads_monthly&quot;:11036131,&quot;forks&quot;:7,&quot;name&quot;:&quot;sebastian/lines-of-code&quot;,&quot;percentage&quot;:0.3929462538275158,&quot;stars&quot;:1101},{&quot;dependents&quot;:349,&quot;downloads_monthly&quot;:10990295,&quot;forks&quot;:18,&quot;name&quot;:&quot;phpunit/php-invoker&quot;,&quot;percentage&quot;:0.3913142430720764,&quot;stars&quot;:1256},{&quot;dependents&quot;:3372,&quot;downloads_monthly&quot;:10973720,&quot;forks&quot;:502,&quot;name&quot;:&quot;ramsey/uuid&quot;,&quot;percentage&quot;:0.3907240829736514,&quot;stars&quot;:12515},{&quot;dependents&quot;:13,&quot;downloads_monthly&quot;:10961496,&quot;forks&quot;:8,&quot;name&quot;:&quot;sebastian/code-unit&quot;,&quot;percentage&quot;:0.39028884212640275,&quot;stars&quot;:1131},{&quot;dependents&quot;:206,&quot;downloads_monthly&quot;:10904701,&quot;forks&quot;:6,&quot;name&quot;:&quot;psr/clock&quot;,&quot;percentage&quot;:0.3882666314000047,&quot;stars&quot;:526},{&quot;dependents&quot;:26,&quot;downloads_monthly&quot;:10675187,&quot;forks&quot;:18,&quot;name&quot;:&quot;doctrine/deprecations&quot;,&quot;percentage&quot;:0.3800946854072498,&quot;stars&quot;:1707},{&quot;dependents&quot;:203,&quot;downloads_monthly&quot;:10551624,&quot;forks&quot;:79,&quot;name&quot;:&quot;brick/math&quot;,&quot;percentage&quot;:0.3756951709431963,&quot;stars&quot;:1907},{&quot;dependents&quot;:67,&quot;downloads_monthly&quot;:10391793,&quot;forks&quot;:57,&quot;name&quot;:&quot;ramsey/collection&quot;,&quot;percentage&quot;:0.3700043185334609,&quot;stars&quot;:1153},{&quot;dependents&quot;:4857,&quot;downloads_monthly&quot;:10090649,&quot;forks&quot;:6,&quot;name&quot;:&quot;nesbot/carbon&quot;,&quot;percentage&quot;:0.3592819551741791,&quot;stars&quot;:48},{&quot;dependents&quot;:95,&quot;downloads_monthly&quot;:9842084,&quot;forks&quot;:3,&quot;name&quot;:&quot;symfony/polyfill-php83&quot;,&quot;percentage&quot;:0.3504316900239524,&quot;stars&quot;:357},{&quot;dependents&quot;:2226,&quot;downloads_monthly&quot;:9684327,&quot;forks&quot;:833,&quot;name&quot;:&quot;league/flysystem&quot;,&quot;percentage&quot;:0.34481468328807124,&quot;stars&quot;:13410},{&quot;dependents&quot;:93,&quot;downloads_monthly&quot;:9632153,&quot;forks&quot;:21,&quot;name&quot;:&quot;league/mime-type-detection&quot;,&quot;percentage&quot;:0.34295700528051615,&quot;stars&quot;:1300},{&quot;dependents&quot;:786,&quot;downloads_monthly&quot;:9592543,&quot;forks&quot;:48,&quot;name&quot;:&quot;symfony/mailer&quot;,&quot;percentage&quot;:0.34154667396838256,&quot;stars&quot;:1527},{&quot;dependents&quot;:349,&quot;downloads_monthly&quot;:9427651,&quot;forks&quot;:62,&quot;name&quot;:&quot;doctrine/instantiator&quot;,&quot;percentage&quot;:0.33567562244805116,&quot;stars&quot;:10975},{&quot;dependents&quot;:3125,&quot;downloads_monthly&quot;:9249313,&quot;forks&quot;:50,&quot;name&quot;:&quot;symfony/filesystem&quot;,&quot;percentage&quot;:0.3293258202379205,&quot;stars&quot;:4621},{&quot;dependents&quot;:5597,&quot;downloads_monthly&quot;:8979714,&quot;forks&quot;:638,&quot;name&quot;:&quot;vlucas/phpdotenv&quot;,&quot;percentage&quot;:0.31972663035102583,&quot;stars&quot;:13265},{&quot;dependents&quot;:167,&quot;downloads_monthly&quot;:8902122,&quot;forks&quot;:63,&quot;name&quot;:&quot;phpoption/phpoption&quot;,&quot;percentage&quot;:0.31696393337624507,&quot;stars&quot;:2644},{&quot;dependents&quot;:801,&quot;downloads_monthly&quot;:8692473,&quot;forks&quot;:313,&quot;name&quot;:&quot;psy/psysh&quot;,&quot;percentage&quot;:0.3094992893657051,&quot;stars&quot;:9767},{&quot;dependents&quot;:407,&quot;downloads_monthly&quot;:8569485,&quot;forks&quot;:340,&quot;name&quot;:&quot;dragonmantank/cron-expression&quot;,&quot;percentage&quot;:0.3051202480272379,&quot;stars&quot;:4594},{&quot;dependents&quot;:1604,&quot;downloads_monthly&quot;:8458789,&quot;forks&quot;:149,&quot;name&quot;:&quot;nette/utils&quot;,&quot;percentage&quot;:0.3011788687056541,&quot;stars&quot;:2034},{&quot;dependents&quot;:3,&quot;downloads_monthly&quot;:8446563,&quot;forks&quot;:2,&quot;name&quot;:&quot;carbonphp/carbon-doctrine-types&quot;,&quot;percentage&quot;:0.30074355664753377,&quot;stars&quot;:156},{&quot;dependents&quot;:236,&quot;downloads_monthly&quot;:8382399,&quot;forks&quot;:185,&quot;name&quot;:&quot;tijsverkoyen/css-to-inline-styles&quot;,&quot;percentage&quot;:0.29845896946470774,&quot;stars&quot;:5826},{&quot;dependents&quot;:119,&quot;downloads_monthly&quot;:8380919,&quot;forks&quot;:35,&quot;name&quot;:&quot;voku/portable-ascii&quot;,&quot;percentage&quot;:0.29840627341972015,&quot;stars&quot;:555},{&quot;dependents&quot;:574,&quot;downloads_monthly&quot;:8376172,&quot;forks&quot;:74,&quot;name&quot;:&quot;composer/semver&quot;,&quot;percentage&quot;:0.29823725441596616,&quot;stars&quot;:3195},{&quot;dependents&quot;:2886,&quot;downloads_monthly&quot;:8333861,&quot;forks&quot;:3584,&quot;name&quot;:&quot;fakerphp/faker&quot;,&quot;percentage&quot;:0.2967307528217303,&quot;stars&quot;:3696},{&quot;dependents&quot;:4,&quot;downloads_monthly&quot;:8301890,&quot;forks&quot;:7,&quot;name&quot;:&quot;graham-campbell/result-type&quot;,&quot;percentage&quot;:0.2955924114336913,&quot;stars&quot;:504},{&quot;dependents&quot;:440,&quot;downloads_monthly&quot;:8244296,&quot;forks&quot;:146,&quot;name&quot;:&quot;paragonie/random_compat&quot;,&quot;percentage&quot;:0.29354175196408716,&quot;stars&quot;:8178},{&quot;dependents&quot;:162,&quot;downloads_monthly&quot;:8204645,&quot;forks&quot;:35,&quot;name&quot;:&quot;laravel/serializable-closure&quot;,&quot;percentage&quot;:0.2921299608290857,&quot;stars&quot;:550},{&quot;dependents&quot;:79,&quot;downloads_monthly&quot;:8159550,&quot;forks&quot;:18,&quot;name&quot;:&quot;dflydev/dot-access-data&quot;,&quot;percentage&quot;:0.29052433370157593,&quot;stars&quot;:666},{&quot;dependents&quot;:1627,&quot;downloads_monthly&quot;:8107720,&quot;forks&quot;:32,&quot;name&quot;:&quot;symfony/options-resolver&quot;,&quot;percentage&quot;:0.28867890396393686,&quot;stars&quot;:3222},{&quot;dependents&quot;:125,&quot;downloads_monthly&quot;:8060636,&quot;forks&quot;:50,&quot;name&quot;:&quot;phpdocumentor/type-resolver&quot;,&quot;percentage&quot;:0.28700245762461607,&quot;stars&quot;:9161},{&quot;dependents&quot;:337,&quot;downloads_monthly&quot;:8021326,&quot;forks&quot;:64,&quot;name&quot;:&quot;phpstan/phpdoc-parser&quot;,&quot;percentage&quot;:0.28560280794322324,&quot;stars&quot;:1398},{&quot;dependents&quot;:172,&quot;downloads_monthly&quot;:8006742,&quot;forks&quot;:9,&quot;name&quot;:&quot;symfony/polyfill-php81&quot;,&quot;percentage&quot;:0.28508353826748084,&quot;stars&quot;:877},{&quot;dependents&quot;:56,&quot;downloads_monthly&quot;:7986881,&quot;forks&quot;:2,&quot;name&quot;:&quot;symfony/polyfill-uuid&quot;,&quot;percentage&quot;:0.28437637870700916,&quot;stars&quot;:657},{&quot;dependents&quot;:5761,&quot;downloads_monthly&quot;:7955368,&quot;forks&quot;:1353,&quot;name&quot;:&quot;doctrine/dbal&quot;,&quot;percentage&quot;:0.2832543446085677,&quot;stars&quot;:9554},{&quot;dependents&quot;:26,&quot;downloads_monthly&quot;:7936497,&quot;forks&quot;:21,&quot;name&quot;:&quot;phpdocumentor/reflection-common&quot;,&quot;percentage&quot;:0.28258243442954034,&quot;stars&quot;:9045},{&quot;dependents&quot;:571,&quot;downloads_monthly&quot;:7914641,&quot;forks&quot;:195,&quot;name&quot;:&quot;league/commonmark&quot;,&quot;percentage&quot;:0.2818042420246428,&quot;stars&quot;:2807},{&quot;dependents&quot;:490,&quot;downloads_monthly&quot;:7873090,&quot;forks&quot;:12,&quot;name&quot;:&quot;symfony/uid&quot;,&quot;percentage&quot;:0.2803248005616168,&quot;stars&quot;:568},{&quot;dependents&quot;:17310,&quot;downloads_monthly&quot;:7860661,&quot;forks&quot;:11237,&quot;name&quot;:&quot;laravel/framework&quot;,&quot;percentage&quot;:0.2798822606000286,&quot;stars&quot;:33237},{&quot;dependents&quot;:101,&quot;downloads_monthly&quot;:7839875,&quot;forks&quot;:26,&quot;name&quot;:&quot;nette/schema&quot;,&quot;percentage&quot;:0.27914216601143965,&quot;stars&quot;:939},{&quot;dependents&quot;:29,&quot;downloads_monthly&quot;:7828016,&quot;forks&quot;:14,&quot;name&quot;:&quot;composer/pcre&quot;,&quot;percentage&quot;:0.27871992114825883,&quot;stars&quot;:597},{&quot;dependents&quot;:24273,&quot;downloads_monthly&quot;:7824504,&quot;forks&quot;:457,&quot;name&quot;:&quot;mockery/mockery&quot;,&quot;percentage&quot;:0.2785948748577208,&quot;stars&quot;:10661},{&quot;dependents&quot;:98,&quot;downloads_monthly&quot;:7784794,&quot;forks&quot;:22,&quot;name&quot;:&quot;doctrine/event-manager&quot;,&quot;percentage&quot;:0.27718098300200694,&quot;stars&quot;:5964},{&quot;dependents&quot;:2006,&quot;downloads_monthly&quot;:7741281,&quot;forks&quot;:1419,&quot;name&quot;:&quot;firebase/php-jwt&quot;,&quot;percentage&quot;:0.2756316836739366,&quot;stars&quot;:9526},{&quot;dependents&quot;:115,&quot;downloads_monthly&quot;:7730385,&quot;forks&quot;:43,&quot;name&quot;:&quot;hamcrest/hamcrest-php&quot;,&quot;percentage&quot;:0.2752437268454335,&quot;stars&quot;:6970},{&quot;dependents&quot;:2259,&quot;downloads_monthly&quot;:7724250,&quot;forks&quot;:1228,&quot;name&quot;:&quot;aws/aws-sdk-php&quot;,&quot;percentage&quot;:0.2750252874967857,&quot;stars&quot;:6082},{&quot;dependents&quot;:289,&quot;downloads_monthly&quot;:7717192,&quot;forks&quot;:7,&quot;name&quot;:&quot;symfony/var-exporter&quot;,&quot;percentage&quot;:0.2747739843308923,&quot;stars&quot;:2065},{&quot;dependents&quot;:136,&quot;downloads_monthly&quot;:7656513,&quot;forks&quot;:54,&quot;name&quot;:&quot;mtdowling/jmespath.php&quot;,&quot;percentage&quot;:0.27261348209183767,&quot;stars&quot;:1954},{&quot;dependents&quot;:16,&quot;downloads_monthly&quot;:7539219,&quot;forks&quot;:13,&quot;name&quot;:&quot;sebastian/resource-operations&quot;,&quot;percentage&quot;:0.2684371781048295,&quot;stars&quot;:6281},{&quot;dependents&quot;:9,&quot;downloads_monthly&quot;:7538044,&quot;forks&quot;:2,&quot;name&quot;:&quot;league/flysystem-local&quot;,&quot;percentage&quot;:0.26839534171776164,&quot;stars&quot;:180},{&quot;dependents&quot;:1953,&quot;downloads_monthly&quot;:7438510,&quot;forks&quot;:128,&quot;name&quot;:&quot;laravel/tinker&quot;,&quot;percentage&quot;:0.2648513902706043,&quot;stars&quot;:7363},{&quot;dependents&quot;:1113,&quot;downloads_monthly&quot;:7415634,&quot;forks&quot;:124,&quot;name&quot;:&quot;phpdocumentor/reflection-docblock&quot;,&quot;percentage&quot;:0.2640368803211883,&quot;stars&quot;:9360},{&quot;dependents&quot;:24,&quot;downloads_monthly&quot;:7359474,&quot;forks&quot;:14,&quot;name&quot;:&quot;league/config&quot;,&quot;percentage&quot;:0.2620372790465248,&quot;stars&quot;:519},{&quot;dependents&quot;:1173,&quot;downloads_monthly&quot;:7354190,&quot;forks&quot;:214,&quot;name&quot;:&quot;doctrine/cache&quot;,&quot;percentage&quot;:0.26184913992374487,&quot;stars&quot;:7868},{&quot;dependents&quot;:139,&quot;downloads_monthly&quot;:7129546,&quot;forks&quot;:35,&quot;name&quot;:&quot;paragonie/constant_time_encoding&quot;,&quot;percentage&quot;:0.2538505924033477,&quot;stars&quot;:836},{&quot;dependents&quot;:1250,&quot;downloads_monthly&quot;:7106394,&quot;forks&quot;:898,&quot;name&quot;:&quot;phpseclib/phpseclib&quot;,&quot;percentage&quot;:0.25302625535365025,&quot;stars&quot;:5434},{&quot;dependents&quot;:4,&quot;downloads_monthly&quot;:7066887,&quot;forks&quot;:13,&quot;name&quot;:&quot;aws/aws-crt-php&quot;,&quot;percentage&quot;:0.25161959140140433,&quot;stars&quot;:367},{&quot;dependents&quot;:1778,&quot;downloads_monthly&quot;:7032322,&quot;forks&quot;:602,&quot;name&quot;:&quot;filp/whoops&quot;,&quot;percentage&quot;:0.25038888951289395,&quot;stars&quot;:13218},{&quot;dependents&quot;:23015,&quot;downloads_monthly&quot;:6957511,&quot;forks&quot;:910,&quot;name&quot;:&quot;phpstan/phpstan&quot;,&quot;percentage&quot;:0.24772521125507965,&quot;stars&quot;:13240},{&quot;dependents&quot;:55,&quot;downloads_monthly&quot;:6945810,&quot;forks&quot;:8,&quot;name&quot;:&quot;guzzlehttp/uri-template&quot;,&quot;percentage&quot;:0.24730859205075564,&quot;stars&quot;:154},{&quot;dependents&quot;:251,&quot;downloads_monthly&quot;:6940907,&quot;forks&quot;:85,&quot;name&quot;:&quot;nunomaduro/termwind&quot;,&quot;percentage&quot;:0.24713401859901638,&quot;stars&quot;:2377},{&quot;dependents&quot;:6033,&quot;downloads_monthly&quot;:6704982,&quot;forks&quot;:42,&quot;name&quot;:&quot;symfony/config&quot;,&quot;percentage&quot;:0.23873380615733217,&quot;stars&quot;:4248},{&quot;dependents&quot;:308,&quot;downloads_monthly&quot;:6688929,&quot;forks&quot;:98,&quot;name&quot;:&quot;laravel/prompts&quot;,&quot;percentage&quot;:0.23816223209639606,&quot;stars&quot;:562},{&quot;dependents&quot;:37,&quot;downloads_monthly&quot;:6671598,&quot;forks&quot;:15,&quot;name&quot;:&quot;fruitcake/php-cors&quot;,&quot;percentage&quot;:0.23754515428850445,&quot;stars&quot;:261},{&quot;dependents&quot;:6775,&quot;downloads_monthly&quot;:6657245,&quot;forks&quot;:94,&quot;name&quot;:&quot;symfony/dependency-injection&quot;,&quot;percentage&quot;:0.2370341094684324,&quot;stars&quot;:4130},{&quot;dependents&quot;:144,&quot;downloads_monthly&quot;:6615220,&quot;forks&quot;:3,&quot;name&quot;:&quot;symfony/http-client-contracts&quot;,&quot;percentage&quot;:0.23553779102883604,&quot;stars&quot;:1952},{&quot;dependents&quot;:84,&quot;downloads_monthly&quot;:6600274,&quot;forks&quot;:8,&quot;name&quot;:&quot;symfony/polyfill-php73&quot;,&quot;percentage&quot;:0.23500563218533319,&quot;stars&quot;:2399},{&quot;dependents&quot;:95,&quot;downloads_monthly&quot;:6543673,&quot;forks&quot;:3,&quot;name&quot;:&quot;symfony/clock&quot;,&quot;percentage&quot;:0.23299032891348082,&quot;stars&quot;:353},{&quot;dependents&quot;:17,&quot;downloads_monthly&quot;:6508996,&quot;forks&quot;:7,&quot;name&quot;:&quot;league/uri-interfaces&quot;,&quot;percentage&quot;:0.23175563921616055,&quot;stars&quot;:482},{&quot;dependents&quot;:2217,&quot;downloads_monthly&quot;:6484555,&quot;forks&quot;:60,&quot;name&quot;:&quot;symfony/cache&quot;,&quot;percentage&quot;:0.23088540675971378,&quot;stars&quot;:4125},{&quot;dependents&quot;:253,&quot;downloads_monthly&quot;:6472130,&quot;forks&quot;:66,&quot;name&quot;:&quot;league/uri&quot;,&quot;percentage&quot;:0.2304430092198688,&quot;stars&quot;:1060},{&quot;dependents&quot;:2361,&quot;downloads_monthly&quot;:6438522,&quot;forks&quot;:235,&quot;name&quot;:&quot;doctrine/annotations&quot;,&quot;percentage&quot;:0.22924638173342132,&quot;stars&quot;:6742}],&quot;threshold&quot;:1404279961,&quot;total_monthly_downloads&quot;:2808559922},&quot;empty_packages&quot;:94911,&quot;loc_packages&quot;:{&quot;classes&quot;:8563791,&quot;classes_loc&quot;:0,&quot;cloc&quot;:164548311,&quot;downloads_monthly&quot;:2601999280,&quot;downloads_total&quot;:123822717088,&quot;files&quot;:5558384,&quot;forks&quot;:2414,&quot;interfaces&quot;:11100944,&quot;lloc&quot;:362889228,&quot;loc&quot;:604562538,&quot;open_issues&quot;:444893,&quot;stars&quot;:29,&quot;total_packages&quot;:513863},&quot;packages_needing_help&quot;:[{&quot;bus_factor&quot;:1,&quot;classes&quot;:10,&quot;downloads_monthly&quot;:559298,&quot;downloads_total&quot;:25513726,&quot;files&quot;:21,&quot;forks&quot;:137,&quot;name&quot;:&quot;orchestra/testbench&quot;,&quot;stars&quot;:2147,&quot;usage&quot;:26435},{&quot;bus_factor&quot;:1,&quot;classes&quot;:9,&quot;downloads_monthly&quot;:11919219,&quot;downloads_total&quot;:627915019,&quot;files&quot;:10,&quot;forks&quot;:130,&quot;name&quot;:&quot;symfony/yaml&quot;,&quot;stars&quot;:3826,&quot;usage&quot;:9438},{&quot;bus_factor&quot;:0,&quot;classes&quot;:19,&quot;downloads_monthly&quot;:15188541,&quot;downloads_total&quot;:963431048,&quot;files&quot;:20,&quot;forks&quot;:182,&quot;name&quot;:&quot;psr/log&quot;,&quot;stars&quot;:10421,&quot;usage&quot;:8736},{&quot;bus_factor&quot;:0,&quot;classes&quot;:17,&quot;downloads_monthly&quot;:2126980,&quot;downloads_total&quot;:96035049,&quot;files&quot;:146,&quot;forks&quot;:82,&quot;name&quot;:&quot;illuminate/contracts&quot;,&quot;stars&quot;:682,&quot;usage&quot;:8216},{&quot;bus_factor&quot;:0,&quot;classes&quot;:1,&quot;downloads_monthly&quot;:2126980,&quot;downloads_total&quot;:96035049,&quot;files&quot;:7,&quot;forks&quot;:82,&quot;name&quot;:&quot;illuminate/contracts&quot;,&quot;stars&quot;:682,&quot;usage&quot;:8216},{&quot;bus_factor&quot;:2,&quot;classes&quot;:62,&quot;downloads_monthly&quot;:12977531,&quot;downloads_total&quot;:678659761,&quot;files&quot;:68,&quot;forks&quot;:103,&quot;name&quot;:&quot;symfony/var-dumper&quot;,&quot;stars&quot;:7426,&quot;usage&quot;:7637},{&quot;bus_factor&quot;:0,&quot;classes&quot;:276,&quot;downloads_monthly&quot;:1896963,&quot;downloads_total&quot;:109882055,&quot;files&quot;:298,&quot;forks&quot;:412,&quot;name&quot;:&quot;composer/installers&quot;,&quot;stars&quot;:1437,&quot;usage&quot;:7264},{&quot;bus_factor&quot;:1,&quot;classes&quot;:33,&quot;downloads_monthly&quot;:1896963,&quot;downloads_total&quot;:109882055,&quot;files&quot;:33,&quot;forks&quot;:412,&quot;name&quot;:&quot;composer/installers&quot;,&quot;stars&quot;:1437,&quot;usage&quot;:7264},{&quot;bus_factor&quot;:1,&quot;classes&quot;:80,&quot;downloads_monthly&quot;:6432473,&quot;downloads_total&quot;:244614019,&quot;files&quot;:71,&quot;forks&quot;:164,&quot;name&quot;:&quot;nunomaduro/collision&quot;,&quot;stars&quot;:4552,&quot;usage&quot;:6700},{&quot;bus_factor&quot;:1,&quot;classes&quot;:24,&quot;downloads_monthly&quot;:2205651,&quot;downloads_total&quot;:67589115,&quot;files&quot;:24,&quot;forks&quot;:48,&quot;name&quot;:&quot;phpstan/phpstan-phpunit&quot;,&quot;stars&quot;:480,&quot;usage&quot;:6550},{&quot;bus_factor&quot;:1,&quot;classes&quot;:7,&quot;downloads_monthly&quot;:2355242,&quot;downloads_total&quot;:62115058,&quot;files&quot;:11,&quot;forks&quot;:27,&quot;name&quot;:&quot;phpstan/extension-installer&quot;,&quot;stars&quot;:446,&quot;usage&quot;:6404},{&quot;bus_factor&quot;:0,&quot;classes&quot;:0,&quot;downloads_monthly&quot;:1486528,&quot;downloads_total&quot;:75758927,&quot;files&quot;:0,&quot;forks&quot;:106,&quot;name&quot;:&quot;roave/security-advisories&quot;,&quot;stars&quot;:2756,&quot;usage&quot;:6377},{&quot;bus_factor&quot;:1,&quot;classes&quot;:71,&quot;downloads_monthly&quot;:6704982,&quot;downloads_total&quot;:368966055,&quot;files&quot;:89,&quot;forks&quot;:42,&quot;name&quot;:&quot;symfony/config&quot;,&quot;stars&quot;:4248,&quot;usage&quot;:6033},{&quot;bus_factor&quot;:1,&quot;classes&quot;:33,&quot;downloads_monthly&quot;:8979714,&quot;downloads_total&quot;:474766398,&quot;files&quot;:41,&quot;forks&quot;:638,&quot;name&quot;:&quot;vlucas/phpdotenv&quot;,&quot;stars&quot;:13265,&quot;usage&quot;:5597},{&quot;bus_factor&quot;:2,&quot;classes&quot;:20,&quot;downloads_monthly&quot;:14087178,&quot;downloads_total&quot;:785182390,&quot;files&quot;:22,&quot;forks&quot;:104,&quot;name&quot;:&quot;symfony/process&quot;,&quot;stars&quot;:7453,&quot;usage&quot;:5283},{&quot;bus_factor&quot;:0,&quot;classes&quot;:3,&quot;downloads_monthly&quot;:14537371,&quot;downloads_total&quot;:823272846,&quot;files&quot;:10,&quot;forks&quot;:186,&quot;name&quot;:&quot;psr/http-message&quot;,&quot;stars&quot;:6998,&quot;usage&quot;:5164},{&quot;bus_factor&quot;:1,&quot;classes&quot;:21,&quot;downloads_monthly&quot;:2008908,&quot;downloads_total&quot;:49058927,&quot;files&quot;:22,&quot;forks&quot;:19,&quot;name&quot;:&quot;phpstan/phpstan-deprecation-rules&quot;,&quot;stars&quot;:389,&quot;usage&quot;:4862},{&quot;bus_factor&quot;:1,&quot;classes&quot;:17,&quot;downloads_monthly&quot;:3369301,&quot;downloads_total&quot;:71591818,&quot;files&quot;:19,&quot;forks&quot;:147,&quot;name&quot;:&quot;spatie/laravel-package-tools&quot;,&quot;stars&quot;:828,&quot;usage&quot;:4850},{&quot;bus_factor&quot;:1,&quot;classes&quot;:6,&quot;downloads_monthly&quot;:1203781,&quot;downloads_total&quot;:18818296,&quot;files&quot;:19,&quot;forks&quot;:42,&quot;name&quot;:&quot;pestphp/pest-plugin-laravel&quot;,&quot;stars&quot;:191,&quot;usage&quot;:4724},{&quot;bus_factor&quot;:0,&quot;classes&quot;:1,&quot;downloads_monthly&quot;:11080,&quot;downloads_total&quot;:5004008,&quot;files&quot;:1,&quot;forks&quot;:101,&quot;name&quot;:&quot;satooshi/php-coveralls&quot;,&quot;stars&quot;:455,&quot;usage&quot;:4558},{&quot;bus_factor&quot;:0,&quot;classes&quot;:95,&quot;downloads_monthly&quot;:555231,&quot;downloads_total&quot;:36257490,&quot;files&quot;:104,&quot;forks&quot;:26,&quot;name&quot;:&quot;illuminate/console&quot;,&quot;stars&quot;:124,&quot;usage&quot;:4445},{&quot;bus_factor&quot;:0,&quot;classes&quot;:2,&quot;downloads_monthly&quot;:555231,&quot;downloads_total&quot;:36257490,&quot;files&quot;:2,&quot;forks&quot;:26,&quot;name&quot;:&quot;illuminate/console&quot;,&quot;stars&quot;:124,&quot;usage&quot;:4445},{&quot;bus_factor&quot;:1,&quot;classes&quot;:26,&quot;downloads_monthly&quot;:2876824,&quot;downloads_total&quot;:163123088,&quot;files&quot;:24,&quot;forks&quot;:47,&quot;name&quot;:&quot;symfony/phpunit-bridge&quot;,&quot;stars&quot;:2460,&quot;usage&quot;:4237},{&quot;bus_factor&quot;:0,&quot;classes&quot;:0,&quot;downloads_monthly&quot;:433617,&quot;downloads_total&quot;:29675737,&quot;files&quot;:0,&quot;forks&quot;:37,&quot;name&quot;:&quot;illuminate/http&quot;,&quot;stars&quot;:114,&quot;usage&quot;:3962},{&quot;bus_factor&quot;:2,&quot;classes&quot;:23,&quot;downloads_monthly&quot;:13973488,&quot;downloads_total&quot;:807162519,&quot;files&quot;:23,&quot;forks&quot;:55,&quot;name&quot;:&quot;symfony/finder&quot;,&quot;stars&quot;:8436,&quot;usage&quot;:3936},{&quot;bus_factor&quot;:1,&quot;classes&quot;:235,&quot;downloads_monthly&quot;:264604,&quot;downloads_total&quot;:33391615,&quot;files&quot;:282,&quot;forks&quot;:280,&quot;name&quot;:&quot;phpspec/phpspec&quot;,&quot;stars&quot;:1888,&quot;usage&quot;:3851},{&quot;bus_factor&quot;:0,&quot;classes&quot;:66,&quot;downloads_monthly&quot;:3096800,&quot;downloads_total&quot;:198345829,&quot;files&quot;:67,&quot;forks&quot;:454,&quot;name&quot;:&quot;doctrine/doctrine-bundle&quot;,&quot;stars&quot;:4736,&quot;usage&quot;:3594},{&quot;bus_factor&quot;:0,&quot;classes&quot;:0,&quot;downloads_monthly&quot;:3096800,&quot;downloads_total&quot;:198345829,&quot;files&quot;:0,&quot;forks&quot;:454,&quot;name&quot;:&quot;doctrine/doctrine-bundle&quot;,&quot;stars&quot;:4736,&quot;usage&quot;:3594},{&quot;bus_factor&quot;:1,&quot;classes&quot;:2,&quot;downloads_monthly&quot;:15042293,&quot;downloads_total&quot;:803012501,&quot;files&quot;:3,&quot;forks&quot;:51,&quot;name&quot;:&quot;psr/container&quot;,&quot;stars&quot;:9981,&quot;usage&quot;:3581},{&quot;bus_factor&quot;:1,&quot;classes&quot;:90,&quot;downloads_monthly&quot;:10973720,&quot;downloads_total&quot;:545844010,&quot;files&quot;:114,&quot;forks&quot;:502,&quot;name&quot;:&quot;ramsey/uuid&quot;,&quot;stars&quot;:12515,&quot;usage&quot;:3372},{&quot;bus_factor&quot;:1,&quot;classes&quot;:44,&quot;downloads_monthly&quot;:1084250,&quot;downloads_total&quot;:39273399,&quot;files&quot;:44,&quot;forks&quot;:49,&quot;name&quot;:&quot;phpstan/phpstan-strict-rules&quot;,&quot;stars&quot;:632,&quot;usage&quot;:3263},{&quot;bus_factor&quot;:1,&quot;classes&quot;:23,&quot;downloads_monthly&quot;:13306972,&quot;downloads_total&quot;:801654417,&quot;files&quot;:30,&quot;forks&quot;:67,&quot;name&quot;:&quot;symfony/event-dispatcher&quot;,&quot;stars&quot;:8534,&quot;usage&quot;:3263},{&quot;bus_factor&quot;:2,&quot;classes&quot;:6,&quot;downloads_monthly&quot;:9249313,&quot;downloads_total&quot;:540730584,&quot;files&quot;:8,&quot;forks&quot;:50,&quot;name&quot;:&quot;symfony/filesystem&quot;,&quot;stars&quot;:4621,&quot;usage&quot;:3125},{&quot;bus_factor&quot;:0,&quot;classes&quot;:44,&quot;downloads_monthly&quot;:1521225,&quot;downloads_total&quot;:85396918,&quot;files&quot;:53,&quot;forks&quot;:102,&quot;name&quot;:&quot;mikey179/vfsstream&quot;,&quot;stars&quot;:1423,&quot;usage&quot;:3010},{&quot;bus_factor&quot;:0,&quot;classes&quot;:33,&quot;downloads_monthly&quot;:14212004,&quot;downloads_total&quot;:814214923,&quot;files&quot;:35,&quot;forks&quot;:295,&quot;name&quot;:&quot;guzzlehttp/psr7&quot;,&quot;stars&quot;:7905,&quot;usage&quot;:2970},{&quot;bus_factor&quot;:1,&quot;classes&quot;:31,&quot;downloads_monthly&quot;:14212004,&quot;downloads_total&quot;:814214923,&quot;files&quot;:32,&quot;forks&quot;:295,&quot;name&quot;:&quot;guzzlehttp/psr7&quot;,&quot;stars&quot;:7905,&quot;usage&quot;:2970},{&quot;bus_factor&quot;:0,&quot;classes&quot;:249,&quot;downloads_monthly&quot;:2511894,&quot;downloads_total&quot;:154249705,&quot;files&quot;:280,&quot;forks&quot;:4596,&quot;name&quot;:&quot;composer/composer&quot;,&quot;stars&quot;:28836,&quot;usage&quot;:2847},{&quot;bus_factor&quot;:0,&quot;classes&quot;:0,&quot;downloads_monthly&quot;:2511894,&quot;downloads_total&quot;:154249705,&quot;files&quot;:0,&quot;forks&quot;:4596,&quot;name&quot;:&quot;composer/composer&quot;,&quot;stars&quot;:28836,&quot;usage&quot;:2847},{&quot;bus_factor&quot;:0,&quot;classes&quot;:0,&quot;downloads_monthly&quot;:6330192,&quot;downloads_total&quot;:232554721,&quot;files&quot;:0,&quot;forks&quot;:50,&quot;name&quot;:&quot;symfony/http-client&quot;,&quot;stars&quot;:1979,&quot;usage&quot;:2631},{&quot;bus_factor&quot;:1,&quot;classes&quot;:44,&quot;downloads_monthly&quot;:11103163,&quot;downloads_total&quot;:605685717,&quot;files&quot;:50,&quot;forks&quot;:46,&quot;name&quot;:&quot;symfony/css-selector&quot;,&quot;stars&quot;:7440,&quot;usage&quot;:2628},{&quot;bus_factor&quot;:2,&quot;classes&quot;:13,&quot;downloads_monthly&quot;:4267067,&quot;downloads_total&quot;:226331863,&quot;files&quot;:14,&quot;forks&quot;:59,&quot;name&quot;:&quot;symfony/browser-kit&quot;,&quot;stars&quot;:2977,&quot;usage&quot;:2568},{&quot;bus_factor&quot;:0,&quot;classes&quot;:307,&quot;downloads_monthly&quot;:2814721,&quot;downloads_total&quot;:153068840,&quot;files&quot;:331,&quot;forks&quot;:1495,&quot;name&quot;:&quot;intervention/image&quot;,&quot;stars&quot;:14082,&quot;usage&quot;:2445},{&quot;bus_factor&quot;:1,&quot;classes&quot;:295,&quot;downloads_monthly&quot;:2814721,&quot;downloads_total&quot;:153068840,&quot;files&quot;:331,&quot;forks&quot;:1495,&quot;name&quot;:&quot;intervention/image&quot;,&quot;stars&quot;:14082,&quot;usage&quot;:2445},{&quot;bus_factor&quot;:0,&quot;classes&quot;:11,&quot;downloads_monthly&quot;:915936,&quot;downloads_total&quot;:49510052,&quot;files&quot;:12,&quot;forks&quot;:24,&quot;name&quot;:&quot;illuminate/filesystem&quot;,&quot;stars&quot;:149,&quot;usage&quot;:2439},{&quot;bus_factor&quot;:0,&quot;classes&quot;:0,&quot;downloads_monthly&quot;:915936,&quot;downloads_total&quot;:49510052,&quot;files&quot;:0,&quot;forks&quot;:24,&quot;name&quot;:&quot;illuminate/filesystem&quot;,&quot;stars&quot;:149,&quot;usage&quot;:2439},{&quot;bus_factor&quot;:2,&quot;classes&quot;:21,&quot;downloads_monthly&quot;:5434878,&quot;downloads_total&quot;:309910053,&quot;files&quot;:23,&quot;forks&quot;:125,&quot;name&quot;:&quot;symfony/dom-crawler&quot;,&quot;stars&quot;:3986,&quot;usage&quot;:2412},{&quot;bus_factor&quot;:0,&quot;classes&quot;:18,&quot;downloads_monthly&quot;:514788,&quot;downloads_total&quot;:35489214,&quot;files&quot;:20,&quot;forks&quot;:11,&quot;name&quot;:&quot;illuminate/config&quot;,&quot;stars&quot;:106,&quot;usage&quot;:2330},{&quot;bus_factor&quot;:1,&quot;classes&quot;:27,&quot;downloads_monthly&quot;:514788,&quot;downloads_total&quot;:35489214,&quot;files&quot;:31,&quot;forks&quot;:11,&quot;name&quot;:&quot;illuminate/config&quot;,&quot;stars&quot;:106,&quot;usage&quot;:2330},{&quot;bus_factor&quot;:0,&quot;classes&quot;:0,&quot;downloads_monthly&quot;:1448220,&quot;downloads_total&quot;:16467245,&quot;files&quot;:0,&quot;forks&quot;:19,&quot;name&quot;:&quot;pestphp/pest-plugin-arch&quot;,&quot;stars&quot;:33,&quot;usage&quot;:2328},{&quot;bus_factor&quot;:2,&quot;classes&quot;:6,&quot;downloads_monthly&quot;:4248293,&quot;downloads_total&quot;:173669521,&quot;files&quot;:7,&quot;forks&quot;:29,&quot;name&quot;:&quot;symfony/dotenv&quot;,&quot;stars&quot;:3766,&quot;usage&quot;:2316},{&quot;bus_factor&quot;:1,&quot;classes&quot;:117,&quot;downloads_monthly&quot;:41564,&quot;downloads_total&quot;:3593448,&quot;files&quot;:118,&quot;forks&quot;:12,&quot;name&quot;:&quot;spryker/code-sniffer&quot;,&quot;stars&quot;:36,&quot;usage&quot;:2310},{&quot;bus_factor&quot;:2,&quot;classes&quot;:37,&quot;downloads_monthly&quot;:3347591,&quot;downloads_total&quot;:148387034,&quot;files&quot;:45,&quot;forks&quot;:24,&quot;name&quot;:&quot;symfony/twig-bundle&quot;,&quot;stars&quot;:2494,&quot;usage&quot;:2303},{&quot;bus_factor&quot;:1,&quot;classes&quot;:42,&quot;downloads_monthly&quot;:632848,&quot;downloads_total&quot;:27234750,&quot;files&quot;:46,&quot;forks&quot;:62,&quot;name&quot;:&quot;spatie/laravel-ray&quot;,&quot;stars&quot;:295,&quot;usage&quot;:2301},{&quot;bus_factor&quot;:2,&quot;classes&quot;:159,&quot;downloads_monthly&quot;:9684327,&quot;downloads_total&quot;:503869950,&quot;files&quot;:182,&quot;forks&quot;:833,&quot;name&quot;:&quot;league/flysystem&quot;,&quot;stars&quot;:13410,&quot;usage&quot;:2226},{&quot;bus_factor&quot;:2,&quot;classes&quot;:80,&quot;downloads_monthly&quot;:6484555,&quot;downloads_total&quot;:264779554,&quot;files&quot;:72,&quot;forks&quot;:60,&quot;name&quot;:&quot;symfony/cache&quot;,&quot;stars&quot;:4125,&quot;usage&quot;:2217},{&quot;bus_factor&quot;:0,&quot;classes&quot;:404,&quot;downloads_monthly&quot;:1188011,&quot;downloads_total&quot;:79495397,&quot;files&quot;:523,&quot;forks&quot;:612,&quot;name&quot;:&quot;behat/behat&quot;,&quot;stars&quot;:3919,&quot;usage&quot;:2173},{&quot;bus_factor&quot;:1,&quot;classes&quot;:412,&quot;downloads_monthly&quot;:1188011,&quot;downloads_total&quot;:79495397,&quot;files&quot;:531,&quot;forks&quot;:612,&quot;name&quot;:&quot;behat/behat&quot;,&quot;stars&quot;:3919,&quot;usage&quot;:2173},{&quot;bus_factor&quot;:1,&quot;classes&quot;:16,&quot;downloads_monthly&quot;:4978944,&quot;downloads_total&quot;:228690965,&quot;files&quot;:20,&quot;forks&quot;:24,&quot;name&quot;:&quot;symfony/property-access&quot;,&quot;stars&quot;:2803,&quot;usage&quot;:2159},{&quot;bus_factor&quot;:0,&quot;classes&quot;:16,&quot;downloads_monthly&quot;:1090474,&quot;downloads_total&quot;:63457043,&quot;files&quot;:16,&quot;forks&quot;:60,&quot;name&quot;:&quot;illuminate/container&quot;,&quot;stars&quot;:309,&quot;usage&quot;:2136},{&quot;bus_factor&quot;:1,&quot;classes&quot;:131,&quot;downloads_monthly&quot;:1623976,&quot;downloads_total&quot;:15520324,&quot;files&quot;:132,&quot;forks&quot;:439,&quot;name&quot;:&quot;larastan/larastan&quot;,&quot;stars&quot;:5756,&quot;usage&quot;:2097},{&quot;bus_factor&quot;:1,&quot;classes&quot;:12,&quot;downloads_monthly&quot;:7741281,&quot;downloads_total&quot;:341396244,&quot;files&quot;:14,&quot;forks&quot;:1419,&quot;name&quot;:&quot;firebase/php-jwt&quot;,&quot;stars&quot;:9526,&quot;usage&quot;:2006},{&quot;bus_factor&quot;:2,&quot;classes&quot;:77,&quot;downloads_monthly&quot;:11857545,&quot;downloads_total&quot;:673133390,&quot;files&quot;:95,&quot;forks&quot;:88,&quot;name&quot;:&quot;symfony/translation&quot;,&quot;stars&quot;:6620,&quot;usage&quot;:1954},{&quot;bus_factor&quot;:0,&quot;classes&quot;:10,&quot;downloads_monthly&quot;:7438510,&quot;downloads_total&quot;:322431601,&quot;files&quot;:24,&quot;forks&quot;:128,&quot;name&quot;:&quot;laravel/tinker&quot;,&quot;stars&quot;:7363,&quot;usage&quot;:1953},{&quot;bus_factor&quot;:0,&quot;classes&quot;:71,&quot;downloads_monthly&quot;:130808,&quot;downloads_total&quot;:6916798,&quot;files&quot;:99,&quot;forks&quot;:81,&quot;name&quot;:&quot;illuminate/routing&quot;,&quot;stars&quot;:118,&quot;usage&quot;:1905},{&quot;bus_factor&quot;:1,&quot;classes&quot;:9,&quot;downloads_monthly&quot;:3369607,&quot;downloads_total&quot;:204926356,&quot;files&quot;:10,&quot;forks&quot;:234,&quot;name&quot;:&quot;symfony/monolog-bundle&quot;,&quot;stars&quot;:2902,&quot;usage&quot;:1891},{&quot;bus_factor&quot;:0,&quot;classes&quot;:5,&quot;downloads_monthly&quot;:707414,&quot;downloads_total&quot;:44463068,&quot;files&quot;:6,&quot;forks&quot;:10,&quot;name&quot;:&quot;illuminate/events&quot;,&quot;stars&quot;:131,&quot;usage&quot;:1800},{&quot;bus_factor&quot;:2,&quot;classes&quot;:42,&quot;downloads_monthly&quot;:707414,&quot;downloads_total&quot;:44463068,&quot;files&quot;:43,&quot;forks&quot;:10,&quot;name&quot;:&quot;illuminate/events&quot;,&quot;stars&quot;:131,&quot;usage&quot;:1800},{&quot;bus_factor&quot;:0,&quot;classes&quot;:1,&quot;downloads_monthly&quot;:3351890,&quot;downloads_total&quot;:116579038,&quot;files&quot;:1,&quot;forks&quot;:37,&quot;name&quot;:&quot;dealerdirect/phpcodesniffer-composer-installer&quot;,&quot;stars&quot;:573,&quot;usage&quot;:1790},{&quot;bus_factor&quot;:0,&quot;classes&quot;:1,&quot;downloads_monthly&quot;:3351890,&quot;downloads_total&quot;:116579038,&quot;files&quot;:1,&quot;forks&quot;:37,&quot;name&quot;:&quot;dealerdirect/phpcodesniffer-composer-installer&quot;,&quot;stars&quot;:573,&quot;usage&quot;:1790},{&quot;bus_factor&quot;:0,&quot;classes&quot;:18,&quot;downloads_monthly&quot;:7032322,&quot;downloads_total&quot;:308147236,&quot;files&quot;:36,&quot;forks&quot;:602,&quot;name&quot;:&quot;filp/whoops&quot;,&quot;stars&quot;:13218,&quot;usage&quot;:1778},{&quot;bus_factor&quot;:1,&quot;classes&quot;:18,&quot;downloads_monthly&quot;:7032322,&quot;downloads_total&quot;:308147236,&quot;files&quot;:36,&quot;forks&quot;:602,&quot;name&quot;:&quot;filp/whoops&quot;,&quot;stars&quot;:13218,&quot;usage&quot;:1778},{&quot;bus_factor&quot;:1,&quot;classes&quot;:29,&quot;downloads_monthly&quot;:98564,&quot;downloads_total&quot;:5796328,&quot;files&quot;:30,&quot;forks&quot;:71,&quot;name&quot;:&quot;nette/tester&quot;,&quot;stars&quot;:465,&quot;usage&quot;:1758},{&quot;bus_factor&quot;:0,&quot;classes&quot;:28,&quot;downloads_monthly&quot;:1285391,&quot;downloads_total&quot;:160827164,&quot;files&quot;:30,&quot;forks&quot;:281,&quot;name&quot;:&quot;sensio/framework-extra-bundle&quot;,&quot;stars&quot;:3378,&quot;usage&quot;:1729},{&quot;bus_factor&quot;:0,&quot;classes&quot;:4,&quot;downloads_monthly&quot;:15970,&quot;downloads_total&quot;:1487192,&quot;files&quot;:4,&quot;forks&quot;:24,&quot;name&quot;:&quot;scrutinizer/ocular&quot;,&quot;stars&quot;:41,&quot;usage&quot;:1712},{&quot;bus_factor&quot;:1,&quot;classes&quot;:258,&quot;downloads_monthly&quot;:13603450,&quot;downloads_total&quot;:713118750,&quot;files&quot;:271,&quot;forks&quot;:1097,&quot;name&quot;:&quot;nikic/php-parser&quot;,&quot;stars&quot;:17179,&quot;usage&quot;:1678},{&quot;bus_factor&quot;:0,&quot;classes&quot;:8,&quot;downloads_monthly&quot;:1133281,&quot;downloads_total&quot;:114941493,&quot;files&quot;:9,&quot;forks&quot;:305,&quot;name&quot;:&quot;pimple/pimple&quot;,&quot;stars&quot;:2647,&quot;usage&quot;:1656},{&quot;bus_factor&quot;:0,&quot;classes&quot;:0,&quot;downloads_monthly&quot;:13335711,&quot;downloads_total&quot;:500430904,&quot;files&quot;:4,&quot;forks&quot;:17,&quot;name&quot;:&quot;psr/http-client&quot;,&quot;stars&quot;:1685,&quot;usage&quot;:1647},{&quot;bus_factor&quot;:1,&quot;classes&quot;:17,&quot;downloads_monthly&quot;:13615876,&quot;downloads_total&quot;:503416561,&quot;files&quot;:26,&quot;forks&quot;:24,&quot;name&quot;:&quot;psr/http-factory&quot;,&quot;stars&quot;:1827,&quot;usage&quot;:1627},{&quot;bus_factor&quot;:2,&quot;classes&quot;:21,&quot;downloads_monthly&quot;:8107720,&quot;downloads_total&quot;:382922850,&quot;files&quot;:23,&quot;forks&quot;:32,&quot;name&quot;:&quot;symfony/options-resolver&quot;,&quot;stars&quot;:3222,&quot;usage&quot;:1627},{&quot;bus_factor&quot;:0,&quot;classes&quot;:21,&quot;downloads_monthly&quot;:2414,&quot;downloads_total&quot;:1672829,&quot;files&quot;:24,&quot;forks&quot;:32,&quot;name&quot;:&quot;codeclimate/php-test-reporter&quot;,&quot;stars&quot;:65,&quot;usage&quot;:1610},{&quot;bus_factor&quot;:0,&quot;classes&quot;:19,&quot;downloads_monthly&quot;:2414,&quot;downloads_total&quot;:1672829,&quot;files&quot;:21,&quot;forks&quot;:32,&quot;name&quot;:&quot;codeclimate/php-test-reporter&quot;,&quot;stars&quot;:65,&quot;usage&quot;:1610},{&quot;bus_factor&quot;:2,&quot;classes&quot;:61,&quot;downloads_monthly&quot;:11769804,&quot;downloads_total&quot;:632059829,&quot;files&quot;:71,&quot;forks&quot;:96,&quot;name&quot;:&quot;symfony/routing&quot;,&quot;stars&quot;:7623,&quot;usage&quot;:1585},{&quot;bus_factor&quot;:0,&quot;classes&quot;:51,&quot;downloads_monthly&quot;:593887,&quot;downloads_total&quot;:36699879,&quot;files&quot;:52,&quot;forks&quot;:49,&quot;name&quot;:&quot;illuminate/view&quot;,&quot;stars&quot;:127,&quot;usage&quot;:1578},{&quot;bus_factor&quot;:0,&quot;classes&quot;:3,&quot;downloads_monthly&quot;:2465915,&quot;downloads_total&quot;:88671724,&quot;files&quot;:3,&quot;forks&quot;:183,&quot;name&quot;:&quot;slevomat/coding-standard&quot;,&quot;stars&quot;:1419,&quot;usage&quot;:1578},{&quot;bus_factor&quot;:1,&quot;classes&quot;:54,&quot;downloads_monthly&quot;:718261,&quot;downloads_total&quot;:40399116,&quot;files&quot;:72,&quot;forks&quot;:1946,&quot;name&quot;:&quot;slim/slim&quot;,&quot;stars&quot;:12052,&quot;usage&quot;:1508},{&quot;bus_factor&quot;:1,&quot;classes&quot;:13,&quot;downloads_monthly&quot;:2024222,&quot;downloads_total&quot;:58924446,&quot;files&quot;:19,&quot;forks&quot;:9,&quot;name&quot;:&quot;psr/http-server-middleware&quot;,&quot;stars&quot;:176,&quot;usage&quot;:1399},{&quot;bus_factor&quot;:1,&quot;classes&quot;:8,&quot;downloads_monthly&quot;:1165935,&quot;downloads_total&quot;:37641076,&quot;files&quot;:8,&quot;forks&quot;:40,&quot;name&quot;:&quot;phpspec/prophecy-phpunit&quot;,&quot;stars&quot;:178,&quot;usage&quot;:1393},{&quot;bus_factor&quot;:2,&quot;classes&quot;:23,&quot;downloads_monthly&quot;:3667097,&quot;downloads_total&quot;:159673353,&quot;files&quot;:25,&quot;forks&quot;:42,&quot;name&quot;:&quot;symfony/expression-language&quot;,&quot;stars&quot;:2814,&quot;usage&quot;:1385},{&quot;bus_factor&quot;:1,&quot;classes&quot;:21,&quot;downloads_monthly&quot;:241475,&quot;downloads_total&quot;:15686967,&quot;files&quot;:32,&quot;forks&quot;:244,&quot;name&quot;:&quot;omnipay/common&quot;,&quot;stars&quot;:333,&quot;usage&quot;:1259},{&quot;bus_factor&quot;:0,&quot;classes&quot;:2,&quot;downloads_monthly&quot;:1005223,&quot;downloads_total&quot;:37707351,&quot;files&quot;:2,&quot;forks&quot;:9,&quot;name&quot;:&quot;codeception/module-asserts&quot;,&quot;stars&quot;:80,&quot;usage&quot;:1254},{&quot;bus_factor&quot;:1,&quot;classes&quot;:7,&quot;downloads_monthly&quot;:1005223,&quot;downloads_total&quot;:37707351,&quot;files&quot;:7,&quot;forks&quot;:9,&quot;name&quot;:&quot;codeception/module-asserts&quot;,&quot;stars&quot;:80,&quot;usage&quot;:1254},{&quot;bus_factor&quot;:0,&quot;classes&quot;:6,&quot;downloads_monthly&quot;:639524,&quot;downloads_total&quot;:66384746,&quot;files&quot;:8,&quot;forks&quot;:749,&quot;name&quot;:&quot;laravelcollective/html&quot;,&quot;stars&quot;:3926,&quot;usage&quot;:1253},{&quot;bus_factor&quot;:1,&quot;classes&quot;:8,&quot;downloads_monthly&quot;:11178404,&quot;downloads_total&quot;:540483998,&quot;files&quot;:10,&quot;forks&quot;:45,&quot;name&quot;:&quot;psr/cache&quot;,&quot;stars&quot;:5138,&quot;usage&quot;:1250},{&quot;bus_factor&quot;:0,&quot;classes&quot;:0,&quot;downloads_monthly&quot;:24508,&quot;downloads_total&quot;:2621596,&quot;files&quot;:0,&quot;forks&quot;:7,&quot;name&quot;:&quot;spryker/kernel&quot;,&quot;stars&quot;:1,&quot;usage&quot;:1245},{&quot;bus_factor&quot;:0,&quot;classes&quot;:59,&quot;downloads_monthly&quot;:449205,&quot;downloads_total&quot;:30532825,&quot;files&quot;:82,&quot;forks&quot;:68,&quot;name&quot;:&quot;illuminate/validation&quot;,&quot;stars&quot;:181,&quot;usage&quot;:1219},{&quot;bus_factor&quot;:0,&quot;classes&quot;:11,&quot;downloads_monthly&quot;:7354190,&quot;downloads_total&quot;:519832384,&quot;files&quot;:20,&quot;forks&quot;:214,&quot;name&quot;:&quot;doctrine/cache&quot;,&quot;stars&quot;:7868,&quot;usage&quot;:1173},{&quot;bus_factor&quot;:0,&quot;classes&quot;:6,&quot;downloads_monthly&quot;:7354190,&quot;downloads_total&quot;:519832384,&quot;files&quot;:13,&quot;forks&quot;:214,&quot;name&quot;:&quot;doctrine/cache&quot;,&quot;stars&quot;:7868,&quot;usage&quot;:1173},{&quot;bus_factor&quot;:0,&quot;classes&quot;:15,&quot;downloads_monthly&quot;:20544,&quot;downloads_total&quot;:1216909,&quot;files&quot;:26,&quot;forks&quot;:9,&quot;name&quot;:&quot;contao/manager-plugin&quot;,&quot;stars&quot;:4,&quot;usage&quot;:1142},{&quot;bus_factor&quot;:1,&quot;classes&quot;:6,&quot;downloads_monthly&quot;:20544,&quot;downloads_total&quot;:1216909,&quot;files&quot;:12,&quot;forks&quot;:9,&quot;name&quot;:&quot;contao/manager-plugin&quot;,&quot;stars&quot;:4,&quot;usage&quot;:1142},{&quot;bus_factor&quot;:2,&quot;classes&quot;:3,&quot;downloads_monthly&quot;:12103984,&quot;downloads_total&quot;:738147462,&quot;files&quot;:5,&quot;forks&quot;:147,&quot;name&quot;:&quot;webmozart/assert&quot;,&quot;stars&quot;:7588,&quot;usage&quot;:1141}],&quot;risk_distribution&quot;:[{&quot;bus_factor_max&quot;:0.5,&quot;bus_factor_min&quot;:0,&quot;count&quot;:39,&quot;usage_max&quot;:5000,&quot;usage_min&quot;:1000},{&quot;bus_factor_max&quot;:1.5,&quot;bus_factor_min&quot;:1,&quot;count&quot;:31,&quot;usage_max&quot;:5000,&quot;usage_min&quot;:1000},{&quot;bus_factor_max&quot;:2.5,&quot;bus_factor_min&quot;:2,&quot;count&quot;:14,&quot;usage_max&quot;:5000,&quot;usage_min&quot;:1000}],&quot;shadow_packages&quot;:288519,&quot;top_dependent_packages&quot;:[{&quot;dep_count&quot;:7576,&quot;dependents&quot;:0,&quot;downloads_monthly&quot;:0,&quot;forks&quot;:0,&quot;name&quot;:&quot;jungleran/drupal-them-all&quot;,&quot;stars&quot;:1},{&quot;dep_count&quot;:2872,&quot;dependents&quot;:0,&quot;downloads_monthly&quot;:0,&quot;forks&quot;:1,&quot;name&quot;:&quot;dynamitechetan/lumenoctane&quot;,&quot;stars&quot;:0},{&quot;dep_count&quot;:2676,&quot;dependents&quot;:0,&quot;downloads_monthly&quot;:7,&quot;forks&quot;:0,&quot;name&quot;:&quot;codehubcare/moderyat&quot;,&quot;stars&quot;:1},{&quot;dep_count&quot;:2670,&quot;dependents&quot;:0,&quot;downloads_monthly&quot;:0,&quot;forks&quot;:0,&quot;name&quot;:&quot;altwaireb/filament-title-and-slug&quot;,&quot;stars&quot;:0},{&quot;dep_count&quot;:2606,&quot;dependents&quot;:0,&quot;downloads_monthly&quot;:0,&quot;forks&quot;:0,&quot;name&quot;:&quot;amohamed/offline-cashier&quot;,&quot;stars&quot;:6},{&quot;dep_count&quot;:2498,&quot;dependents&quot;:0,&quot;downloads_monthly&quot;:0,&quot;forks&quot;:0,&quot;name&quot;:&quot;chengz0109/demo&quot;,&quot;stars&quot;:0},{&quot;dep_count&quot;:2072,&quot;dependents&quot;:0,&quot;downloads_monthly&quot;:8,&quot;forks&quot;:0,&quot;name&quot;:&quot;codehubcare/laravel-deployer&quot;,&quot;stars&quot;:0},{&quot;dep_count&quot;:1982,&quot;dependents&quot;:0,&quot;downloads_monthly&quot;:0,&quot;forks&quot;:0,&quot;name&quot;:&quot;bambolee-digital/event-user-manager&quot;,&quot;stars&quot;:0},{&quot;dep_count&quot;:1974,&quot;dependents&quot;:0,&quot;downloads_monthly&quot;:0,&quot;forks&quot;:0,&quot;name&quot;:&quot;chep6915/test&quot;,&quot;stars&quot;:0},{&quot;dep_count&quot;:1950,&quot;dependents&quot;:0,&quot;downloads_monthly&quot;:0,&quot;forks&quot;:0,&quot;name&quot;:&quot;eutranet/laravel-init&quot;,&quot;stars&quot;:0},{&quot;dep_count&quot;:1804,&quot;dependents&quot;:0,&quot;downloads_monthly&quot;:0,&quot;forks&quot;:0,&quot;name&quot;:&quot;amohamed/autoportserve&quot;,&quot;stars&quot;:0},{&quot;dep_count&quot;:1798,&quot;dependents&quot;:0,&quot;downloads_monthly&quot;:3,&quot;forks&quot;:0,&quot;name&quot;:&quot;amirgaber2491/jt-express&quot;,&quot;stars&quot;:0},{&quot;dep_count&quot;:1744,&quot;dependents&quot;:0,&quot;downloads_monthly&quot;:0,&quot;forks&quot;:0,&quot;name&quot;:&quot;althinect/laravel-sendlk&quot;,&quot;stars&quot;:0},{&quot;dep_count&quot;:1740,&quot;dependents&quot;:0,&quot;downloads_monthly&quot;:0,&quot;forks&quot;:0,&quot;name&quot;:&quot;digitcode/digitcodeflazz&quot;,&quot;stars&quot;:0},{&quot;dep_count&quot;:1738,&quot;dependents&quot;:0,&quot;downloads_monthly&quot;:0,&quot;forks&quot;:0,&quot;name&quot;:&quot;bregananta/nicepaysnap&quot;,&quot;stars&quot;:0},{&quot;dep_count&quot;:1690,&quot;dependents&quot;:0,&quot;downloads_monthly&quot;:1,&quot;forks&quot;:0,&quot;name&quot;:&quot;azhar416/composer-test&quot;,&quot;stars&quot;:0},{&quot;dep_count&quot;:1690,&quot;dependents&quot;:0,&quot;downloads_monthly&quot;:0,&quot;forks&quot;:0,&quot;name&quot;:&quot;azdhebar/validate-ean&quot;,&quot;stars&quot;:0},{&quot;dep_count&quot;:1684,&quot;dependents&quot;:0,&quot;downloads_monthly&quot;:0,&quot;forks&quot;:0,&quot;name&quot;:&quot;avanahuda/avanatest&quot;,&quot;stars&quot;:0},{&quot;dep_count&quot;:1680,&quot;dependents&quot;:0,&quot;downloads_monthly&quot;:0,&quot;forks&quot;:1,&quot;name&quot;:&quot;amohamed/laravelmodelmaker&quot;,&quot;stars&quot;:4},{&quot;dep_count&quot;:1666,&quot;dependents&quot;:0,&quot;downloads_monthly&quot;:0,&quot;forks&quot;:0,&quot;name&quot;:&quot;alonso/miratio&quot;,&quot;stars&quot;:0}],&quot;top_dev_packages&quot;:[{&quot;count&quot;:&quot;201547&quot;,&quot;name&quot;:&quot;phpunit/phpunit&quot;},{&quot;count&quot;:&quot;33917&quot;,&quot;name&quot;:&quot;orchestra/testbench&quot;},{&quot;count&quot;:&quot;33796&quot;,&quot;name&quot;:&quot;squizlabs/php_codesniffer&quot;},{&quot;count&quot;:&quot;32283&quot;,&quot;name&quot;:&quot;phpstan/phpstan&quot;},{&quot;count&quot;:&quot;31787&quot;,&quot;name&quot;:&quot;mockery/mockery&quot;},{&quot;count&quot;:&quot;24353&quot;,&quot;name&quot;:&quot;friendsofphp/php-cs-fixer&quot;},{&quot;count&quot;:&quot;11541&quot;,&quot;name&quot;:&quot;pestphp/pest&quot;},{&quot;count&quot;:&quot;10510&quot;,&quot;name&quot;:&quot;vimeo/psalm&quot;},{&quot;count&quot;:&quot;9693&quot;,&quot;name&quot;:&quot;phpstan/phpstan-phpunit&quot;},{&quot;count&quot;:&quot;9592&quot;,&quot;name&quot;:&quot;laravel/pint&quot;},{&quot;count&quot;:&quot;9048&quot;,&quot;name&quot;:&quot;phpstan/extension-installer&quot;},{&quot;count&quot;:&quot;8875&quot;,&quot;name&quot;:&quot;symfony/var-dumper&quot;},{&quot;count&quot;:&quot;8386&quot;,&quot;name&quot;:&quot;nunomaduro/collision&quot;},{&quot;count&quot;:&quot;8239&quot;,&quot;name&quot;:&quot;roave/security-advisories&quot;},{&quot;count&quot;:&quot;6675&quot;,&quot;name&quot;:&quot;phpmd/phpmd&quot;},{&quot;count&quot;:&quot;6487&quot;,&quot;name&quot;:&quot;symfony/phpunit-bridge&quot;},{&quot;count&quot;:&quot;6356&quot;,&quot;name&quot;:&quot;phpstan/phpstan-deprecation-rules&quot;},{&quot;count&quot;:&quot;6227&quot;,&quot;name&quot;:&quot;pestphp/pest-plugin-laravel&quot;},{&quot;count&quot;:&quot;6046&quot;,&quot;name&quot;:&quot;satooshi/php-coveralls&quot;},{&quot;count&quot;:&quot;5794&quot;,&quot;name&quot;:&quot;php-coveralls/php-coveralls&quot;}],&quot;top_loc_packages&quot;:[{&quot;classes&quot;:363,&quot;cloc&quot;:949879,&quot;downloads_total&quot;:19,&quot;files&quot;:11353,&quot;lloc&quot;:1516517,&quot;loc&quot;:2843287,&quot;name&quot;:&quot;xnrcms/alipaysdk&quot;,&quot;stars&quot;:0},{&quot;classes&quot;:7,&quot;cloc&quot;:610,&quot;downloads_total&quot;:36,&quot;files&quot;:48,&quot;lloc&quot;:2198770,&quot;loc&quot;:2199718,&quot;name&quot;:&quot;zerossb/laravel-world&quot;,&quot;stars&quot;:0},{&quot;classes&quot;:10,&quot;cloc&quot;:1099995,&quot;downloads_total&quot;:124,&quot;files&quot;:19224,&quot;lloc&quot;:646364,&quot;loc&quot;:1948506,&quot;name&quot;:&quot;tencentcloud/tencentcloud-sdk-php-intl-en&quot;,&quot;stars&quot;:3},{&quot;classes&quot;:213,&quot;cloc&quot;:880671,&quot;downloads_total&quot;:253,&quot;files&quot;:15459,&quot;lloc&quot;:520034,&quot;loc&quot;:1563294,&quot;name&quot;:&quot;vccmas/tencentcloud-sdk-php-intl-en&quot;,&quot;stars&quot;:0},{&quot;classes&quot;:0,&quot;cloc&quot;:428732,&quot;downloads_total&quot;:13388,&quot;files&quot;:15683,&quot;lloc&quot;:825904,&quot;loc&quot;:1517347,&quot;name&quot;:&quot;zohocrm/php-sdk-6.0&quot;,&quot;stars&quot;:1},{&quot;classes&quot;:56,&quot;cloc&quot;:626946,&quot;downloads_total&quot;:1628,&quot;files&quot;:1062,&quot;lloc&quot;:607385,&quot;loc&quot;:1452841,&quot;name&quot;:&quot;udemy/googleads-php-lib&quot;,&quot;stars&quot;:0},{&quot;classes&quot;:41,&quot;cloc&quot;:554129,&quot;downloads_total&quot;:369,&quot;files&quot;:3136,&quot;lloc&quot;:592562,&quot;loc&quot;:1301637,&quot;name&quot;:&quot;volcengine/volcengine-php-sdk&quot;,&quot;stars&quot;:5},{&quot;classes&quot;:4556,&quot;cloc&quot;:149368,&quot;downloads_total&quot;:20,&quot;files&quot;:4352,&quot;lloc&quot;:962175,&quot;loc&quot;:1264440,&quot;name&quot;:&quot;vavajke/bxcore&quot;,&quot;stars&quot;:0},{&quot;classes&quot;:6346,&quot;cloc&quot;:427719,&quot;downloads_total&quot;:36,&quot;files&quot;:8671,&quot;lloc&quot;:655997,&quot;loc&quot;:1232142,&quot;name&quot;:&quot;sos-solution/other-framework&quot;,&quot;stars&quot;:0},{&quot;classes&quot;:1,&quot;cloc&quot;:56932,&quot;downloads_total&quot;:2,&quot;files&quot;:4164,&quot;lloc&quot;:1146878,&quot;loc&quot;:1221602,&quot;name&quot;:&quot;tombeachell/forza-magento&quot;,&quot;stars&quot;:0}],&quot;top_monthly_downloads&quot;:[{&quot;dependents&quot;:354,&quot;downloads_monthly&quot;:15689866,&quot;favers&quot;:2058,&quot;forks&quot;:8,&quot;name&quot;:&quot;symfony/deprecation-contracts&quot;,&quot;stars&quot;:2053},{&quot;dependents&quot;:462,&quot;downloads_monthly&quot;:15528737,&quot;favers&quot;:7862,&quot;forks&quot;:38,&quot;name&quot;:&quot;symfony/polyfill-mbstring&quot;,&quot;stars&quot;:7847},{&quot;dependents&quot;:8736,&quot;downloads_monthly&quot;:15188541,&quot;favers&quot;:10599,&quot;forks&quot;:182,&quot;name&quot;:&quot;psr/log&quot;,&quot;stars&quot;:10421},{&quot;dependents&quot;:3581,&quot;downloads_monthly&quot;:15042293,&quot;favers&quot;:10014,&quot;forks&quot;:51,&quot;name&quot;:&quot;psr/container&quot;,&quot;stars&quot;:9981},{&quot;dependents&quot;:11415,&quot;downloads_monthly&quot;:14851715,&quot;favers&quot;:9958,&quot;forks&quot;:259,&quot;name&quot;:&quot;symfony/console&quot;,&quot;stars&quot;:9785},{&quot;dependents&quot;:209,&quot;downloads_monthly&quot;:14653286,&quot;favers&quot;:2596,&quot;forks&quot;:9,&quot;name&quot;:&quot;symfony/service-contracts&quot;,&quot;stars&quot;:2595},{&quot;dependents&quot;:28,&quot;downloads_monthly&quot;:14595392,&quot;favers&quot;:2027,&quot;forks&quot;:4,&quot;name&quot;:&quot;symfony/polyfill-intl-normalizer&quot;,&quot;stars&quot;:2026},{&quot;dependents&quot;:5164,&quot;downloads_monthly&quot;:14537371,&quot;favers&quot;:7079,&quot;forks&quot;:186,&quot;name&quot;:&quot;psr/http-message&quot;,&quot;stars&quot;:6998},{&quot;dependents&quot;:2970,&quot;downloads_monthly&quot;:14212004,&quot;favers&quot;:7964,&quot;forks&quot;:295,&quot;name&quot;:&quot;guzzlehttp/psr7&quot;,&quot;stars&quot;:7905},{&quot;dependents&quot;:461,&quot;downloads_monthly&quot;:14089514,&quot;favers&quot;:7678,&quot;forks&quot;:117,&quot;name&quot;:&quot;guzzlehttp/promises&quot;,&quot;stars&quot;:7645},{&quot;dependents&quot;:5283,&quot;downloads_monthly&quot;:14087178,&quot;favers&quot;:7511,&quot;forks&quot;:104,&quot;name&quot;:&quot;symfony/process&quot;,&quot;stars&quot;:7453},{&quot;dependents&quot;:111,&quot;downloads_monthly&quot;:14082419,&quot;favers&quot;:4059,&quot;forks&quot;:7,&quot;name&quot;:&quot;symfony/polyfill-ctype&quot;,&quot;stars&quot;:4056},{&quot;dependents&quot;:3936,&quot;downloads_monthly&quot;:13973488,&quot;favers&quot;:8507,&quot;forks&quot;:55,&quot;name&quot;:&quot;symfony/finder&quot;,&quot;stars&quot;:8436},{&quot;dependents&quot;:33276,&quot;downloads_monthly&quot;:13951038,&quot;favers&quot;:24149,&quot;forks&quot;:2398,&quot;name&quot;:&quot;guzzlehttp/guzzle&quot;,&quot;stars&quot;:23313},{&quot;dependents&quot;:620,&quot;downloads_monthly&quot;:13899161,&quot;favers&quot;:1748,&quot;forks&quot;:21,&quot;name&quot;:&quot;symfony/string&quot;,&quot;stars&quot;:1741},{&quot;dependents&quot;:88,&quot;downloads_monthly&quot;:13865220,&quot;favers&quot;:3784,&quot;forks&quot;:34,&quot;name&quot;:&quot;ralouphie/getallheaders&quot;,&quot;stars&quot;:3778},{&quot;dependents&quot;:1627,&quot;downloads_monthly&quot;:13615876,&quot;favers&quot;:1833,&quot;forks&quot;:24,&quot;name&quot;:&quot;psr/http-factory&quot;,&quot;stars&quot;:1827},{&quot;dependents&quot;:1678,&quot;downloads_monthly&quot;:13603450,&quot;favers&quot;:17251,&quot;forks&quot;:1097,&quot;name&quot;:&quot;nikic/php-parser&quot;,&quot;stars&quot;:17179},{&quot;dependents&quot;:499,&quot;downloads_monthly&quot;:13565065,&quot;favers&quot;:1735,&quot;forks&quot;:26,&quot;name&quot;:&quot;symfony/polyfill-php80&quot;,&quot;stars&quot;:1731},{&quot;dependents&quot;:21,&quot;downloads_monthly&quot;:13553454,&quot;favers&quot;:1689,&quot;forks&quot;:4,&quot;name&quot;:&quot;symfony/polyfill-intl-grapheme&quot;,&quot;stars&quot;:1689},{&quot;dependents&quot;:1647,&quot;downloads_monthly&quot;:13335711,&quot;favers&quot;:1695,&quot;forks&quot;:17,&quot;name&quot;:&quot;psr/http-client&quot;,&quot;stars&quot;:1685},{&quot;dependents&quot;:3263,&quot;downloads_monthly&quot;:13306972,&quot;favers&quot;:8580,&quot;forks&quot;:67,&quot;name&quot;:&quot;symfony/event-dispatcher&quot;,&quot;stars&quot;:8534},{&quot;dependents&quot;:7517,&quot;downloads_monthly&quot;:13099994,&quot;favers&quot;:21986,&quot;forks&quot;:1899,&quot;name&quot;:&quot;monolog/monolog&quot;,&quot;stars&quot;:21132},{&quot;dependents&quot;:288,&quot;downloads_monthly&quot;:13070516,&quot;favers&quot;:3394,&quot;forks&quot;:3,&quot;name&quot;:&quot;symfony/event-dispatcher-contracts&quot;,&quot;stars&quot;:3394},{&quot;dependents&quot;:7637,&quot;downloads_monthly&quot;:12977531,&quot;favers&quot;:7558,&quot;forks&quot;:103,&quot;name&quot;:&quot;symfony/var-dumper&quot;,&quot;stars&quot;:7426},{&quot;dependents&quot;:163,&quot;downloads_monthly&quot;:12776353,&quot;favers&quot;:8831,&quot;forks&quot;:103,&quot;name&quot;:&quot;myclabs/deep-copy&quot;,&quot;stars&quot;:8807},{&quot;dependents&quot;:139853,&quot;downloads_monthly&quot;:12679593,&quot;favers&quot;:20294,&quot;forks&quot;:2206,&quot;name&quot;:&quot;phpunit/phpunit&quot;,&quot;stars&quot;:19791},{&quot;dependents&quot;:5080,&quot;downloads_monthly&quot;:12655313,&quot;favers&quot;:8726,&quot;forks&quot;:298,&quot;name&quot;:&quot;symfony/http-foundation&quot;,&quot;stars&quot;:8652},{&quot;dependents&quot;:237,&quot;downloads_monthly&quot;:12611304,&quot;favers&quot;:7648,&quot;forks&quot;:84,&quot;name&quot;:&quot;sebastian/diff&quot;,&quot;stars&quot;:7600},{&quot;dependents&quot;:102,&quot;downloads_monthly&quot;:12566777,&quot;favers&quot;:11119,&quot;forks&quot;:59,&quot;name&quot;:&quot;doctrine/lexer&quot;,&quot;stars&quot;:11102}],&quot;top_prod_packages&quot;:[{&quot;count&quot;:&quot;46437&quot;,&quot;name&quot;:&quot;illuminate/support&quot;},{&quot;count&quot;:&quot;39682&quot;,&quot;name&quot;:&quot;guzzlehttp/guzzle&quot;},{&quot;count&quot;:&quot;18948&quot;,&quot;name&quot;:&quot;laravel/framework&quot;},{&quot;count&quot;:&quot;16238&quot;,&quot;name&quot;:&quot;illuminate/contracts&quot;},{&quot;count&quot;:&quot;16190&quot;,&quot;name&quot;:&quot;yiisoft/yii2&quot;},{&quot;count&quot;:&quot;12982&quot;,&quot;name&quot;:&quot;symfony/console&quot;},{&quot;count&quot;:&quot;11680&quot;,&quot;name&quot;:&quot;psr/log&quot;},{&quot;count&quot;:&quot;11197&quot;,&quot;name&quot;:&quot;symfony/framework-bundle&quot;},{&quot;count&quot;:&quot;10241&quot;,&quot;name&quot;:&quot;illuminate/database&quot;},{&quot;count&quot;:&quot;9043&quot;,&quot;name&quot;:&quot;composer/installers&quot;},{&quot;count&quot;:&quot;8795&quot;,&quot;name&quot;:&quot;symfony/yaml&quot;},{&quot;count&quot;:&quot;8585&quot;,&quot;name&quot;:&quot;psr/http-message&quot;},{&quot;count&quot;:&quot;7986&quot;,&quot;name&quot;:&quot;monolog/monolog&quot;},{&quot;count&quot;:&quot;7696&quot;,&quot;name&quot;:&quot;symfony/dependency-injection&quot;},{&quot;count&quot;:&quot;7636&quot;,&quot;name&quot;:&quot;symfony/http-kernel&quot;},{&quot;count&quot;:&quot;7143&quot;,&quot;name&quot;:&quot;symfony/http-foundation&quot;},{&quot;count&quot;:&quot;7056&quot;,&quot;name&quot;:&quot;symfony/config&quot;},{&quot;count&quot;:&quot;6631&quot;,&quot;name&quot;:&quot;twig/twig&quot;},{&quot;count&quot;:&quot;6485&quot;,&quot;name&quot;:&quot;spatie/laravel-package-tools&quot;},{&quot;count&quot;:&quot;6397&quot;,&quot;name&quot;:&quot;doctrine/orm&quot;}],&quot;top_total_downloads&quot;:[{&quot;dependents&quot;:8736,&quot;downloads_total&quot;:963431048,&quot;favers&quot;:10599,&quot;forks&quot;:182,&quot;name&quot;:&quot;psr/log&quot;,&quot;stars&quot;:10421},{&quot;dependents&quot;:462,&quot;downloads_total&quot;:955254771,&quot;favers&quot;:7862,&quot;forks&quot;:38,&quot;name&quot;:&quot;symfony/polyfill-mbstring&quot;,&quot;stars&quot;:7847},{&quot;dependents&quot;:11415,&quot;downloads_total&quot;:885244463,&quot;favers&quot;:9958,&quot;forks&quot;:259,&quot;name&quot;:&quot;symfony/console&quot;,&quot;stars&quot;:9785},{&quot;dependents&quot;:5164,&quot;downloads_total&quot;:823272846,&quot;favers&quot;:7079,&quot;forks&quot;:186,&quot;name&quot;:&quot;psr/http-message&quot;,&quot;stars&quot;:6998},{&quot;dependents&quot;:2970,&quot;downloads_total&quot;:814214923,&quot;favers&quot;:7964,&quot;forks&quot;:295,&quot;name&quot;:&quot;guzzlehttp/psr7&quot;,&quot;stars&quot;:7905},{&quot;dependents&quot;:3936,&quot;downloads_total&quot;:807162519,&quot;favers&quot;:8507,&quot;forks&quot;:55,&quot;name&quot;:&quot;symfony/finder&quot;,&quot;stars&quot;:8436},{&quot;dependents&quot;:3581,&quot;downloads_total&quot;:803012501,&quot;favers&quot;:10014,&quot;forks&quot;:51,&quot;name&quot;:&quot;psr/container&quot;,&quot;stars&quot;:9981},{&quot;dependents&quot;:3263,&quot;downloads_total&quot;:801654417,&quot;favers&quot;:8580,&quot;forks&quot;:67,&quot;name&quot;:&quot;symfony/event-dispatcher&quot;,&quot;stars&quot;:8534},{&quot;dependents&quot;:33276,&quot;downloads_total&quot;:800504182,&quot;favers&quot;:24149,&quot;forks&quot;:2398,&quot;name&quot;:&quot;guzzlehttp/guzzle&quot;,&quot;stars&quot;:23313},{&quot;dependents&quot;:7517,&quot;downloads_total&quot;:788872755,&quot;favers&quot;:21986,&quot;forks&quot;:1899,&quot;name&quot;:&quot;monolog/monolog&quot;,&quot;stars&quot;:21132},{&quot;dependents&quot;:111,&quot;downloads_total&quot;:787938032,&quot;favers&quot;:4059,&quot;forks&quot;:7,&quot;name&quot;:&quot;symfony/polyfill-ctype&quot;,&quot;stars&quot;:4056},{&quot;dependents&quot;:5283,&quot;downloads_total&quot;:785182390,&quot;favers&quot;:7511,&quot;forks&quot;:104,&quot;name&quot;:&quot;symfony/process&quot;,&quot;stars&quot;:7453},{&quot;dependents&quot;:461,&quot;downloads_total&quot;:780413771,&quot;favers&quot;:7678,&quot;forks&quot;:117,&quot;name&quot;:&quot;guzzlehttp/promises&quot;,&quot;stars&quot;:7645},{&quot;dependents&quot;:102,&quot;downloads_total&quot;:740573713,&quot;favers&quot;:11119,&quot;forks&quot;:59,&quot;name&quot;:&quot;doctrine/lexer&quot;,&quot;stars&quot;:11102},{&quot;dependents&quot;:139853,&quot;downloads_total&quot;:738780466,&quot;favers&quot;:20294,&quot;forks&quot;:2206,&quot;name&quot;:&quot;phpunit/phpunit&quot;,&quot;stars&quot;:19791},{&quot;dependents&quot;:1141,&quot;downloads_total&quot;:738147462,&quot;favers&quot;:7659,&quot;forks&quot;:147,&quot;name&quot;:&quot;webmozart/assert&quot;,&quot;stars&quot;:7588},{&quot;dependents&quot;:349,&quot;downloads_total&quot;:734081448,&quot;favers&quot;:11001,&quot;forks&quot;:62,&quot;name&quot;:&quot;doctrine/instantiator&quot;,&quot;stars&quot;:10975},{&quot;dependents&quot;:237,&quot;downloads_total&quot;:729909604,&quot;favers&quot;:7648,&quot;forks&quot;:84,&quot;name&quot;:&quot;sebastian/diff&quot;,&quot;stars&quot;:7600},{&quot;dependents&quot;:1522,&quot;downloads_total&quot;:725085270,&quot;favers&quot;:8907,&quot;forks&quot;:376,&quot;name&quot;:&quot;phpunit/php-code-coverage&quot;,&quot;stars&quot;:8863},{&quot;dependents&quot;:75,&quot;downloads_total&quot;:715295250,&quot;favers&quot;:6810,&quot;forks&quot;:34,&quot;name&quot;:&quot;sebastian/exporter&quot;,&quot;stars&quot;:6801},{&quot;dependents&quot;:69,&quot;downloads_total&quot;:713273284,&quot;favers&quot;:7453,&quot;forks&quot;:46,&quot;name&quot;:&quot;phpunit/php-file-iterator&quot;,&quot;stars&quot;:7444},{&quot;dependents&quot;:1678,&quot;downloads_total&quot;:713118750,&quot;favers&quot;:17251,&quot;forks&quot;:1097,&quot;name&quot;:&quot;nikic/php-parser&quot;,&quot;stars&quot;:17179},{&quot;dependents&quot;:141,&quot;downloads_total&quot;:711827957,&quot;favers&quot;:7747,&quot;forks&quot;:65,&quot;name&quot;:&quot;phpunit/php-timer&quot;,&quot;stars&quot;:7684},{&quot;dependents&quot;:152,&quot;downloads_total&quot;:711533096,&quot;favers&quot;:7038,&quot;forks&quot;:69,&quot;name&quot;:&quot;sebastian/comparator&quot;,&quot;stars&quot;:7024},{&quot;dependents&quot;:18,&quot;downloads_total&quot;:706487222,&quot;favers&quot;:6560,&quot;forks&quot;:18,&quot;name&quot;:&quot;sebastian/recursion-context&quot;,&quot;stars&quot;:6556},{&quot;dependents&quot;:63,&quot;downloads_total&quot;:705815492,&quot;favers&quot;:6762,&quot;forks&quot;:35,&quot;name&quot;:&quot;sebastian/environment&quot;,&quot;stars&quot;:6752},{&quot;dependents&quot;:5080,&quot;downloads_total&quot;:704319494,&quot;favers&quot;:8726,&quot;forks&quot;:298,&quot;name&quot;:&quot;symfony/http-foundation&quot;,&quot;stars&quot;:8652},{&quot;dependents&quot;:73,&quot;downloads_total&quot;:703572581,&quot;favers&quot;:7402,&quot;forks&quot;:30,&quot;name&quot;:&quot;phpunit/php-text-template&quot;,&quot;stars&quot;:7389},{&quot;dependents&quot;:138,&quot;downloads_total&quot;:700309956,&quot;favers&quot;:6575,&quot;forks&quot;:32,&quot;name&quot;:&quot;sebastian/version&quot;,&quot;stars&quot;:6557},{&quot;dependents&quot;:787,&quot;downloads_total&quot;:699076879,&quot;favers&quot;:11323,&quot;forks&quot;:135,&quot;name&quot;:&quot;doctrine/inflector&quot;,&quot;stars&quot;:11290}],&quot;underrated_packages&quot;:[{&quot;download_star_ratio&quot;:46868,&quot;downloads_monthly&quot;:3562001,&quot;downloads_total&quot;:8234587,&quot;favers&quot;:76,&quot;name&quot;:&quot;staabm/side-effects-detector&quot;,&quot;stars&quot;:76},{&quot;download_star_ratio&quot;:58797,&quot;downloads_monthly&quot;:2469489,&quot;downloads_total&quot;:17813872,&quot;favers&quot;:42,&quot;name&quot;:&quot;spatie/error-solutions&quot;,&quot;stars&quot;:42},{&quot;download_star_ratio&quot;:53192,&quot;downloads_monthly&quot;:2287292,&quot;downloads_total&quot;:36569212,&quot;favers&quot;:43,&quot;name&quot;:&quot;google/longrunning&quot;,&quot;stars&quot;:43},{&quot;download_star_ratio&quot;:25029,&quot;downloads_monthly&quot;:1827182,&quot;downloads_total&quot;:48775694,&quot;favers&quot;:74,&quot;name&quot;:&quot;illuminate/macroable&quot;,&quot;stars&quot;:73},{&quot;download_star_ratio&quot;:43452,&quot;downloads_monthly&quot;:1607744,&quot;downloads_total&quot;:26682353,&quot;favers&quot;:39,&quot;name&quot;:&quot;pestphp/pest-plugin&quot;,&quot;stars&quot;:37},{&quot;download_star_ratio&quot;:20210,&quot;downloads_monthly&quot;:1596657,&quot;downloads_total&quot;:34137970,&quot;favers&quot;:79,&quot;name&quot;:&quot;php-http/guzzle7-adapter&quot;,&quot;stars&quot;:79},{&quot;download_star_ratio&quot;:15943,&quot;downloads_monthly&quot;:1450867,&quot;downloads_total&quot;:16432345,&quot;favers&quot;:91,&quot;name&quot;:&quot;ta-tikoma/phpunit-architecture-test&quot;,&quot;stars&quot;:91},{&quot;download_star_ratio&quot;:43885,&quot;downloads_monthly&quot;:1448220,&quot;downloads_total&quot;:16467245,&quot;favers&quot;:33,&quot;name&quot;:&quot;pestphp/pest-plugin-arch&quot;,&quot;stars&quot;:33},{&quot;download_star_ratio&quot;:25705,&quot;downloads_monthly&quot;:1413796,&quot;downloads_total&quot;:14926683,&quot;favers&quot;:55,&quot;name&quot;:&quot;symfony/polyfill-php82&quot;,&quot;stars&quot;:55},{&quot;download_star_ratio&quot;:17402,&quot;downloads_monthly&quot;:1409620,&quot;downloads_total&quot;:24231085,&quot;favers&quot;:82,&quot;name&quot;:&quot;illuminate/conditionable&quot;,&quot;stars&quot;:81},{&quot;download_star_ratio&quot;:15265,&quot;downloads_monthly&quot;:1404425,&quot;downloads_total&quot;:15044207,&quot;favers&quot;:95,&quot;name&quot;:&quot;kelunik/certificate&quot;,&quot;stars&quot;:92},{&quot;download_star_ratio&quot;:25076,&quot;downloads_monthly&quot;:1354147,&quot;downloads_total&quot;:10013761,&quot;favers&quot;:54,&quot;name&quot;:&quot;amphp/pipeline&quot;,&quot;stars&quot;:54},{&quot;download_star_ratio&quot;:91579,&quot;downloads_monthly&quot;:1282117,&quot;downloads_total&quot;:10878343,&quot;favers&quot;:16,&quot;name&quot;:&quot;open-telemetry/api&quot;,&quot;stars&quot;:14},{&quot;download_star_ratio&quot;:12999,&quot;downloads_monthly&quot;:1260932,&quot;downloads_total&quot;:63871523,&quot;favers&quot;:97,&quot;name&quot;:&quot;pear/pear_exception&quot;,&quot;stars&quot;:97},{&quot;download_star_ratio&quot;:111863,&quot;downloads_monthly&quot;:1230502,&quot;downloads_total&quot;:10718104,&quot;favers&quot;:11,&quot;name&quot;:&quot;open-telemetry/context&quot;,&quot;stars&quot;:11},{&quot;download_star_ratio&quot;:15580,&quot;downloads_monthly&quot;:1199700,&quot;downloads_total&quot;:57591646,&quot;favers&quot;:78,&quot;name&quot;:&quot;pear/pear-core-minimal&quot;,&quot;stars&quot;:77},{&quot;download_star_ratio&quot;:13689,&quot;downloads_monthly&quot;:1149937,&quot;downloads_total&quot;:56739652,&quot;favers&quot;:84,&quot;name&quot;:&quot;pear/console_getopt&quot;,&quot;stars&quot;:84},{&quot;download_star_ratio&quot;:19283,&quot;downloads_monthly&quot;:1060588,&quot;downloads_total&quot;:15810440,&quot;favers&quot;:55,&quot;name&quot;:&quot;phpcsstandards/phpcsutils&quot;,&quot;stars&quot;:55},{&quot;download_star_ratio&quot;:22802,&quot;downloads_monthly&quot;:1048935,&quot;downloads_total&quot;:17505202,&quot;favers&quot;:46,&quot;name&quot;:&quot;mpdf/psr-log-aware-trait&quot;,&quot;stars&quot;:46},{&quot;download_star_ratio&quot;:12565,&quot;downloads_monthly&quot;:1005223,&quot;downloads_total&quot;:37707351,&quot;favers&quot;:81,&quot;name&quot;:&quot;codeception/module-asserts&quot;,&quot;stars&quot;:80},{&quot;download_star_ratio&quot;:13526,&quot;downloads_monthly&quot;:1000954,&quot;downloads_total&quot;:48004786,&quot;favers&quot;:76,&quot;name&quot;:&quot;pear/archive_tar&quot;,&quot;stars&quot;:74},{&quot;download_star_ratio&quot;:30258,&quot;downloads_monthly&quot;:998531,&quot;downloads_total&quot;:13631481,&quot;favers&quot;:33,&quot;name&quot;:&quot;mpdf/psr-http-message-shim&quot;,&quot;stars&quot;:33},{&quot;download_star_ratio&quot;:9798,&quot;downloads_monthly&quot;:960247,&quot;downloads_total&quot;:47386371,&quot;favers&quot;:98,&quot;name&quot;:&quot;consolidation/self-update&quot;,&quot;stars&quot;:98},{&quot;download_star_ratio&quot;:11268,&quot;downloads_monthly&quot;:957795,&quot;downloads_total&quot;:24112218,&quot;favers&quot;:86,&quot;name&quot;:&quot;async-aws/core&quot;,&quot;stars&quot;:85},{&quot;download_star_ratio&quot;:15545,&quot;downloads_monthly&quot;:917195,&quot;downloads_total&quot;:37384405,&quot;favers&quot;:59,&quot;name&quot;:&quot;consolidation/site-alias&quot;,&quot;stars&quot;:59},{&quot;download_star_ratio&quot;:44598,&quot;downloads_monthly&quot;:891972,&quot;downloads_total&quot;:2767437,&quot;favers&quot;:21,&quot;name&quot;:&quot;symfony/polyfill-php84&quot;,&quot;stars&quot;:20},{&quot;download_star_ratio&quot;:19572,&quot;downloads_monthly&quot;:880764,&quot;downloads_total&quot;:35635569,&quot;favers&quot;:45,&quot;name&quot;:&quot;consolidation/filter-via-dot-access-data&quot;,&quot;stars&quot;:45},{&quot;download_star_ratio&quot;:10540,&quot;downloads_monthly&quot;:874850,&quot;downloads_total&quot;:30790296,&quot;favers&quot;:83,&quot;name&quot;:&quot;codeception/lib-innerbrowser&quot;,&quot;stars&quot;:83},{&quot;download_star_ratio&quot;:17333,&quot;downloads_monthly&quot;:866678,&quot;downloads_total&quot;:34679443,&quot;favers&quot;:51,&quot;name&quot;:&quot;consolidation/site-process&quot;,&quot;stars&quot;:50},{&quot;download_star_ratio&quot;:17526,&quot;downloads_monthly&quot;:858811,&quot;downloads_total&quot;:31379072,&quot;favers&quot;:50,&quot;name&quot;:&quot;drupal/core-composer-scaffold&quot;,&quot;stars&quot;:49}],&quot;zero_stars_packages&quot;:245070};
  const languages = [{&quot;Language&quot;:&quot;PHP&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:707353896,&quot;Comments&quot;:320719500,&quot;Blanks&quot;:149366159},{&quot;Language&quot;:&quot;JavaScript&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:216447233,&quot;Comments&quot;:42754373,&quot;Blanks&quot;:29489796},{&quot;Language&quot;:&quot;JSON&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:177571760,&quot;Comments&quot;:0,&quot;Blanks&quot;:126355},{&quot;Language&quot;:&quot;CSS&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:96900448,&quot;Comments&quot;:3466375,&quot;Blanks&quot;:13092368},{&quot;Language&quot;:&quot;HTML&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:70849158,&quot;Comments&quot;:1085498,&quot;Blanks&quot;:9760760},{&quot;Language&quot;:&quot;XML&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:49900719,&quot;Comments&quot;:1265421,&quot;Blanks&quot;:1113886},{&quot;Language&quot;:&quot;YAML&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:25322932,&quot;Comments&quot;:506971,&quot;Blanks&quot;:775201},{&quot;Language&quot;:&quot;SVG&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:25211075,&quot;Comments&quot;:172811,&quot;Blanks&quot;:246033},{&quot;Language&quot;:&quot;SQL&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:10374794,&quot;Comments&quot;:314469,&quot;Blanks&quot;:318288},{&quot;Language&quot;:&quot;Sass&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:9255298,&quot;Comments&quot;:984480,&quot;Blanks&quot;:1568503},{&quot;Language&quot;:&quot;LESS&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:5968904,&quot;Comments&quot;:811168,&quot;Blanks&quot;:983435},{&quot;Language&quot;:&quot;PO File&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:5622443,&quot;Comments&quot;:3443936,&quot;Blanks&quot;:2561674},{&quot;Language&quot;:&quot;Twig&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:4989336,&quot;Comments&quot;:508527,&quot;Blanks&quot;:589527},{&quot;Language&quot;:&quot;TypeScript&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:2764367,&quot;Comments&quot;:392051,&quot;Blanks&quot;:160355},{&quot;Language&quot;:&quot;Bitbake&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:2154509,&quot;Comments&quot;:16243,&quot;Blanks&quot;:240554},{&quot;Language&quot;:&quot;ReStructuredText&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:1874361,&quot;Comments&quot;:0,&quot;Blanks&quot;:825635},{&quot;Language&quot;:&quot;INI&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:1532443,&quot;Comments&quot;:601460,&quot;Blanks&quot;:328312},{&quot;Language&quot;:&quot;Ruby&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:1485300,&quot;Comments&quot;:81457,&quot;Blanks&quot;:233365},{&quot;Language&quot;:&quot;TeX&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:1482197,&quot;Comments&quot;:29578,&quot;Blanks&quot;:5352},{&quot;Language&quot;:&quot;Shell&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:1369745,&quot;Comments&quot;:205426,&quot;Blanks&quot;:253603},{&quot;Language&quot;:&quot;Pan&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:1147859,&quot;Comments&quot;:2087,&quot;Blanks&quot;:115262},{&quot;Language&quot;:&quot;C&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:993627,&quot;Comments&quot;:141078,&quot;Blanks&quot;:163269},{&quot;Language&quot;:&quot;Lua&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:678874,&quot;Comments&quot;:3670,&quot;Blanks&quot;:5019},{&quot;Language&quot;:&quot;Perl&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:606282,&quot;Comments&quot;:56995,&quot;Blanks&quot;:24951},{&quot;Language&quot;:&quot;XSL&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:603850,&quot;Comments&quot;:51833,&quot;Blanks&quot;:42468},{&quot;Language&quot;:&quot;Puppet&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:595345,&quot;Comments&quot;:172555,&quot;Blanks&quot;:77077},{&quot;Language&quot;:&quot;C Header&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:584315,&quot;Comments&quot;:127703,&quot;Blanks&quot;:102504},{&quot;Language&quot;:&quot;Vue&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:451881,&quot;Comments&quot;:7271,&quot;Blanks&quot;:72600},{&quot;Language&quot;:&quot;Python&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:423252,&quot;Comments&quot;:162342,&quot;Blanks&quot;:130145},{&quot;Language&quot;:&quot;Makefile&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:413986,&quot;Comments&quot;:44765,&quot;Blanks&quot;:129067},{&quot;Language&quot;:&quot;TSX&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:385366,&quot;Comments&quot;:14981,&quot;Blanks&quot;:46005},{&quot;Language&quot;:&quot;Modelica&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:361648,&quot;Comments&quot;:2525,&quot;Blanks&quot;:3308},{&quot;Language&quot;:&quot;BASH&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:300048,&quot;Comments&quot;:85183,&quot;Blanks&quot;:64664},{&quot;Language&quot;:&quot;JSX&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:297726,&quot;Comments&quot;:12951,&quot;Blanks&quot;:34822},{&quot;Language&quot;:&quot;C++&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:287595,&quot;Comments&quot;:35133,&quot;Blanks&quot;:47455},{&quot;Language&quot;:&quot;Java&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:278237,&quot;Comments&quot;:77305,&quot;Blanks&quot;:56027},{&quot;Language&quot;:&quot;Go&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:270660,&quot;Comments&quot;:22450,&quot;Blanks&quot;:42700},{&quot;Language&quot;:&quot;Scheme&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:224964,&quot;Comments&quot;:45,&quot;Blanks&quot;:20281},{&quot;Language&quot;:&quot;Dockerfile&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:210925,&quot;Comments&quot;:37307,&quot;Blanks&quot;:68526},{&quot;Language&quot;:&quot;C#&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:199645,&quot;Comments&quot;:29727,&quot;Blanks&quot;:27626},{&quot;Language&quot;:&quot;Mustache&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:198803,&quot;Comments&quot;:1459,&quot;Blanks&quot;:16273},{&quot;Language&quot;:&quot;Ruby HTML&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:178511,&quot;Comments&quot;:26,&quot;Blanks&quot;:15557},{&quot;Language&quot;:&quot;AsciiDoc&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:176814,&quot;Comments&quot;:4987,&quot;Blanks&quot;:42828},{&quot;Language&quot;:&quot;CoffeeScript&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:175124,&quot;Comments&quot;:22805,&quot;Blanks&quot;:42719},{&quot;Language&quot;:&quot;Rust&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:166004,&quot;Comments&quot;:4690,&quot;Blanks&quot;:24888},{&quot;Language&quot;:&quot;GraphQL&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:161623,&quot;Comments&quot;:2486,&quot;Blanks&quot;:18237},{&quot;Language&quot;:&quot;Batch&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:127339,&quot;Comments&quot;:8183,&quot;Blanks&quot;:26023},{&quot;Language&quot;:&quot;Stylus&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:112491,&quot;Comments&quot;:9679,&quot;Blanks&quot;:22731},{&quot;Language&quot;:&quot;Handlebars&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:89346,&quot;Comments&quot;:1042,&quot;Blanks&quot;:6281},{&quot;Language&quot;:&quot;Autoconf&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:87488,&quot;Comments&quot;:10441,&quot;Blanks&quot;:11100},{&quot;Language&quot;:&quot;Protocol Buffers&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:85283,&quot;Comments&quot;:29329,&quot;Blanks&quot;:19387},{&quot;Language&quot;:&quot;Happy&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:71591,&quot;Comments&quot;:0,&quot;Blanks&quot;:12069},{&quot;Language&quot;:&quot;ActionScript&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:64371,&quot;Comments&quot;:12476,&quot;Blanks&quot;:11795},{&quot;Language&quot;:&quot;Templ&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:60108,&quot;Comments&quot;:12247,&quot;Blanks&quot;:7373},{&quot;Language&quot;:&quot;M4&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:58949,&quot;Comments&quot;:13257,&quot;Blanks&quot;:6126},{&quot;Language&quot;:&quot;Dart&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:43563,&quot;Comments&quot;:3123,&quot;Blanks&quot;:9014},{&quot;Language&quot;:&quot;Pug&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:34256,&quot;Comments&quot;:991,&quot;Blanks&quot;:3679},{&quot;Language&quot;:&quot;ReScript&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:31988,&quot;Comments&quot;:48,&quot;Blanks&quot;:642},{&quot;Language&quot;:&quot;Jinja2&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:30395,&quot;Comments&quot;:87,&quot;Blanks&quot;:5239},{&quot;Language&quot;:&quot;D&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:28764,&quot;Comments&quot;:13629,&quot;Blanks&quot;:5994},{&quot;Language&quot;:&quot;Nix&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:28099,&quot;Comments&quot;:1004,&quot;Blanks&quot;:847},{&quot;Language&quot;:&quot;Forge Config&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:26217,&quot;Comments&quot;:20806,&quot;Blanks&quot;:10140},{&quot;Language&quot;:&quot;Svelte&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:23967,&quot;Comments&quot;:418,&quot;Blanks&quot;:2284},{&quot;Language&quot;:&quot;Thrift&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:22511,&quot;Comments&quot;:21628,&quot;Blanks&quot;:6347},{&quot;Language&quot;:&quot;Pascal&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:22165,&quot;Comments&quot;:3282,&quot;Blanks&quot;:5169},{&quot;Language&quot;:&quot;Objective-C&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:20434,&quot;Comments&quot;:2887,&quot;Blanks&quot;:5238},{&quot;Language&quot;:&quot;Org&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:20224,&quot;Comments&quot;:33,&quot;Blanks&quot;:4995},{&quot;Language&quot;:&quot;Dust.js&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:18852,&quot;Comments&quot;:47,&quot;Blanks&quot;:2436},{&quot;Language&quot;:&quot;Rakefile&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:17503,&quot;Comments&quot;:895,&quot;Blanks&quot;:3261},{&quot;Language&quot;:&quot;ASP&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:14903,&quot;Comments&quot;:4193,&quot;Blanks&quot;:2737},{&quot;Language&quot;:&quot;Haxe&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:13040,&quot;Comments&quot;:5035,&quot;Blanks&quot;:3248},{&quot;Language&quot;:&quot;PostCSS&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:12135,&quot;Comments&quot;:1902,&quot;Blanks&quot;:2601},{&quot;Language&quot;:&quot;PlantUML&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:11878,&quot;Comments&quot;:154,&quot;Blanks&quot;:1857},{&quot;Language&quot;:&quot;Erlang&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:10802,&quot;Comments&quot;:2449,&quot;Blanks&quot;:1955},{&quot;Language&quot;:&quot;Visual Studio Project&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:10801,&quot;Comments&quot;:0,&quot;Blanks&quot;:0},{&quot;Language&quot;:&quot;PowerShell&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:10432,&quot;Comments&quot;:1685,&quot;Blanks&quot;:1869},{&quot;Language&quot;:&quot;ASP.NET&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:10349,&quot;Comments&quot;:529,&quot;Blanks&quot;:126},{&quot;Language&quot;:&quot;TOML&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:10110,&quot;Comments&quot;:2704,&quot;Blanks&quot;:2007},{&quot;Language&quot;:&quot;GNU Style Assembly&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:9293,&quot;Comments&quot;:7189,&quot;Blanks&quot;:1917},{&quot;Language&quot;:&quot;RPM Specfile&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:8312,&quot;Comments&quot;:373,&quot;Blanks&quot;:1817},{&quot;Language&quot;:&quot;ColdFusion&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:8025,&quot;Comments&quot;:1928,&quot;Blanks&quot;:1267},{&quot;Language&quot;:&quot;MSBuild&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:8008,&quot;Comments&quot;:798,&quot;Blanks&quot;:298},{&quot;Language&quot;:&quot;TCL&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:7953,&quot;Comments&quot;:485,&quot;Blanks&quot;:1257},{&quot;Language&quot;:&quot;Swift&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:7848,&quot;Comments&quot;:1345,&quot;Blanks&quot;:1825},{&quot;Language&quot;:&quot;Haml&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:7723,&quot;Comments&quot;:161,&quot;Blanks&quot;:993},{&quot;Language&quot;:&quot;Automake&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:7358,&quot;Comments&quot;:3168,&quot;Blanks&quot;:1963},{&quot;Language&quot;:&quot;Bazel&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:6533,&quot;Comments&quot;:409,&quot;Blanks&quot;:1493},{&quot;Language&quot;:&quot;SRecode Template&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:6467,&quot;Comments&quot;:0,&quot;Blanks&quot;:1745},{&quot;Language&quot;:&quot;Solidity&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:6116,&quot;Comments&quot;:1280,&quot;Blanks&quot;:1777},{&quot;Language&quot;:&quot;Prolog&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:5696,&quot;Comments&quot;:33,&quot;Blanks&quot;:1468},{&quot;Language&quot;:&quot;HCL&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:5669,&quot;Comments&quot;:156,&quot;Blanks&quot;:1160},{&quot;Language&quot;:&quot;Assembly&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:5599,&quot;Comments&quot;:1930,&quot;Blanks&quot;:1704},{&quot;Language&quot;:&quot;LiveScript&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:5462,&quot;Comments&quot;:188,&quot;Blanks&quot;:930},{&quot;Language&quot;:&quot;Liquid&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:5319,&quot;Comments&quot;:440,&quot;Blanks&quot;:993},{&quot;Language&quot;:&quot;ColdFusion CFScript&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:4816,&quot;Comments&quot;:140,&quot;Blanks&quot;:748},{&quot;Language&quot;:&quot;Kotlin&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:4666,&quot;Comments&quot;:636,&quot;Blanks&quot;:862},{&quot;Language&quot;:&quot;Apache Velocity&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:4409,&quot;Comments&quot;:332,&quot;Blanks&quot;:231},{&quot;Language&quot;:&quot;VB6&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:4201,&quot;Comments&quot;:0,&quot;Blanks&quot;:123},{&quot;Language&quot;:&quot;CMake&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:4104,&quot;Comments&quot;:1552,&quot;Blanks&quot;:855},{&quot;Language&quot;:&quot;Visual Studio Solution&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:4016,&quot;Comments&quot;:0,&quot;Blanks&quot;:125},{&quot;Language&quot;:&quot;KV Language&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:3842,&quot;Comments&quot;:0,&quot;Blanks&quot;:0},{&quot;Language&quot;:&quot;FreeMarker&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:3144,&quot;Comments&quot;:20,&quot;Blanks&quot;:960},{&quot;Language&quot;:&quot;OCaml&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:3111,&quot;Comments&quot;:528,&quot;Blanks&quot;:364},{&quot;Language&quot;:&quot;Lex&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:2829,&quot;Comments&quot;:647,&quot;Blanks&quot;:887},{&quot;Language&quot;:&quot;Zsh&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:2822,&quot;Comments&quot;:2395,&quot;Blanks&quot;:667},{&quot;Language&quot;:&quot;R&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:2590,&quot;Comments&quot;:614,&quot;Blanks&quot;:902},{&quot;Language&quot;:&quot;Haskell&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:2409,&quot;Comments&quot;:615,&quot;Blanks&quot;:586},{&quot;Language&quot;:&quot;AWK&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:2157,&quot;Comments&quot;:903,&quot;Blanks&quot;:178},{&quot;Language&quot;:&quot;Scala&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:2124,&quot;Comments&quot;:621,&quot;Blanks&quot;:553},{&quot;Language&quot;:&quot;Vim Script&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:1969,&quot;Comments&quot;:196,&quot;Blanks&quot;:196},{&quot;Language&quot;:&quot;SWIG&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:1844,&quot;Comments&quot;:310,&quot;Blanks&quot;:240},{&quot;Language&quot;:&quot;COBOL&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:1739,&quot;Comments&quot;:20,&quot;Blanks&quot;:181},{&quot;Language&quot;:&quot;Visual Basic&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:1704,&quot;Comments&quot;:295,&quot;Blanks&quot;:604},{&quot;Language&quot;:&quot;Fish&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:1677,&quot;Comments&quot;:225,&quot;Blanks&quot;:284},{&quot;Language&quot;:&quot;Groovy&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:1631,&quot;Comments&quot;:689,&quot;Blanks&quot;:356},{&quot;Language&quot;:&quot;Arturo&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:1541,&quot;Comments&quot;:0,&quot;Blanks&quot;:64},{&quot;Language&quot;:&quot;Arduino C++&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:1428,&quot;Comments&quot;:459,&quot;Blanks&quot;:352},{&quot;Language&quot;:&quot;Jupyter Notebooks&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:1263,&quot;Comments&quot;:335,&quot;Blanks&quot;:192},{&quot;Language&quot;:&quot;VBScript&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:1239,&quot;Comments&quot;:275,&quot;Blanks&quot;:310},{&quot;Language&quot;:&quot;Elm&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:1221,&quot;Comments&quot;:13,&quot;Blanks&quot;:291},{&quot;Language&quot;:&quot;Objective-C++&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:1129,&quot;Comments&quot;:0,&quot;Blanks&quot;:39},{&quot;Language&quot;:&quot;Elixir&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:1128,&quot;Comments&quot;:35,&quot;Blanks&quot;:145},{&quot;Language&quot;:&quot;SystemVerilog&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:966,&quot;Comments&quot;:0,&quot;Blanks&quot;:240},{&quot;Language&quot;:&quot;LLVM&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:949,&quot;Comments&quot;:0,&quot;Blanks&quot;:103},{&quot;Language&quot;:&quot;jq&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:882,&quot;Comments&quot;:24,&quot;Blanks&quot;:9},{&quot;Language&quot;:&quot;Just&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:872,&quot;Comments&quot;:213,&quot;Blanks&quot;:314},{&quot;Language&quot;:&quot;XAML&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:746,&quot;Comments&quot;:3,&quot;Blanks&quot;:42},{&quot;Language&quot;:&quot;Common Lisp&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:702,&quot;Comments&quot;:172,&quot;Blanks&quot;:192},{&quot;Language&quot;:&quot;Edn&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:676,&quot;Comments&quot;:18,&quot;Blanks&quot;:12},{&quot;Language&quot;:&quot;FORTRAN Modern&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:625,&quot;Comments&quot;:4,&quot;Blanks&quot;:118},{&quot;Language&quot;:&quot;Coq&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:604,&quot;Comments&quot;:0,&quot;Blanks&quot;:123},{&quot;Language&quot;:&quot;Astro&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:567,&quot;Comments&quot;:9,&quot;Blanks&quot;:69},{&quot;Language&quot;:&quot;ABNF&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:363,&quot;Comments&quot;:24,&quot;Blanks&quot;:151},{&quot;Language&quot;:&quot;Forth&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:356,&quot;Comments&quot;:1,&quot;Blanks&quot;:77},{&quot;Language&quot;:&quot;Clojure&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:336,&quot;Comments&quot;:25,&quot;Blanks&quot;:48},{&quot;Language&quot;:&quot;Razor&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:323,&quot;Comments&quot;:32,&quot;Blanks&quot;:60},{&quot;Language&quot;:&quot;Nim&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:312,&quot;Comments&quot;:63,&quot;Blanks&quot;:78},{&quot;Language&quot;:&quot;Emacs Lisp&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:306,&quot;Comments&quot;:58,&quot;Blanks&quot;:52},{&quot;Language&quot;:&quot;GLSL&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:302,&quot;Comments&quot;:41,&quot;Blanks&quot;:84},{&quot;Language&quot;:&quot;C Shell&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:294,&quot;Comments&quot;:66,&quot;Blanks&quot;:50},{&quot;Language&quot;:&quot;AutoHotKey&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:288,&quot;Comments&quot;:68,&quot;Blanks&quot;:55},{&quot;Language&quot;:&quot;Cabal&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:243,&quot;Comments&quot;:45,&quot;Blanks&quot;:26},{&quot;Language&quot;:&quot;Cython&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:220,&quot;Comments&quot;:2,&quot;Blanks&quot;:63},{&quot;Language&quot;:&quot;Alex&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:176,&quot;Comments&quot;:0,&quot;Blanks&quot;:62},{&quot;Language&quot;:&quot;PSL Assertion&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:165,&quot;Comments&quot;:0,&quot;Blanks&quot;:28},{&quot;Language&quot;:&quot;.NET Resource&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:147,&quot;Comments&quot;:118,&quot;Blanks&quot;:3},{&quot;Language&quot;:&quot;Madlang&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:136,&quot;Comments&quot;:0,&quot;Blanks&quot;:26},{&quot;Language&quot;:&quot;GDScript&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:119,&quot;Comments&quot;:4,&quot;Blanks&quot;:39},{&quot;Language&quot;:&quot;Snakemake&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:109,&quot;Comments&quot;:37,&quot;Blanks&quot;:30},{&quot;Language&quot;:&quot;LOLCODE&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:104,&quot;Comments&quot;:17,&quot;Blanks&quot;:32},{&quot;Language&quot;:&quot;VHDL&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:100,&quot;Comments&quot;:0,&quot;Blanks&quot;:36},{&quot;Language&quot;:&quot;Pacman&apos;s makepkg&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:96,&quot;Comments&quot;:6,&quot;Blanks&quot;:15},{&quot;Language&quot;:&quot;OpenSCAD&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:95,&quot;Comments&quot;:6,&quot;Blanks&quot;:11},{&quot;Language&quot;:&quot;Nushell&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:92,&quot;Comments&quot;:0,&quot;Blanks&quot;:22},{&quot;Language&quot;:&quot;Gml&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:90,&quot;Comments&quot;:0,&quot;Blanks&quot;:2},{&quot;Language&quot;:&quot;NuGet Config&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:79,&quot;Comments&quot;:0,&quot;Blanks&quot;:0},{&quot;Language&quot;:&quot;ABAP&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:75,&quot;Comments&quot;:36,&quot;Blanks&quot;:38},{&quot;Language&quot;:&quot;Open Policy Agent&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:74,&quot;Comments&quot;:24,&quot;Blanks&quot;:42},{&quot;Language&quot;:&quot;Julia&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:73,&quot;Comments&quot;:1,&quot;Blanks&quot;:10},{&quot;Language&quot;:&quot;Monkey C&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:69,&quot;Comments&quot;:0,&quot;Blanks&quot;:0},{&quot;Language&quot;:&quot;Vala&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:64,&quot;Comments&quot;:0,&quot;Blanks&quot;:20},{&quot;Language&quot;:&quot;Phix&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:63,&quot;Comments&quot;:6,&quot;Blanks&quot;:24},{&quot;Language&quot;:&quot;Module-Definition&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:48,&quot;Comments&quot;:0,&quot;Blanks&quot;:22},{&quot;Language&quot;:&quot;Ada&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:47,&quot;Comments&quot;:0,&quot;Blanks&quot;:2},{&quot;Language&quot;:&quot;Jsonnet&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:38,&quot;Comments&quot;:1,&quot;Blanks&quot;:5},{&quot;Language&quot;:&quot;FlatBuffers Schema&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:36,&quot;Comments&quot;:3,&quot;Blanks&quot;:7},{&quot;Language&quot;:&quot;CUE&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:34,&quot;Comments&quot;:0,&quot;Blanks&quot;:0},{&quot;Language&quot;:&quot;Lean&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:24,&quot;Comments&quot;:0,&quot;Blanks&quot;:3},{&quot;Language&quot;:&quot;HEX&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:23,&quot;Comments&quot;:0,&quot;Blanks&quot;:0},{&quot;Language&quot;:&quot;Wolfram&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:20,&quot;Comments&quot;:0,&quot;Blanks&quot;:3},{&quot;Language&quot;:&quot;MoonScript&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:18,&quot;Comments&quot;:0,&quot;Blanks&quot;:12},{&quot;Language&quot;:&quot;Lingua Franca&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:16,&quot;Comments&quot;:0,&quot;Blanks&quot;:0},{&quot;Language&quot;:&quot;FORTRAN Legacy&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:13,&quot;Comments&quot;:0,&quot;Blanks&quot;:0},{&quot;Language&quot;:&quot;F#&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:9,&quot;Comments&quot;:2,&quot;Blanks&quot;:2},{&quot;Language&quot;:&quot;Mlatu&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:8,&quot;Comments&quot;:0,&quot;Blanks&quot;:0},{&quot;Language&quot;:&quot;Racket&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:6,&quot;Comments&quot;:1,&quot;Blanks&quot;:0},{&quot;Language&quot;:&quot;Xcode Config&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:4,&quot;Comments&quot;:0,&quot;Blanks&quot;:0},{&quot;Language&quot;:&quot;Raku&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:4,&quot;Comments&quot;:0,&quot;Blanks&quot;:1},{&quot;Language&quot;:&quot;Pyret&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:3,&quot;Comments&quot;:0,&quot;Blanks&quot;:0},{&quot;Language&quot;:&quot;MDX&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:0,&quot;Comments&quot;:49844,&quot;Blanks&quot;:12807},{&quot;Language&quot;:&quot;Plain Text&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:0,&quot;Comments&quot;:55756858,&quot;Blanks&quot;:2132456},{&quot;Language&quot;:&quot;Standard ML (SML)&quot;,&quot;Files&quot;:&quot;&quot;,&quot;Lines&quot;:0,&quot;Code&quot;:0,&quot;Comments&quot;:38,&quot;Blanks&quot;:0},{&quot;Language&quot;:&quot;Gherkin (Cucumber)&quot;,&quot;Files&quot;:&quot;&quot;,&quot;Lines&quot;:0,&quot;Code&quot;:0,&quot;Comments&quot;:523700,&quot;Blanks&quot;:8855},{&quot;Language&quot;:&quot;Bean&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:0,&quot;Comments&quot;:0,&quot;Blanks&quot;:0},{&quot;Language&quot;:&quot;Markdown&quot;,&quot;Files&quot;:0,&quot;Lines&quot;:0,&quot;Code&quot;:0,&quot;Comments&quot;:35203447,&quot;Blanks&quot;:16642677}]
    // Create charts
    createComplexityChart(complexity, fileAnalysis);
    createCodeQualityChart(codeMetrics, fileAnalysis);
    createPopularityBubbleChart(dependencies);
    createCyclomaticDistributionChart(fileAnalysis);
    createLOCDistributionChart(fileAnalysis);
    createDownloadDominanceChart(packageStats.download_dominance);
    createUnderratedPackagesChart(packageStats.underrated_packages);
    createLanguageGraph(languages);
}

// Complexity distribution chart
function createComplexityChart(complexity, fileAnalysis) {
  const ctx = document.getElementById(&apos;complexity-chart&apos;).getContext(&apos;2d&apos;);
  
  new Chart(ctx, {
    type: &apos;bar&apos;,
    data: {
      labels: [&apos;Cyclomatic Complexity&apos;, &apos;Lines of Code&apos;],
      datasets: [
        {
          label: &apos;Average&apos;,
          data: [fileAnalysis.avg_cyclomatic_per_file, complexity.avg_loc],
          backgroundColor: &apos;rgba(99, 102, 241, 0.6)&apos;,
          borderColor: &apos;rgb(99, 102, 241)&apos;,
          borderWidth: 1
        },
        {
          label: &apos;Median&apos;,
          data: [fileAnalysis.median_cyclomatic, fileAnalysis.median_loc],
          backgroundColor: &apos;rgba(139, 92, 246, 0.6)&apos;,
          borderColor: &apos;rgb(139, 92, 246)&apos;,
          borderWidth: 1
        }
      ]
    },
    options: {
      responsive: true,
      maintainAspectRatio: false,
      plugins: {
        title: {
          display: false
        },
        legend: {
          position: &apos;top&apos;
        },
        tooltip: {
          callbacks: {
            label: function(context) {
              return `${context.dataset.label}: ${context.raw.toFixed(1)}`;
            }
          }
        }
      },
      scales: {
        y: {
          beginAtZero: true
        }
      }
    }
  });
}

// Code quality chart
function createCodeQualityChart(codeMetrics, fileAnalysis) {
const ctx = document.getElementById(&apos;code-quality-chart&apos;).getContext(&apos;2d&apos;);

// Normalize data to improve visualization
const miNormalized = codeMetrics.avg_mi / 100 * 10; // Maintainability index normalized to 10
const acNormalized = Math.min(codeMetrics.avg_afferent_coupling / 5, 10); // Limited to 10
const ecNormalized = Math.min(codeMetrics.avg_efferent_coupling / 5, 10); // Limited to 10
const filesNormalized = Math.min(codeMetrics.avg_files / 5, 10); // Limited to 10
const ccNormalized = Math.min(fileAnalysis.avg_cyclomatic_per_file, 10); // Limited to 10

new Chart(ctx, {
    type: &apos;radar&apos;,
    data: {
    labels: [
        &apos;Maintainability Index&apos;, 
        &apos;Cyclomatic Complexity&apos;, 
        &apos;Afferent Coupling&apos;, 
        &apos;Efferent Coupling&apos;, 
        &apos;Files per Package&apos;
    ],
    datasets: [{
        label: &apos;Code Quality&apos;,
        data: [
        miNormalized,
        ccNormalized,
        acNormalized,
        ecNormalized,
        filesNormalized
        ],
        fill: true,
        backgroundColor: &apos;rgba(99, 102, 241, 0.3)&apos;,
        borderColor: &apos;rgb(99, 102, 241)&apos;,
        pointBackgroundColor: &apos;rgb(99, 102, 241)&apos;,
        pointBorderColor: &apos;#fff&apos;,
        pointHoverBackgroundColor: &apos;#fff&apos;,
        pointHoverBorderColor: &apos;rgb(99, 102, 241)&apos;
    },
    // {
    //     label: &apos;Ideal&apos;,
    //     data: [10, 5, acNormalized, ecNormalized, filesNormalized],
    //     fill: true,
    //     backgroundColor: &apos;rgba(16, 185, 129, 0.1)&apos;,
    //     borderColor: &apos;rgba(16, 185, 129, 0.8)&apos;,
    //     borderWidth: 2,
    //     borderDash: [5, 5],
    //     pointBackgroundColor: &apos;rgba(16, 185, 129, 0.8)&apos;,
    //     pointBorderColor: &apos;#fff&apos;,
    //     pointHoverBackgroundColor: &apos;#fff&apos;,
    //     pointHoverBorderColor: &apos;rgb(16, 185, 129)&apos;
    // }
    ]
    },
    options: {
    responsive: true,
    maintainAspectRatio: false,
    plugins: {
        legend: {
        display: true,
        position: &apos;top&apos;
        },
        tooltip: {
        callbacks: {
            label: function(context) {
            const labels = [
                `MI: ${codeMetrics.avg_mi.toFixed(1)}`,
                `CC: ${fileAnalysis.avg_cyclomatic_per_file.toFixed(1)}`,
                `AC: ${codeMetrics.avg_afferent_coupling.toFixed(1)}`,
                `EC: ${codeMetrics.avg_efferent_coupling.toFixed(1)}`,
                `Files: ${codeMetrics.avg_files.toFixed(1)}`
            ];
            if (context.datasetIndex === 0) {
                return labels[context.dataIndex];
            } else {
                // Reference values for ideal
                const idealLabels = [
                &apos;Ideal Maintainability Index: 85-100&apos;,
                &apos;Ideal Cyclomatic Complexity: &lt;= 5&apos;,
                &apos;There is not ideal value for Afferent Coupling&apos;,
                &apos;There is not ideal value for Efferent Coupling&apos;,
                &apos;There is not ideal value for Files per Package&apos;
                ];
                return idealLabels[context.dataIndex];
            }
            }
        }
        }
    },
    scales: {
        r: {
        min: 0,
        max: 10,
        ticks: {
            display: false
        },
        pointLabels: {
            font: {
            size: 12,
            weight: &apos;bold&apos;
            }
        }
        }
    }
    }
});
}






// Version corrigée de la fonction createPopularityBubbleChart pour éviter la superposition des bulles
function createPopularityBubbleChart(dependencies) {
const ctx = document.getElementById(&apos;popularity-bubble-chart&apos;).getContext(&apos;2d&apos;);

// Prendre les 30 premiers paquets par usage
const topDependencies = dependencies.slice(0, 30);

// Appliquer un algorithme pour éviter la superposition des bulles
const processedData = applyBubbleCollisionAvoidance(topDependencies);

// Créer le graphique
const chart = new Chart(ctx, {
type: &apos;bubble&apos;,
data: {
  datasets: [{
    label: &apos;Packages populaires&apos;,
    data: processedData,
    backgroundColor: function(context) {
      // Calcul d&apos;une couleur basée sur l&apos;usage et les étoiles
      const usage = context.raw.y;
      const stars = context.raw.x;
      
      // Coefficient de popularité normalisé (0-1)
      const popularity = Math.min(1, Math.log(usage + stars + 1) / 15);
      
      // Dégradé de couleur de bleu foncé à orange
      return `rgba(${Math.floor(25 + popularity * 230)}, ${Math.floor(100 + popularity * 105)}, ${Math.floor(230 - popularity * 190)}, 0.7)`;
    }
  }]
},
options: {
  responsive: true,
  maintainAspectRatio: false,
  scales: {
    x: {
      title: {
        display: true,
        text: &apos;GitHub Stars&apos;
      },
      type: &apos;logarithmic&apos;,
      min: 1
    },
    y: {
      title: {
        display: true,
        text: &apos;Number of dependencies&apos;
      },
      type: &apos;logarithmic&apos;,
      min: 1
    }
  },
  plugins: {
    tooltip: {
      callbacks: {
        label: function(context) {
          const packageName = context.raw.name;
          return [
            `Package: ${packageName}`,
            `Stars: ${context.raw.x.toLocaleString()}`,
            `Usage: ${context.raw.y.toLocaleString()}`,
            `Forks: ${Math.pow(context.raw.r / 3, 2).toLocaleString()}`
          ];
        }
      }
    },
    title: {
      display: true,
      text: &apos;Popularity of Packages Chart&apos;
    },
    legend: {
      display: false
    }
  }
}
});

return chart;
}

// Fonction pour éviter la superposition des bulles
function applyBubbleCollisionAvoidance(dependencies) {
// Créer les données pour le graphique à bulles
const bubbleData = dependencies.map(dep =&gt; ({
x: dep.stars || 1, // Utiliser au moins 1 pour l&apos;échelle logarithmique
y: dep.usage || 1, // Utiliser au moins 1 pour l&apos;échelle logarithmique
r: Math.min(30, Math.max(5, Math.sqrt(dep.forks || 1) * 2)), // Limiter la taille des bulles
name: dep.name,
// Ajouter les valeurs originales pour référence
originalX: dep.stars || 1,
originalY: dep.usage || 1,
originalR: Math.min(30, Math.max(5, Math.sqrt(dep.forks || 1) * 2))
}));

// Trier par taille de bulle (plus grandes d&apos;abord) pour une meilleure disposition
bubbleData.sort((a, b) =&gt; b.r - a.r);

// Fonction pour vérifier si deux bulles se chevauchent
function bubbleOverlap(b1, b2) {
const distance = Math.sqrt(
  Math.pow(Math.log10(b1.x) - Math.log10(b2.x), 2) + 
  Math.pow(Math.log10(b1.y) - Math.log10(b2.y), 2)
);
// Calculer en échelle logarithmique pour tenir compte de l&apos;échelle du graphique
const minDistance = (b1.r + b2.r) / 100; // Ajuster cette valeur selon vos besoins
return distance &lt; minDistance;
}

// Fonction pour ajuster la position d&apos;une bulle
function adjustBubblePosition(bubble, otherBubbles) {
const MAX_ATTEMPTS = 50;
let attempts = 0;

// Conserver les valeurs originales pour limiter la dérive
const originalLogX = Math.log10(bubble.originalX);
const originalLogY = Math.log10(bubble.originalY);

while (attempts &lt; MAX_ATTEMPTS) {
  let overlap = false;
  
  for (const other of otherBubbles) {
    if (bubbleOverlap(bubble, other)) {
      overlap = true;
      
      // Calculer un vecteur de répulsion (en échelle logarithmique)
      const logX1 = Math.log10(bubble.x);
      const logY1 = Math.log10(bubble.y);
      const logX2 = Math.log10(other.x);
      const logY2 = Math.log10(other.y);
      
      let dx = logX1 - logX2;
      let dy = logY1 - logY2;
      
      // Éviter division par zéro et normaliser
      const len = Math.sqrt(dx * dx + dy * dy) || 0.001;
      dx /= len;
      dy /= len;
      
      // Ajuster la position en échelle logarithmique
      const pushFactor = 0.05; // Ajuster cette valeur au besoin
      const currentLogX = Math.log10(bubble.x);
      const currentLogY = Math.log10(bubble.y);
      
      // Calculer nouvelle position
      const newLogX = currentLogX + dx * pushFactor;
      const newLogY = currentLogY + dy * pushFactor;
      
      // Limiter la dérive par rapport à la position originale
      const maxDrift = 0.5; // Dérive logarithmique maximale autorisée
      const adjustedLogX = Math.max(originalLogX - maxDrift, Math.min(originalLogX + maxDrift, newLogX));
      const adjustedLogY = Math.max(originalLogY - maxDrift, Math.min(originalLogY + maxDrift, newLogY));
      
      // Convertir en échelle linéaire et mettre à jour
      bubble.x = Math.pow(10, adjustedLogX);
      bubble.y = Math.pow(10, adjustedLogY);
      break;
    }
  }
  
  if (!overlap) {
    break;
  }
  
  attempts++;
}

return bubble;
}

// Ajuster les positions des bulles pour éviter les chevauchements
const processedBubbles = [];

for (const bubble of bubbleData) {
const adjustedBubble = adjustBubblePosition(bubble, processedBubbles);
processedBubbles.push(adjustedBubble);
}

return processedBubbles;
}

// Fonction pour créer le graphique de la concentration des téléchargements
function createDownloadDominanceChart(data) {
if (!data || !data.packages || data.packages.length === 0) {
console.warn(&quot;Aucune donnée disponible pour le graphique de concentration des téléchargements&quot;);
return;
}

const ctx = document.getElementById(&apos;download-dominance-chart&apos;).getContext(&apos;2d&apos;);

// Limiter aux 10 premiers packages pour une meilleure lisibilité
const topPackages = data.packages.slice(0, 10);

// Calculer le cumul des pourcentages
let cumulativePercentage = 0;
const cumulativeData = topPackages.map(pkg =&gt; {
cumulativePercentage += pkg.percentage;
return cumulativePercentage;
});

new Chart(ctx, {
type: &apos;bar&apos;,
data: {
  labels: topPackages.map(pkg =&gt; pkg.name),
  datasets: [
    {
      type: &apos;bar&apos;,
      label: &apos;Téléchargements (%)&apos;,
      data: topPackages.map(pkg =&gt; pkg.percentage),
      backgroundColor: &apos;rgba(79, 70, 229, 0.6)&apos;,
      borderColor: &apos;rgb(79, 70, 229)&apos;,
      borderWidth: 1,
      order: 2
    },
    {
      type: &apos;line&apos;,
      label: &apos;Pourcentage cumulé&apos;,
      data: cumulativeData,
      borderColor: &apos;rgba(239, 68, 68, 0.8)&apos;,
      backgroundColor: &apos;rgba(239, 68, 68, 0.1)&apos;,
      borderWidth: 2,
      fill: true,
      tension: 0.4,
      order: 1,
      yAxisID: &apos;percentage&apos;
    }
  ]
},
options: {
  responsive: true,
  maintainAspectRatio: false,
  plugins: {
    title: {
      display: true,
      text: &apos;Concentration des téléchargements mensuels&apos;
    },
    tooltip: {
      callbacks: {
        label: function(context) {
          if (context.datasetIndex === 0) {
            return `${context.dataset.label}: ${context.parsed.y.toFixed(2)}%`;
          } else {
            return `${context.dataset.label}: ${context.parsed.y.toFixed(2)}%`;
          }
        }
      }
    }
  },
  scales: {
    x: {
      ticks: {
        maxRotation: 45,
        minRotation: 45
      }
    },
    y: {
      beginAtZero: true,
      title: {
        display: true,
        text: &apos;% des téléchargements&apos;
      }
    },
    percentage: {
      position: &apos;right&apos;,
      beginAtZero: true,
      max: 100,
      title: {
        display: true,
        text: &apos;% cumulé&apos;
      }
    }
  }
}
});
}

// Fonction pour créer le graphique des packages sous-reconnus
function createUnderratedPackagesChart(data) {
if (!data || data.length === 0) {
console.warn(&quot;Aucune donnée disponible pour le graphique des packages sous-reconnus&quot;);
return;
}

const ctx = document.getElementById(&apos;underrated-packages-chart&apos;).getContext(&apos;2d&apos;);

// Limiter aux 15 premiers packages pour une meilleure lisibilité
const topPackages = data.slice(0, 15);

new Chart(ctx, {
type: &apos;scatter&apos;,
data: {
  datasets: [{
    label: &apos;Underrated Packages&apos;,
    data: topPackages.map(pkg =&gt; ({
      x: pkg.stars,
      y: pkg.downloads_monthly,
      r: Math.sqrt(pkg.download_star_ratio) / 2
    })),
    backgroundColor: function(context) {
      const value = context.raw.r;
      // Plus le ratio est élevé, plus la couleur est intense
      return `rgba(220, 38, 38, ${Math.min(0.8, 0.3 + value / 20)})`;
    },
    pointRadius: topPackages.map(pkg =&gt; Math.min(20, Math.max(5, Math.sqrt(pkg.download_star_ratio) / 2)))
  }]
},
options: {
  responsive: true,
  maintainAspectRatio: false,
  plugins: {
    title: {
      display: true,
      text: &apos;Packages with high downloads but few stars&apos;
    },
    tooltip: {
      callbacks: {
        label: function(context) {
          const package = topPackages[context.dataIndex];
          return [
            `Package: ${package.name}`,
            `Stars: ${formatNumber(package.stars)}`,
            `Monthly downloads: ${formatNumber(package.downloads_monthly)}`,
            `Ratio downloads./stars: ${formatNumber(package.download_star_ratio.toFixed(1))}`
          ];
        }
      }
    },
    legend: {
      display: false
    }
  },
  scales: {
    x: {
      type: &apos;logarithmic&apos;,
      title: {
        display: true,
        text: &apos;GitHub Stars (log. scale)&apos;
      },
      min: 1
    },
    y: {
      type: &apos;logarithmic&apos;,
      title: {
        display: true,
        text: &apos;Monthly downloads (log. scale)&apos;
      },
      min: 1000
    }
  }
}
});
}

function createLOCDistributionChart(dataset) {
const locDistribution = dataset.loc_distribution;
const ctx = document.getElementById(&apos;loc-distribution-chart&apos;).getContext(&apos;2d&apos;);

// Extraction des données du JSON
const labels = Object.keys(locDistribution);
const data = Object.values(locDistribution);

// Tri des étiquettes en ordre numérique
const sortedIndices = labels.map((label, index) =&gt; ({ label, index }))
  .sort((a, b) =&gt; {
    // Extraction des valeurs numériques des plages
    const getMinValue = range =&gt; parseInt(range.split(&apos;-&apos;)[0]);
    return getMinValue(a.label) - getMinValue(b.label);
  });

const sortedLabels = sortedIndices.map(item =&gt; item.label);
const sortedData = sortedIndices.map(item =&gt; data[item.index]);

// Création du graphique
new Chart(ctx, {
  type: &apos;bar&apos;,
  data: {
    labels: sortedLabels,
    datasets: [{
      label: &apos;Nombre de fichiers&apos;,
      data: sortedData,
      backgroundColor: &apos;rgba(79, 70, 229, 0.6)&apos;,
      borderColor: &apos;rgb(79, 70, 229)&apos;,
      borderWidth: 1
    }]
  },
  options: {
    responsive: true,
    maintainAspectRatio: false,
    plugins: {
      title: {
        display: true,
        text: &apos;Distribution of files by lines of code&apos;
      },
      legend: {
        display: false
      },
      tooltip: {
        callbacks: {
          label: function(context) {
            return `Nombre de fichiers: ${formatNumber(context.raw)}`;
          },
          title: function(tooltipItems) {
            return `Plage de LOC: ${tooltipItems[0].label}`;
          }
        }
      }
    },
    scales: {
      y: {
        beginAtZero: true,
        title: {
          display: true,
          text: &apos;Number of files&apos;
        },
        ticks: {
          callback: function(value) {
            return formatNumber(value);
          }
        }
      },
      x: {
        title: {
          display: true,
          text: &apos;Logical lines of code&apos;
        }
      }
    }
  }
});
}

// Fonction pour créer le graphique de distribution de la complexité cyclomatique
function createCyclomaticDistributionChart(dataset) {
const  cyclomaticDistribution  = dataset.cyclomatic_distribution;
const ctx = document.getElementById(&apos;cyclomatic-distribution-chart&apos;).getContext(&apos;2d&apos;);

// Extraction des données du JSON
const labels = Object.keys(cyclomaticDistribution);
const data = Object.values(cyclomaticDistribution);

// Tri des étiquettes en ordre numérique
const sortedIndices = labels.map((label, index) =&gt; ({ label, index }))
  .sort((a, b) =&gt; {
    // Extraction des valeurs numériques des plages
    const getMinValue = range =&gt; parseInt(range.split(&apos;-&apos;)[0]);
    return getMinValue(a.label) - getMinValue(b.label);
  });

const sortedLabels = sortedIndices.map(item =&gt; item.label);
const sortedData = sortedIndices.map(item =&gt; data[item.index]);

// Création du graphique
new Chart(ctx, {
  type: &apos;bar&apos;,
  data: {
    labels: sortedLabels,
    datasets: [{
      label: &apos;Nombre de fichiers&apos;,
      data: sortedData,
      backgroundColor: function(context) {
        // Gradient de couleurs selon la complexité
        // Plus la complexité est élevée, plus la couleur est rouge
        const index = context.dataIndex;
        const complexity = parseInt(sortedLabels[index].split(&apos;-&apos;)[0]);
        
        if (complexity &lt; 10) return &apos;rgba(16, 185, 129, 0.6)&apos;; // vert pour faible complexité
        if (complexity &lt; 30) return &apos;rgba(245, 158, 11, 0.6)&apos;; // jaune pour complexité moyenne
        if (complexity &lt; 60) return &apos;rgba(249, 115, 22, 0.6)&apos;; // orange pour complexité élevée
        return &apos;rgba(239, 68, 68, 0.6)&apos;; // rouge pour complexité très élevée
      },
      borderColor: function(context) {
        const index = context.dataIndex;
        const complexity = parseInt(sortedLabels[index].split(&apos;-&apos;)[0]);
        
        if (complexity &lt; 10) return &apos;rgb(16, 185, 129)&apos;;
        if (complexity &lt; 30) return &apos;rgb(245, 158, 11)&apos;;
        if (complexity &lt; 60) return &apos;rgb(249, 115, 22)&apos;;
        return &apos;rgb(239, 68, 68)&apos;;
      },
      borderWidth: 1
    }]
  },
  options: {
    responsive: true,
    maintainAspectRatio: false,
    plugins: {
      title: {
        display: true,
        textEn: &apos;Distribution of files by cyclomatic complexity&apos;
      },
      legend: {
        display: false
      },
      tooltip: {
        callbacks: {
          label: function(context) {
            return `Nombre de fichiers: ${formatNumber(context.raw)}`;
          },
          title: function(tooltipItems) {
            return `Complexité: ${tooltipItems[0].label}`;
          }
        }
      }
    },
    scales: {
      y: {
        beginAtZero: true,
        title: {
          display: true,
          text: &apos;Number of files&apos;
        },
        ticks: {
          callback: function(value) {
            return formatNumber(value);
          }
        }
      },
      x: {
        title: {
          display: true,
          text: &apos;Cyclomatic complexity&apos;
        }
      }
    }
  }
});
}

function createLanguageGraph(languages) {
    // Récupération du JSON depuis la page HTML

    // Trier les langages par nombre de lignes de code en ordre décroissant
    const sortedLanguages = languages
        .filter(lang =&gt; lang.Code &gt; 0) // Exclure les langages avec 0 lignes de code
        .sort((a, b) =&gt; b.Code - a.Code) // Trier du plus grand au plus petit
        .slice(0, 10);

    // Extraire les noms et les valeurs
    const labels = sortedLanguages.map(lang =&gt; lang.Language);
    const dataValues = sortedLanguages.map(lang =&gt; lang.Code);

    // Sélection de la div pour afficher le graphique
    const ctx = document.getElementById(&quot;chart-languages&quot;).getContext(&quot;2d&quot;);

    // Création du camembert avec Chart.js
    new Chart(ctx, {
        type: &quot;pie&quot;,
        data: {
            labels: labels,
            datasets: [{
                data: dataValues,
                // gradient of blue
                backgroundColor: labels.map((_, i) =&gt; `rgba(99, 102, 241, ${0.6 - i * 0.05})`),
                borderWidth: 1
            }]
        },
        options: {
            responsive: true,
            plugins: {
                legend: {
                    position: &quot;bottom&quot;
                }
            }
        }
    });
}

// Adapter le DOMContentLoaded pour notre nouvelle approche
document.addEventListener(&apos;DOMContentLoaded&apos;, function() {
loadData();
});


&lt;/script&gt;</content>
 </entry>
 
 <entry>
   <title>Yet another static analysis tool. Yes, but better!</title>
   <link href="https://blog.lepine.pro/en/ast-metrics-static-analysis/"/>
   <updated>2024-04-18T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/en/ast-metrics-static-analysis</id>
   <content type="html">&lt;p&gt;10 years after starting the development of &lt;a href=&quot;https://github.com/Phpmetrics/PhpMetrics&quot;&gt;PHP Metrics&lt;/a&gt;, I think it’s time to start something new, more modern… and more ambitious.&lt;/p&gt;

&lt;h2 id=&quot;ast-metrics&quot;&gt;AST Metrics&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/Halleck45/ast-metrics/&quot;&gt;AST Metrics&lt;/a&gt; is a tool, written in Go, for static code analysis.
It is a &lt;strong&gt;performant&lt;/strong&gt;, &lt;strong&gt;simple&lt;/strong&gt;, &lt;strong&gt;language-agnostic&lt;/strong&gt; tool.&lt;/p&gt;

&lt;p&gt;Why Go? &lt;strong&gt;First and foremost for performance&lt;/strong&gt;. Where most code analyzers take several minutes, &lt;strong&gt;AST Metrics only takes a few seconds to parse several million lines of code and tens of thousands of commits.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And then for fun: I’ve wanted to learn Go for a long time, and I thought this was a good opportunity.&lt;/p&gt;

&lt;h2 id=&quot;why-a-new-tool&quot;&gt;Why a new tool?&lt;/h2&gt;

&lt;p&gt;Code analysis involves traversing the source code, transforming it into an &lt;a href=&quot;https://en.wikipedia.org/wiki/Abstract_syntax_tree&quot;&gt;abstract syntax tree (AST)&lt;/a&gt;, and analyzing this tree to extract metrics.&lt;/p&gt;

&lt;p&gt;Among the most common metrics are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;code complexity (the number of decision points);&lt;/li&gt;
  &lt;li&gt;maintainability index;&lt;/li&gt;
  &lt;li&gt;coupling between classes;&lt;/li&gt;
  &lt;li&gt;etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;My vision is to make these metrics readable and understandable to as many people as possible&lt;/strong&gt;, and to make them accessible to all developers.&lt;/p&gt;

&lt;p&gt;I want to produce something that is &lt;strong&gt;easy to use&lt;/strong&gt; and &lt;strong&gt;attractive&lt;/strong&gt;, &lt;strong&gt;performant&lt;/strong&gt;, and &lt;strong&gt;easy to install&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Think of AST Metrics as a linter on the architecture of your code, which allows you to detect quality problems before they become problems.&lt;/p&gt;

&lt;h2 id=&quot;how-does-it-work&quot;&gt;How does it work?&lt;/h2&gt;

&lt;p&gt;Installation is quite simple. You just need to fetch a binary from Github. &lt;strong&gt;No dependencies&lt;/strong&gt;, no complicated installation, no configuration file to edit.&lt;/p&gt;

&lt;p&gt;In the command line, run the following command:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl -s https://raw.githubusercontent.com/Halleck45/ast-metrics/main/scripts/download.sh|bash
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Be careful, as with any command found on the Internet, read the script before running it.&lt;/p&gt;

&lt;p&gt;If you prefer a manual installation, &lt;a href=&quot;https://halleck45.github.io/ast-metrics/getting-started/install/&quot;&gt;everything is explained here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Then run the following command to analyze, for example, your project &lt;code&gt;/www/myproject&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ast-metrics analyze /www/myproject --non-interactive --report-html=/tmp/report 
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;An HTML report will be generated in the file &lt;code&gt;/tmp/report/index.html&lt;/code&gt;, which you can open in your browser.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://halleck45.github.io/ast-metrics/images/preview-html.webp&quot; alt=&quot;preview&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Note that I used the &lt;code&gt;--non-interactive&lt;/code&gt; option for simplicity in this blog post. If you don’t add it, a CLI application will allow you to navigate among the different metrics.&lt;/p&gt;

&lt;h2 id=&quot;lint-your-code&quot;&gt;Lint your code&lt;/h2&gt;

&lt;p&gt;Of course, AST Metrics goes further. You can, for example, ensure that your code does not exceed certain thresholds.&lt;/p&gt;

&lt;p&gt;Generate a &lt;code&gt;.ast-metrics.yaml&lt;/code&gt; configuration file in your project by running the following command:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ast-metrics init
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And edit it to add your thresholds:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;sources:
  - /www/myproject

exclude:
  - /vendor/
  - /node_modules/

reports:
  html: ./build/report
  markdown: ./build/report.md

requirements:
  rules:
    fail_on_error: true

    maintainability:
      min: 85
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;From now on, the analysis will fail if the maintainability of your code is less than 85.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ast-metrics analyze --non-interactive
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You can also control cyclomatic complexity, coupling between classes, etc.&lt;/p&gt;

&lt;p&gt;For example, to forbid too complex code:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;requirements:
  rules:
    fail_on_error: true
    cyclomatic_complexity:
      max: 10
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Or to check the coupling between classes:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;requirements:
  rules:
    fail_on_error: true
    coupling:
      forbidden:
        - from: &quot;Controller&quot;
          to: &quot;Repository&quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now, if a controller depends on a repository, the analysis will fail (note that these are regular expressions that are used here).&lt;/p&gt;

&lt;p&gt;This is very useful, for example, if you want to ensure that your code respects the architecture principles you have defined with your colleagues.&lt;/p&gt;

&lt;h2 id=&quot;and-continuous-integration&quot;&gt;And continuous integration?&lt;/h2&gt;

&lt;p&gt;AST Metrics is designed to be used in a CI/CD pipeline.&lt;/p&gt;

&lt;p&gt;For example, for Github, you just need to add the &lt;a href=&quot;https://halleck45.github.io/ast-metrics/ci/github-actions/&quot;&gt;Github action&lt;/a&gt; that is already ready to use for you:&lt;/p&gt;

&lt;p&gt;In the file &lt;code&gt;.github/workflows/ast-metrics.yml&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;name: AST Metrics
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
        - name: AST Metrics
          uses: halleck45/action-ast-metrics@v1.0.2
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And that’s it, with each push, your code will be analyzed, and you will receive a complete report. If you have defined
thresholds, the CI will fail if these thresholds are not met.&lt;/p&gt;

&lt;p&gt;For example, here is a report generated by the CI in Github:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/Halleck45/action-ast-metrics/main/docs/preview.png&quot; alt=&quot;example of CI&quot; /&gt;&lt;/p&gt;

&lt;p&gt;To go further, do not hesitate to consult the &lt;a href=&quot;https://halleck45.github.io/ast-metrics/&quot;&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;and-whats-next&quot;&gt;And what’s next?&lt;/h2&gt;

&lt;p&gt;The project is still in its infancy, and experimental. But promising, I think!&lt;/p&gt;

&lt;p&gt;The next step is to better support other programming languages, and, in an ideal world, to add trends.
There is also a lot of work to be done on error handling, improving the tool…&lt;/p&gt;

&lt;p&gt;In the long run, I would like to add two AIs: a generative AI, to give refactoring advice, and a predictive AI, to predict bugs and risky commits.&lt;/p&gt;

&lt;p&gt;I would like this project to grow and offer the maximum number of features and services. &lt;strong&gt;And for that, I need help!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you want to help, the best thing to do is to test the tool and talk about it around you. Thank you!&lt;/strong&gt; And don’t hesitate to tell me
what you think, if you find bugs, or &lt;a href=&quot;https://github.com/sponsors/Halleck45&quot;&gt;even to encourage me by offering me a ☕ coffee&lt;/a&gt;. It’s always nice to have feedback, whatever it is.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Encore un outil d'analyse statique. Oui, mais en mieux !</title>
   <link href="https://blog.lepine.pro/ast-metrics-analyse-statique"/>
   <updated>2024-04-18T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/ast-metrics-analyse-statique</id>
   <content type="html">&lt;p&gt;10 ans après avoir démarré le développement de &lt;a href=&quot;https://github.com/Phpmetrics/PhpMetrics&quot;&gt;PHP Metrics&lt;/a&gt;, je crois
qu’il est temps de démarrer quelque chose de nouveau, de plus moderne… et de plus ambitieux.&lt;/p&gt;

&lt;h2 id=&quot;ast-metrics&quot;&gt;AST Metrics&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/Halleck45/ast-metrics/&quot;&gt;AST Metrics&lt;/a&gt; est un outil, écrit en Go, d’analyse statique de code source.
C’est un outil &lt;strong&gt;performant&lt;/strong&gt;, &lt;strong&gt;simple&lt;/strong&gt;, et &lt;strong&gt;agnostique&lt;/strong&gt; du langage de programmation.&lt;/p&gt;

&lt;p&gt;Pourquoi en Go ? &lt;strong&gt;Avant tout pour la performance&lt;/strong&gt;. Là où il faut plusieurs minutes pour la majorité
des analyseurs de code, &lt;strong&gt;il ne faut que quelques secondes à AST Metrics pour parser plusieurs millions de ligne de code et des dizaines de milliers de commits.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ensuite pour le plaisir : je voulais apprendre le Go depuis longtemps, et j’ai trouvé que c’était une bonne occasion.&lt;/p&gt;

&lt;h2 id=&quot;pourquoi-un-nouvel-outil-&quot;&gt;Pourquoi un nouvel outil ?&lt;/h2&gt;

&lt;p&gt;L’analyse de code consiste à parcourir le code source, à le transformer en un &lt;a href=&quot;https://en.wikipedia.org/wiki/Abstract_syntax_tree&quot;&gt;arbre de syntaxe abstraite (AST)&lt;/a&gt; et à analyser cet arbre pour en extraire des métriques.&lt;/p&gt;

&lt;p&gt;Parmi les métriques fréquentes, on trouve :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;la complexité du code (le nombre de points de décision) ;&lt;/li&gt;
  &lt;li&gt;l’indice de maintenabilité ;&lt;/li&gt;
  &lt;li&gt;le couplage entre les classes ;&lt;/li&gt;
  &lt;li&gt;etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Ma vision consiste à rendre ces métriques lisibles et compréhensibles pour le plus grand nombre&lt;/strong&gt;, et à les rendre accessibles à tous les développeurs et développeuses.&lt;/p&gt;

&lt;p&gt;Je souhaite produire quelque chose de &lt;strong&gt;simple à utiliser&lt;/strong&gt; et d’&lt;strong&gt;attrayant&lt;/strong&gt;, de &lt;strong&gt;performant&lt;/strong&gt;, et de &lt;strong&gt;simple à installer&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Voyez AST Metrics comme un linter sur l’architecture de votre code, qui vous permet de détecter les problèmes de qualité avant qu’ils ne deviennent des problèmes.&lt;/p&gt;

&lt;h2 id=&quot;comment-ça-marche-&quot;&gt;Comment ça marche ?&lt;/h2&gt;

&lt;p&gt;L’installation est assez simple. Il s’agit d’aller chercher un binaire sur Github. &lt;strong&gt;Aucune dépendance&lt;/strong&gt;, pas d’installation compliquée, pas de fichier de configuration à éditer.&lt;/p&gt;

&lt;p&gt;En ligne de commande, lancez la commande suivante:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl -s https://raw.githubusercontent.com/Halleck45/ast-metrics/main/scripts/download.sh|bash
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Attention comme toute commande trouvée sur Internet, soyez viligant(e) et lisez le script avant de l’exécuter.&lt;/p&gt;

&lt;p&gt;Si vous préférez une installation manuelle, &lt;a href=&quot;https://halleck45.github.io/ast-metrics/getting-started/install/&quot;&gt;tout est expliqué ici&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Ensuite, lancez la commande suivante pour analyser, par exemple, votre projet &lt;code&gt;/www/myproject&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ast-metrics analyze /www/myproject --non-interactive --report-html=/tmp/report 
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Un rapport HTML sera généré dans le fichier &lt;code&gt;/tmp/report/index.html&lt;/code&gt;, que vous pouvez ouvrir dans votre navigateur.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://halleck45.github.io/ast-metrics/images/preview-html.webp&quot; alt=&quot;preview&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Notez que j’ai utilisé l’option &lt;code&gt;--non-interactive&lt;/code&gt; par simplicité dans ce billet de blog. Si vous ne l’ajoutez pas, une application en mode CLI vous permettra de
naviguer parmi les différentes métriques.&lt;/p&gt;

&lt;h2 id=&quot;linter-votre-code&quot;&gt;Linter votre code&lt;/h2&gt;

&lt;p&gt;Bien sûr, AST Metrics va plus loin. Vous pouvez par exemple vous assurer que votre code ne dépasse pas certains seuils.&lt;/p&gt;

&lt;p&gt;Générez un fichier de configuration &lt;code&gt;.ast-metrics.yaml&lt;/code&gt; dans votre projet, en lançant la commande suivante:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ast-metrics init
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Et éditez-le pour y ajouter vos seuils:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;sources:
  - /www/myproject

exclude:
  - /vendor/
  - /node_modules/

reports:
  html: ./build/report
  markdown: ./build/report.md

requirements:
  rules:
    fail_on_error: true

    maintainability:
      min: 85
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Désormais, l’analyse échouera si la maintenabilité de votre code est inférieure à 85.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ast-metrics analyze --non-interactive
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Vous pouvez également contrôler la complexité cyclomatique, le couplage entre les classes, etc.&lt;/p&gt;

&lt;p&gt;Par exemple pour interdire le code trop complexe:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;requirements:
  rules:
    fail_on_error: true
    cyclomatic_complexity:
      max: 10
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Ou encore pour vérifier le couplage entre les classes:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;requirements:
  rules:
    fail_on_error: true
    coupling:
      forbidden:
        - from: &quot;Controller&quot;
          to: &quot;Repository&quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Désormais, si un contrôleur dépend d’un repository, l’analyse échouera (notez que ce sont ici des expression régulières qui sont utilisées).&lt;/p&gt;

&lt;p&gt;C’est très pratique, par exemple si vous souhaitez vous assurer que votre code respecte les principes d’architecture que vous avez
définis avec vos collègues.&lt;/p&gt;

&lt;h2 id=&quot;et-lintégration-continue-&quot;&gt;Et l’intégration continue ?&lt;/h2&gt;

&lt;p&gt;AST Metrics est conçu pour être utilisé dans un pipeline CI/CD.&lt;/p&gt;

&lt;p&gt;Par exemple, pour Github, il vous suffira d’ajouter la &lt;a href=&quot;https://halleck45.github.io/ast-metrics/ci/github-actions/&quot;&gt;Github action&lt;/a&gt; qui est déjà prête à l’emploi pour vous:&lt;/p&gt;

&lt;p&gt;Dans le fichier &lt;code&gt;.github/workflows/ast-metrics.yml&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;name: AST Metrics
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
        - name: AST Metrics
          uses: halleck45/action-ast-metrics@v1.0.2
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Et voilà, à chaque push, votre code sera analysé et vous recevrez un rapport complet. Si vous avez défini
des seuils, la CI échouera si ces seuils ne sont pas respectés.&lt;/p&gt;

&lt;p&gt;Voici par exemple un rapport généré par la CI dans Github:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/Halleck45/action-ast-metrics/main/docs/preview.png&quot; alt=&quot;exemple de CI&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Pour aller plus loin, n’hésitez pas à consulter la &lt;a href=&quot;https://halleck45.github.io/ast-metrics/&quot;&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;et-la-suite-&quot;&gt;Et la suite ?&lt;/h2&gt;

&lt;p&gt;Le projet est encore balbutiant, et expérimental. Mais prometteur je trouve !&lt;/p&gt;

&lt;p&gt;La suite consiste à mieux supporter d’autres langages de programmation, et, dans un monde idéal, à ajouter des tendances.
Il reste également pas mal de boulot sur la gestion des erreurs, améliorer l’outil…&lt;/p&gt;

&lt;p&gt;A terme, j’aimerai ajouter deux IA: une IA générative, pour donner des conseils au refactoring, et une IA prédictive, pour prédire les bugs et les commits à risque.&lt;/p&gt;

&lt;p&gt;J’aimerai que ce projet grossisse, et puisse offrir le maximum de fonctionnalités et de service. &lt;strong&gt;Et pour ça j’ai besoin d’aide !&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Si vous avez envie d’aider, le mieux reste de tester l’outil et d’en parler autour de vous. Merci !&lt;/strong&gt; Et n’hésitez pas à me dire
ce que vous en pensez, si vous trouvez des bugs, ou &lt;a href=&quot;https://github.com/sponsors/Halleck45&quot;&gt;même à m’encourager en m’offrant un ☕ café&lt;/a&gt;. Ca fait toujours plaisir d’avoir du feedback, quel qu’il soit.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Industrialization and Software Quality</title>
   <link href="https://blog.lepine.pro/en/"/>
   <updated>2024-01-01T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/en</id>
   <content type="html"></content>
 </entry>
 
 <entry>
   <title>Un éditeur de texte pour les enfants dyslexiques</title>
   <link href="https://blog.lepine.pro/dyslexic-editor"/>
   <updated>2023-02-15T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/dyslexic-editor</id>
   <content type="html">&lt;p&gt;La dyslexie est une chose complexe et multiple. On parle d’ailleurs en général de dyslexies, au pluriel.&lt;/p&gt;

&lt;p&gt;C’est un monde que je ne connaissais pas. Mais, depuis deux ans, ma fille est pressentie dyslexique. Au-delà de la 
difficulté à faire les devoirs, il y a la difficulté à trouver les bons outils.&lt;/p&gt;

&lt;p&gt;On a essayé les lunettes spéciale dyslexie, investi dans une lampe pour dyslexiques… Mais, pour les devoirs, nous 
sommes longtemps restés sur des choses peu confortables.&lt;/p&gt;

&lt;p&gt;Les maîtresses de ma fille sont toutes géniales, et font l’effort d’utiliser &lt;a href=&quot;https://lirecouleur.arkaline.fr/&quot;&gt;LireCouleur&lt;/a&gt;, sans doute le seul 
logiciel pour dyslexique accessible en français. Cependant, elles ont du courage. L’interface est un peu lourde, et il manque quelques fonctionnalités essentielles, 
comme la reconnaissance d’image, ou le partage de liens. Cela leur prend beaucoup de temps.&lt;/p&gt;

&lt;p&gt;C’est pour cela (également pour ma veille personnelle) qu’il y a quelques mois, j’ai commencé &lt;a href=&quot;https://captaindys.com/&quot;&gt;CaptainDys.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;C’est un éditeur en ligne, le plus simple possible, pour rendre la lecture de texte la plus confortable possible. 
Parmi les fonctionnalités implémentées, on y retrouvera :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;la lecture vocale ;&lt;/li&gt;
  &lt;li&gt;la mise en couleur des phonèmes ;&lt;/li&gt;
  &lt;li&gt;la reconnaissance vocale ;&lt;/li&gt;
  &lt;li&gt;la reconnaissance d’images (pour importer les devoirs).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;J’aimerais aussi, rapidement, intégrer la possibilité de résumer des textes grâce à OpenAI, que je commence à plutôt bien utiliser au travail.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/captain-dys-preview.png&quot; alt=&quot;aperçu du site captaindys.com&quot; /&gt;&lt;/p&gt;

&lt;p&gt;J’ai choisi d’Open Sourcer cet outil pour permettre à chacun d’en profiter, mais également de contribuer pour l’améliorer.&lt;/p&gt;

&lt;p&gt;Il y a déjà eu un accueil très favorable, et des premières contributions très appréciables !&lt;/p&gt;

&lt;p&gt;C’est un outil simple, en JavaScript. Si vous avez envie de contribuer, ou ne serait-ce que de le tester ou laisser un 
mot d’encouragement, &lt;a href=&quot;https://github.com/Halleck45/captain-dys&quot;&gt;🏕 toute l’aventure se passe sur Github&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Je vous remercie pour votre aide et pour vos partages !&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Boostez vos Échanges de Données entre Services : DataBus, RabbitMQ et Protobuf</title>
   <link href="https://blog.lepine.pro/bus-de-donnees-datapipeline"/>
   <updated>2022-10-28T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/bus-de-donnees-datapipeline</id>
   <content type="html">&lt;p&gt;Je vous propose de parler un peu de Bus de données. Un bus de données permet de transférer des données entre plusieurs logiciels.
Par exemple, entre une application web et un moteur de traitement en masse de données (Big Data). Ou encore entre deux micro-services.&lt;/p&gt;

&lt;p&gt;Nous utilisons RabbitMQ et PHP pour nos bus. Ce n’est pas forcément les technologies les plus adaptées dans votre cas,
mais ça reste très pratique à mettre en œuvre et couvre bien nos besoins.&lt;/p&gt;

&lt;h2 id=&quot;triplets-et-un-peu-de-grammaire&quot;&gt;Triplets, et un peu de grammaire&lt;/h2&gt;

&lt;p&gt;Au quotidien, dans notre &lt;a href=&quot;https://martinfowler.com/bliki/CQRS.html&quot;&gt;architecture CQRS&lt;/a&gt;, tout événement métier sur nos sites web déclenche
l’émission d’un message RabbitMQ.&lt;/p&gt;

&lt;p&gt;Par exemple (en schématisant) voici le code lorsqu’on veut dépublier une offre d’emploi sur l’un de nos sites :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;?php
$event = new JobPostingHasBeenUnublished($jobPosting);
$eventDispatcher-&amp;gt;dispatch($event);
// L&apos;information part alors dans RabbitMQ
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;On a donc un événement métier &lt;code&gt;JobPostingHasBeenUnublished&lt;/code&gt; (&lt;em&gt;Une offre d’emploi a été dépubliée&lt;/em&gt;).
Un événement (du latin evenio), indique que quelque chose s’est passé (&lt;strong&gt;il faut impérativement que la chose dont on parle soit déjà réalisée&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On souhaite alors que cet événement métier déclenche des actions&lt;/strong&gt;, comme l’envoi d’un mail, ou la suppression de l’offre
d’emploi dans un moteur de recherche, mais aussi dans notre moteur de recommendation, etc.&lt;/p&gt;

&lt;p&gt;Chez nous, chaque événement, s’il veut être ingéré dans RabbitMQ, va implémenter une interface qui l’oblige à se décrire sous
la forme d’un &lt;strong&gt;triplet &lt;code&gt;Sujet&lt;/code&gt; + &lt;code&gt;Verbe&lt;/code&gt; + &lt;code&gt;Prédicat&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Par exemple :&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;# triplet qui représente un événement

Utilisateur 123         career.job_publishing.delete        JobPosting 456 
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Un triplet est une expression de la forme &lt;code&gt;Sujet + Verbe + Prédicat&lt;/code&gt;, mais peut prendre des formes complexes :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;?php

// Un triplet simple
$event = new EventLog(&apos;Utilisateur 123&apos;, &apos;career.job_publishing.delete&apos;, &apos;JobPosting 456&apos;);

// Un triplet avec un prédicat complexe
class JobPosting implements Predicate 
{
    public function asPredicate(): array; // @implement me
    public function asPredicateidentifier(): string; // @implement me
}

$jobPosting = new JobPosting(456);
$event = new EventLog(&apos;Utilisateur 123&apos;, &apos;career.job_publishing.delete&apos;, $jobPosting);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;En général chaque élément du triplet est un objet qui implémente la bonne interface.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dès lors, notre moteur va envoyer ce triplet à RabbitMQ.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Un énorme avantage de cette approche, c’est que l’on peut facilement &lt;strong&gt;retrouver l’événement qui a déclenché une action&lt;/strong&gt;, et on
peut le documenter très facilement : lorsqu’on génère notre documentation, savoir si un événement part dans le bus de données
est très facile.&lt;/p&gt;

&lt;h2 id=&quot;standardisation-des-données-via-protobuff&quot;&gt;Standardisation des données via ProtoBuff.&lt;/h2&gt;

&lt;p&gt;À partir de ce moment, ce triplet va être sérializé en JSON via &lt;a href=&quot;./2022-07-13-protobuf-standard-pour-echanger-des-donnes-php-go.md&quot;&gt;ProtoBuf&lt;/a&gt;, et être émis vers RabbitMQ.&lt;/p&gt;

&lt;p&gt;Nous avons fait le choix de fortement rationaliser les noms des messages. Tous se terminent par l’un des suffixes suivants :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;*.*.post&lt;/code&gt; lorsque l’événement implique une création&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;*.*.put&lt;/code&gt; lorsque l’événement implique une mise à jour&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;*.*.delete&lt;/code&gt; lorsque l’événement implique une suppression&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;*.*.hit&lt;/code&gt; lorsque l’événement implique une visite (comme “voir une page”)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pour plus de facilités, les schémas protobuf sont partagés entre les différents services et sont stockés dans un dépôt git séparé,
 récupérables via &lt;a href=&quot;https://getcomposer.org/&quot;&gt;Composer&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;lémission&quot;&gt;L’émission&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;RabbitMQ est un passe-plat&lt;/strong&gt; entre un outil qui émet des informations, et un ou plusieurs outils qui les consomment (&lt;code&gt;consumers&lt;/code&gt;).
Il existe plusieurs stratégies de consommation, mais ce n’est pas l’objet de ce billet.&lt;/p&gt;

&lt;p&gt;En très bref, RabbitMQ permet d’émettre des messages :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;vers une queue&lt;/strong&gt; de messages (&lt;code&gt;queue&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;vers un échangeur&lt;/strong&gt; (&lt;code&gt;exchange&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Une queue est une sorte de base de données temporaire&lt;/strong&gt;, dans les quelles les consumers vont chercher les messages à lire.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Un échangeur est comme un aiguilleur&lt;/strong&gt; : à partir de règles de configuration, il va orienter chaque message vers une,
ou plusieurs, queues. Il est ainsi en général préférable d’émettre via un échangeur plutôt qu’une queue, car ça offre
beaucoup plus d’évolutivité. Un échangeur peut même dupliquer le message, pour l’envoyer dans plusieurs queues à la fois.
&lt;strong&gt;C’est ce qu’on appelle le Multiplexing&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Tout se passe via du mapping très simple, à coup de détection de motifs dans le nom du message (&lt;code&gt;routing key&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2022-10-28-rabbitmq-exchange.png&quot; alt=&quot;Exemple de mapping RabbitMQ&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Ici, les messages qui matchent &lt;code&gt;*.*.post&lt;/code&gt; sont dirigés vers une queue dédiée au traitement métier, 
tandis que les messages &lt;code&gt;*.*.hit&lt;/code&gt; sont dirigés vers un autre échangeur dédié à l’analytics, 
qui lui-même se chargera de les diriger aux bonnes queues.&lt;/p&gt;

&lt;h2 id=&quot;exploiter-le-bus-de-données&quot;&gt;Exploiter le bus de données&lt;/h2&gt;

&lt;p&gt;Dans ce contexte &lt;strong&gt;il devient alors possible de spécialiser certaines briques de consommation&lt;/strong&gt; pour les faire traiter
des sujets bien précis. C’est ce qu’on appelle des &lt;strong&gt;micro-services&lt;/strong&gt; (ici on ne parle pas d’API, mais bel et bien de
services. Parfois ces services peuvent disposer d’une API, mais ce n’est pas une obligation).&lt;/p&gt;

&lt;p&gt;On peut par exemple imaginer un service dédié à l’analytics, un autre à la synchronisation des données avec
un service externe, un autre qui va faire de la consolidation, un autre qui va hydrater une DataPipeline
en vue de l’exploiter en Big data, etc.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2022-10-28-rabbitmq-multiplexing.png&quot; alt=&quot;Exemple de multiplexing&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;En multiplexant le message, d’échangeur en échangeur, on permet à chaque donnée d’être traitée autant de fois que
nécessaire dans le système&lt;/strong&gt;. Chaque service de consommation a une seule responsabilité simple, limitant sa complexité
(la complexité ne porte plus alors sur le code, mais sur la compréhension des flux).&lt;/p&gt;

&lt;h2 id=&quot;choisir-comment-structurer-la-donnée&quot;&gt;Choisir comment structurer la donnée&lt;/h2&gt;

&lt;p&gt;Dans un échange de données, il y a en gros &lt;strong&gt;2 manières de voir la donnée&lt;/strong&gt; (et donc de construire le message RabbitMQ) :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;ou bien elle est très légère&lt;/strong&gt;. Elle ne contient que les infos minimales (un type et identifiant).&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;ou bien elle est suffisante&lt;/strong&gt;. Elle contient en elle-même les infos nécessaires à son traitement.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Chaque solution a ses inconvénients.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Si le message RabbitMQ est léger&lt;/strong&gt;, il consomme moins de RAM. Mais chaque
service de consommation doit avoir accès à une source de données complète (une base ou une API), ce qui renforce la dépendance
entre les services et ralentit le traitement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Si le message RabbitMQ est suffisant&lt;/strong&gt;, il consomme beaucoup de RAM. Il faut être vigilant à ne pas le faire grossir inutilement,
sous peine de saturer les serveurs. Par contre, le service de consommation n’a rien à faire de son côté, il lui suffit de déserialiser
la donnée pour l’exploiter facilement.&lt;/p&gt;

&lt;p&gt;J’avoue que je préfère avoir des données suffisantes. C’est, de mon expérience, de très loin plus simple et performant. C’est
pour ça que j’aime beaucoup ProtoBuf, vu qu’il me permet de sérializer / déserializer des données complètes très facilement,
sans me soucier de la technologie utilisée (PHP, NodeJS, Go…).&lt;/p&gt;

&lt;p&gt;De mon point de vue, devoir aller chercher
des informations complémentaires sur une donnée entraîne nécessairement des “micro-services spaghettis”, qui s’appellent
tous les uns les autres et dans tous les sens. Mais il doit être toutefois possible d’y arriver de manière élégante.&lt;/p&gt;

&lt;h2 id=&quot;quelques-exemples-de-ce-quil-est-possible-de-faire&quot;&gt;Quelques exemples de ce qu’il est possible de faire&lt;/h2&gt;

&lt;p&gt;Les possibilités sont infinies.&lt;/p&gt;

&lt;p&gt;En s’appuyant sur des événements métiers, &lt;strong&gt;on peut avoir une vue en temps réel de ce qui se passe dans une application&lt;/strong&gt;.
Chaque événement étant daté, il suffit de les lire dans l’ordre pour savoir ce qu’il se passe. Voici par exemple une
capture d’un écran de suivi d’activité de production :&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2022-10-28-eventlog-overview.png&quot; alt=&quot;un écran qui, à partir d&apos;événement métier dans RabbitMQ, affiche un historique&quot; /&gt;&lt;/p&gt;

&lt;p&gt;On s’en sert aussi pour &lt;strong&gt;hydrater un moteur de recommendations&lt;/strong&gt;, en temps réel. Le consumer RabbitMQ devient alors
la première brique de notre DataPipeline.&lt;/p&gt;

&lt;p&gt;Encore un dernier exemple : on s’en sert pour &lt;strong&gt;synchroniser des données entre notre système d’information et celui de certains
clients / fournisseurs&lt;/strong&gt;, ce qui permet d’avoir en temps réel des informations à jour partout.&lt;/p&gt;

&lt;h1 id=&quot;limites&quot;&gt;Limites&lt;/h1&gt;

&lt;p&gt;Ce paradigme a toutefois quelques limites.&lt;/p&gt;

&lt;p&gt;D’abord, en termes de &lt;strong&gt;cohérence de données&lt;/strong&gt;. S’appuyer sur RabbitMQ est facile, mais il existe d’autres types de
bus plus “solides”. Par exemple, RabbitMQ ne garantie pas que les événements seront reçus dans l’ordre d’émission.
Le message RabbitMQ d’une suppression d’utilisateur peut être reçu avant même de recevoir celui de création !&lt;/p&gt;

&lt;p&gt;Le risque est minime dans un contexte à faible charge, mais sur de très gros volumes d’événements le risque est là.&lt;/p&gt;

&lt;p&gt;Ensuite, en termes de code même : il nécessite de &lt;strong&gt;bien cibler ce qu’il est pertinent d’émettre dans le Bus&lt;/strong&gt;. On est
vite tentés de tout “bourrer” dans le bus de données, au cas où on en aurait besoin un jour. Il ne faut pas
oublier que toute donnée utilise de la RAM et du CPU, qu’il convient d’utiliser avec parcimonie.&lt;/p&gt;

&lt;h1 id=&quot;en-bref&quot;&gt;En bref&lt;/h1&gt;

&lt;p&gt;Une fois cela dit, c’est un pattern de conception extrêmement pratique et simple à mettre en oeuvre.&lt;/p&gt;

&lt;p&gt;Il &lt;strong&gt;facilite la scalabilité&lt;/strong&gt;, en rendant les traitements asynchrones simples et systématiques, et &lt;strong&gt;il rend possible l’isolation
des responsabilités au sein des micro-services.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ce n’est pas une solution miracle, mais pour l’avoir mis en place sur plusieurs gros projets, dans différents contextes pour
lesquels il me semblait adapté, il rend bien service !&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>ProtoBuf in PHP for ultra-efficient and agnostic serialization</title>
   <link href="https://blog.lepine.pro/en/protobuf-php-go/"/>
   <updated>2022-07-13T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/en/protobuf-php-go</id>
   <content type="html">&lt;p&gt;Today I want to talk about a tool that I now use almost every day: &lt;a href=&quot;https://developers.google.com/protocol-buffers&quot;&gt;Protocol Buffers&lt;/a&gt; (or ProtoBuf for short).&lt;/p&gt;

&lt;p&gt;Contrary to popular belief, it is perfectly possible (and efficient!) to use ProtoBuf in PHP.&lt;/p&gt;

&lt;h2 id=&quot;-what-is-protobuf&quot;&gt;❔ What is ProtoBuf?&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;ProtoBuf is&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;A standard for exchanging data&lt;/strong&gt; (to structure and serialize it);&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;A code generator&lt;/strong&gt; (Java, PHP, Go…) to process this data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My use case is quite basic: I need to transmit information between several microservices, via a RabbitMQ bus.
So I use ProtoBuf for that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We will exchange data between a PHP application and a Go application&lt;/strong&gt;. Let’s see how it works! 🎉&lt;/p&gt;

&lt;h2 id=&quot;-the-standard&quot;&gt;📄 The standard&lt;/h2&gt;

&lt;p&gt;If you have looked at the official website, you see the word “Google” everywhere. Don’t panic, it’s still very interoperable.
The coupling to Google is quite non-existent, and the technology is used by many different actors. Google is mainly at the initiative of the project.&lt;/p&gt;

&lt;p&gt;The idea behind all this is to describe data via &lt;code&gt;.proto&lt;/code&gt; files, which are standardized and agnostic. From these files,
any data will be serialized and deserialized, in binary or JSON.&lt;/p&gt;

&lt;p&gt;For a basic example, we will describe a simple message, of type blog post:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-protobuf&quot;&gt;# file src/BlogPost.proto

syntax = &quot;proto3&quot;;
message BlogPost {
  string uuid = 1;
  string title = 2;
  string content = 3;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This is a simple message, which contains a title and content. &lt;strong&gt;Each attribute is associated with a position&lt;/strong&gt; (1, 2, 3, …), which should never
change over time. &lt;strong&gt;It is on this that serialization and deserialization are based&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Let’s continue with our &lt;code&gt;BlogPost&lt;/code&gt;, in order to add tags and an author (in a rather simplistic way, but the idea is there):&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-protobuf&quot;&gt;# file src/User.proto

syntax = &quot;proto3&quot;;
message User {
    string uuid = 1;
    string name = 2;
    optional string avatar = 3;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&quot;language-protobuf&quot;&gt;# file src/Tag.proto

syntax = &quot;proto3&quot;;
message Tag {
    string label = 1;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Let’s modify the &lt;code&gt;BlogPost&lt;/code&gt; to connect everything. The file now looks like:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-protobuf&quot;&gt;syntax = &quot;proto3&quot;;

import &quot;src/User.proto&quot;;
import &quot;src/Tag.proto&quot;;

message BlogPost {
  string uuid = 1;
  string title = 2;
  string content = 3;
  User author = 4;
  repeated Tag tags = 5;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We can have as many tags as we want, via the &lt;code&gt;repeated&lt;/code&gt; instruction.&lt;/p&gt;

&lt;p&gt;We will finally add a publication date to our BlogPost. To do this, we will need to import the
&lt;code&gt;timestamp&lt;/code&gt; type, which is native, but must be imported if you want to use it. There are quite a few types, I let you
discover them &lt;a href=&quot;https://developers.google.com/protocol-buffers/docs/proto3&quot;&gt;in the documentation&lt;/a&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-protobuf&quot;&gt;# ...
import &quot;google/protobuf/timestamp.proto&quot;;

message BlogPost {
  # ...
  google.protobuf.Timestamp published = 6;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To go all the way and discover one last rather useful aspect, know that it is also possible
to use enums:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-protobuf&quot;&gt;# ...

message BlogPost {
  # ...
  enum PublicationStatus {
    DRAFT = 0;
    PUBLISHED = 1;
    ARCHIVED = 2;
  }
  PublicationStatus status = 7;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you want to test, and don’t have the courage to copy-paste everything, here is the complete code for the &lt;code&gt;BlogPost&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-protobuf&quot;&gt;syntax = &quot;proto3&quot;;

import &quot;src/User.proto&quot;;
import &quot;src/Tag.proto&quot;;
import &quot;google/protobuf/timestamp.proto&quot;;

message BlogPost {
  string uuid = 1;
  string title = 2;
  string content = 3;
  User author = 4;
  repeated Tag tags = 5;
  google.protobuf.Timestamp published = 6;
  enum PublicationStatus {
    DRAFT = 0;
    PUBLISHED = 1;
    ARCHIVED = 2;
  }
  PublicationStatus status = 7;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It is possible to define Namespaces for classes generated by ProtoBuf. It is even required for some languages (like Go).&lt;/p&gt;

&lt;p&gt;We will add metadata to each of our &lt;code&gt;.proto&lt;/code&gt; files, by adding:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-protobuf&quot;&gt;option go_package = &quot;blog/demo&quot;;
option php_namespace = &quot;Blog\\Demo&quot;;
option php_metadata_namespace = &quot;Blog\\Demo\\Metadata&quot;;
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;💡
Tips&lt;/strong&gt;&lt;/p&gt;

  &lt;p&gt;As your project progresses, you will need to evolve your messages. If you are concerned about 
breaking backward compatibility (e.g. by making an attribute obsolete), 
&lt;strong&gt;a good practice is to use a version attribute&lt;/strong&gt;.&lt;/p&gt;

  &lt;pre&gt;&lt;code class=&quot;language-protobuf&quot;&gt;message ... {
 optional int32 version = 999;
}
&lt;/code&gt;&lt;/pre&gt;

  &lt;p&gt;Store the current version of your data there, you will then be able to manage it according to its version without breaking everything.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;-using-protobuf-and-generating-code&quot;&gt;🧬 Using ProtoBuf and Generating Code&lt;/h2&gt;

&lt;p&gt;We’ve described a whole bunch of great things, but using them is even better! It’s time to install ProtoBuf.&lt;/p&gt;

&lt;p&gt;Simply download the latest release &lt;a href=&quot;https://github.com/protocolbuffers/protobuf/releases&quot;&gt;on the official Github repository&lt;/a&gt; (look for the &lt;code&gt;protoc-xxx&lt;/code&gt; file that corresponds to your distribution).&lt;/p&gt;

&lt;p&gt;For example, in my case, I download version 21.2 for Ubuntu:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl https://github.com/protocolbuffers/protobuf/releases/download/v21.2/protoc-21.2-linux-x86_64.zip \
  -o protoc-21.2-linux-x86_64.zip
unzip -qq protoc-21.2-linux-x86_64.zip -d protoc
chmod +x protoc/bin/protoc
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I now have a &lt;code&gt;protoc&lt;/code&gt; folder in my current directory, with the &lt;code&gt;bin/protoc&lt;/code&gt; binary that we will use for everything else.&lt;/p&gt;

&lt;p&gt;We will now do something quite magical: we will generate PHP code to serialize and deserialize BlogPost objects.&lt;/p&gt;

&lt;p&gt;Still in bash, run:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir -p generated # the &quot;generated&quot; folder will contain the generated code
protoc/bin/protoc --php_out=./generated  --proto_path=src $(find src -name &apos;*.proto&apos;)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You will find a set of ready-to-use PHP classes in the &lt;code&gt;generated&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;Let’s create a small script to use them. The first step will be to install ProtoBuf for PHP:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;composer require google/protobuf
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then let’s create a script that will generate the PHP code:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;?php

require_once __DIR__ . &apos;/vendor/autoload.php&apos;;

$blogSpot = new \Blog\Demo\BlogPost();
$blogSpot
    -&amp;gt;setTitle(&apos;My great post&apos;)
    -&amp;gt;setContent(&apos;Lorem ipsum&apos;)
    -&amp;gt;setAuthor(
        (new \Blog\Demo\User())
        -&amp;gt;setName(&apos;Jean-François&apos;)
    )
    -&amp;gt;setPublished(new Google\Protobuf\Timestamp());

// the content serialized in JSON:
$json = $blogSpot-&amp;gt;serializeToJsonString();

// the content serialized in binary
$binary = $blogSpot-&amp;gt;serializeToString();

// We put the binary in a temporary file, which will be read by Go
file_put_contents(&apos;blogpost.bin&apos;, $json);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The JSON looks like:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{&quot;title&quot;:&quot;My great post&quot;,&quot;content&quot;:&quot;Lorem ipsum&quot;,&quot;author&quot;:{&quot;name&quot;:&quot;Jean-François&quot;},&quot;published&quot;:&quot;1970-01-01T00:00:00Z&quot;}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And there you go! Now we will deserialize our post, but in Go this time.&lt;/p&gt;

&lt;h2 id=&quot;-using-protobuf-in-go&quot;&gt;🚀 Using ProtoBuf in Go&lt;/h2&gt;

&lt;p&gt;Let’s install the dependencies:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go mod init Version1
go get github.com/golang/protobuf
go get github.com/golang/protobuf/proto
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Let’s use ProtoBuf to automatically generate the Go code:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;protoc/bin/protoc --go_out=vendor $(find src -name &apos;*.proto&apos;)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here is a very simple code to read and parse the &lt;code&gt;blogspot.bin&lt;/code&gt; file that we generated in PHP:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;package main
import &quot;io/ioutil&quot;
import &quot;github.com/golang/protobuf/proto&quot;
import &quot;blog/demo&quot;
import &quot;log&quot;

func main() {

    in, err := ioutil.ReadFile(&quot;blogpost.bin&quot;)
    if err != nil {
        log.Fatalf(&quot;Read File Error: %s &quot;, err.Error())
    }
    blogpost := &amp;amp;demo.BlogPost{}
    err2 := proto.Unmarshal(in, blogpost)
    if err2 != nil {
        log.Fatalf(&quot;DeSerialization error: %s&quot;, err.Error())
    }

    log.Printf(&quot;BlogPost: %s&quot;, blogpost.Title)
    log.Printf(&quot;Author is: %s&quot;, blogpost.Author.Name)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Let’s run it:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;go run demo.go

# BlogPost: My great post
# Author is: Jean-François
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Our Go program successfully read the file serialized by PHP, and was able to extract the information from it, without any problems.&lt;/p&gt;

&lt;h2 id=&quot;-conclusion&quot;&gt;🔥 Conclusion&lt;/h2&gt;

&lt;p&gt;In both cases we were able to use typed objects or structures. If the data is deserialized, it means it’s valid!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The advantages of ProtoBuf are truly numerous:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The data is &lt;strong&gt;standardized&lt;/strong&gt;.
You manipulate &lt;strong&gt;typed structures&lt;/strong&gt;.
There is no need to add &lt;strong&gt;validators&lt;/strong&gt;.
You can use complex types.
Serialization/deserialization is &lt;strong&gt;efficient&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;However, from my experience, there is one caveat: &lt;strong&gt;the documentation could be greatly simplified&lt;/strong&gt; to make it more accessible for beginners.&lt;/p&gt;

&lt;p&gt;I hope I’ve made you want to try this tool. Feel free to share your experience on the subject.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;💡
Additional Resources&lt;/strong&gt;&lt;/p&gt;

  &lt;p&gt;You can also discover a &lt;a href=&quot;./2022-10-28-bus-de-donnees-datapipeline.md&quot;&gt;real production use case of ProtoBuf&lt;/a&gt; in a RabbitMQ data bus&lt;/p&gt;
&lt;/blockquote&gt;
</content>
 </entry>
 
 <entry>
   <title>ProtoBuf en PHP, pour une serialisation ultra-performante et agnostique</title>
   <link href="https://blog.lepine.pro/protobuf-standard-pour-echanger-des-donnes-php-go"/>
   <updated>2022-07-13T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/protobuf-standard-pour-echanger-des-donnes-php-go</id>
   <content type="html">&lt;p&gt;Aujourd’hui j’ai envie de vous parler d’un outil que j’utilise 
désormais presque tous les jours : &lt;a href=&quot;https://developers.google.com/protocol-buffers&quot;&gt;Protocol Buffers&lt;/a&gt; (ou &lt;strong&gt;ProtoBuf&lt;/strong&gt; pour les intimes).&lt;/p&gt;

&lt;p&gt;Contrairement à une idée reçue, il est tout à fait possible (et efficace !) d’utiliser ProtoBuf en &lt;code&gt;PHP&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;-quest-ce-que-protobuf-&quot;&gt;❔ Qu’est-ce que ProtoBuf ?&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;ProtoBuf, c’est :&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;un &lt;strong&gt;standard pour échanger des données&lt;/strong&gt; (pour les structurer et les sérialiser) ;&lt;/li&gt;
  &lt;li&gt;un &lt;strong&gt;générateur de code&lt;/strong&gt; (&lt;code&gt;Java&lt;/code&gt;, &lt;code&gt;PHP&lt;/code&gt;, &lt;code&gt;Go&lt;/code&gt;…) pour traiter ces données.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mon cas d’usage est assez basique : je dois faire transiter de l’information entre plusieurs microservices, via un bus &lt;code&gt;RabbitMQ&lt;/code&gt;. 
Je me sert donc de ProtoBuf pour ça.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nous allons échanger de la donnée entre une application PHP et une application Go 🎉 .&lt;/strong&gt; Voyons comment ça marche !&lt;/p&gt;

&lt;h2 id=&quot;-le-standard&quot;&gt;📄 Le standard&lt;/h2&gt;

&lt;p&gt;Si vous avez regardé le site officiel, vous voyez le mot “Google” un peu partout. Pas de panique, ça reste très intéropérable. 
Le couplage à Google est assez inexistant, et la technologie est utilisée par beaucoup d’acteurs différents. Google est surtout à l’initiative du projet.&lt;/p&gt;

&lt;p&gt;L’idée derrière tout ça est de décrire une donnée via des fichiers &lt;code&gt;.proto&lt;/code&gt;, standardisés et agnostiques. &lt;strong&gt;À partir de ces fichiers, 
toute donnée sera sérialisée et désérialisée, en binaire ou en JSON.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Pour un exemple basique, nous allons décrire un message simple, de type billet de blog :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-protobuf&quot;&gt;# fichier src/BlogPost.proto

syntax = &quot;proto3&quot;;
message BlogPost {
  string uuid = 1;
  string title = 2;
  string content = 3;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;C’est un message simple, qui contient un titre et un contenu. &lt;strong&gt;Chaque attribut est associé à une position (1, 2, 3, …), qui ne doit jamais 
changer dans le temps. C’est sur elle que s’appuie la sérialisation et déserialisation.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Continuons avec notre &lt;code&gt;BlogPost&lt;/code&gt;, afin de lui ajouter des tags et un auteur (de manière assez simpliste, mais l’idée est là) :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-protobuf&quot;&gt;# fichier src/User.proto

syntax = &quot;proto3&quot;;
message User {
    string uuid = 1;
    string name = 2;
    optional string avatar = 3;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&quot;language-protobuf&quot;&gt;# fichier src/Tag.proto

syntax = &quot;proto3&quot;;
message Tag {
    string label = 1;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Modifions le &lt;code&gt;BlogPost&lt;/code&gt; pour relier le tout. Le fichier ressemble désormais à :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-protobuf&quot;&gt;syntax = &quot;proto3&quot;;

import &quot;src/User.proto&quot;;
import &quot;src/Tag.proto&quot;;

message BlogPost {
  string uuid = 1;
  string title = 2;
  string content = 3;
  User author = 4;
  repeated Tag tags = 5;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Nous pouvons avoir autant de tags que nous le souhaitons, via l’instruction &lt;code&gt;repeated&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Nous allons enfin ajouter une date de publication à notre BlogPost. Pour cela, nous allons devoir importer le 
type &lt;code&gt;timestamp&lt;/code&gt;, qui est natif, mais à importer si vous souhaitez l’utiliser. Il existe pas mal de types, je vous 
laisse &lt;a href=&quot;https://developers.google.com/protocol-buffers/docs/proto3&quot;&gt;les découvrir dans la documentation&lt;/a&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-protobuf&quot;&gt;# ...
import &quot;google/protobuf/timestamp.proto&quot;;

message BlogPost {
  # ...
  google.protobuf.Timestamp published = 6;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Pour aller jusqu’au bout et découvrir un dernier aspect assez utile, sachez qu’&lt;strong&gt;il est possible également 
d’utiliser des enums&lt;/strong&gt; :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-protobuf&quot;&gt;# ...

message BlogPost {
  # ...
  enum PublicationStatus {
    DRAFT = 0;
    PUBLISHED = 1;
    ARCHIVED = 2;
  }
  PublicationStatus status = 7;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Si vous avez envie de tester, et pas le courage de tout copier-coller, &lt;strong&gt;voici le code complet&lt;/strong&gt; pour le &lt;code&gt;BlogPost&lt;/code&gt; :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-protobuf&quot;&gt;syntax = &quot;proto3&quot;;

import &quot;src/User.proto&quot;;
import &quot;src/Tag.proto&quot;;
import &quot;google/protobuf/timestamp.proto&quot;;

# Il reste une étape ici pour les namespaces

message BlogPost {
  string uuid = 1;
  string title = 2;
  string content = 3;
  User author = 4;
  repeated Tag tags = 5;
  google.protobuf.Timestamp published = 6;
  enum PublicationStatus {
    DRAFT = 0;
    PUBLISHED = 1;
    ARCHIVED = 2;
  }
  PublicationStatus status = 7;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Il est possible de définir des Namespaces pour les classes générées par ProtoBuf. C’est même requis pour certains 
langages (comme le Go).&lt;/p&gt;

&lt;p&gt;Nous allons ajouter des metadonnées à chacun de nos fichiers &lt;code&gt;.proto&lt;/code&gt;, en y ajoutant :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-protobuf&quot;&gt;option go_package = &quot;blog/demo&quot;;
option php_namespace = &quot;Blog\\Demo&quot;;
option php_metadata_namespace = &quot;Blog\\Demo\\Metadata&quot;;
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;💡
Astuce&lt;/strong&gt;&lt;/p&gt;

  &lt;p&gt;Au-fur-et-à-mesure de la vie du projet, vous allez faire évoluer vos messages. Si vous craignez de briser 
la rétrocompatibilité (par exemple en rendant obsolète un attribut), &lt;strong&gt;une bonne pratique consiste à utiliser 
un attribut de version&lt;/strong&gt;.&lt;/p&gt;

  &lt;pre&gt;&lt;code class=&quot;language-protobuf&quot;&gt;message ... {
 optional int32 version = 999;
}
&lt;/code&gt;&lt;/pre&gt;

  &lt;p&gt;Stockez-y la version actuelle de votre donnée, vous pourrez alors gérer cette dernière en fonction de sa version
sans tout casser.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;-utiliser-protobuf-et-générer-du-code&quot;&gt;🧬 Utiliser ProtoBuf et générer du code&lt;/h2&gt;

&lt;p&gt;On a décrit tout plein de belles choses, c’est bien. Mais les utiliser c’est mieux ! &lt;strong&gt;Il est temps d’installer ProtoBuf&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Téléchargez simplement la &lt;a href=&quot;https://github.com/protocolbuffers/protobuf/releases&quot;&gt;dernière release sur le 
dépôt Github officiel&lt;/a&gt; (cherchez le fichier &lt;code&gt;protoc-xxx&lt;/code&gt;qui correspond 
à votre distribution).&lt;/p&gt;

&lt;p&gt;Par exemple dans mon cas, je télécharge la version 21.2 pour Ubuntu :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl https://github.com/protocolbuffers/protobuf/releases/download/v21.2/protoc-21.2-linux-x86_64.zip \
  -o protoc-21.2-linux-x86_64.zip
unzip -qq protoc-21.2-linux-x86_64.zip -d protoc
chmod +x protoc/bin/protoc
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;J’ai désormais un dossier &lt;code&gt;protoc&lt;/code&gt; dans mon dossier courant, avec le binaire &lt;code&gt;bin/protoc&lt;/code&gt; qui nous servira pour 
tout le reste.&lt;/p&gt;

&lt;p&gt;Nous allons maintenant faire quelque chose d’assez magique : &lt;strong&gt;nous allons générer du code PHP pour sérialiser et désérialiser
des &lt;code&gt;BlogPost&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Toujours en bash, lancez :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir -p generated # le dossier &quot;generated&quot; va accueillir le code généré
protoc/bin/protoc --php_out=./generated  --proto_path=src $(find src -name &apos;*.proto&apos;)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Vous trouverez dans le dossier &lt;code&gt;generated&lt;/code&gt; un ensemble de classes PHP prêtes à l’emploi.&lt;/p&gt;

&lt;p&gt;Créons un petit script pour les utiliser. La première étape sera d’installer ProtoBuf pour PHP:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;composer require google/protobuf
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Puis créons un script qui va générer le code PHP :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;?php

require_once __DIR__ . &apos;/vendor/autoload.php&apos;;

$blogSpot = new \Blog\Demo\BlogPost();
$blogSpot
    -&amp;gt;setTitle(&apos;Mon super billet&apos;)
    -&amp;gt;setContent(&apos;Lorem ipsum&apos;)
    -&amp;gt;setAuthor(
        (new \Blog\Demo\User())
        -&amp;gt;setName(&apos;Jean-François&apos;)
    )
    -&amp;gt;setPublished(new Google\Protobuf\Timestamp());

// le contenu sérialisé en JSON:
$json = $blogSpot-&amp;gt;serializeToJsonString();

// le contenu sérialisé en binaire
$binary = $blogSpot-&amp;gt;serializeToString();

// Nous déposons le binaire dans un fichier temporaire, qui sera lu par Go
file_put_contents(&apos;blogpost.bin&apos;, $json);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;le JSON ressemble à :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{&quot;title&quot;:&quot;Mon super billet&quot;,&quot;content&quot;:&quot;Lorem ipsum&quot;,&quot;author&quot;:{&quot;name&quot;:&quot;Jean-François&quot;},&quot;published&quot;:&quot;1970-01-01T00:00:00Z&quot;}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Et voilà ! Nous allons maintenant désérialiser notre billet, mais en Go cette fois.&lt;/p&gt;

&lt;p&gt;Installons les dépendances :&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go mod init Version1
go get github.com/golang/protobuf
go get github.com/golang/protobuf/proto
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Utilisons ProtoBuf pour générer le code Go automatiquement :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;protoc/bin/protoc --go_out=vendor $(find src -name &apos;*.proto&apos;)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Voici un code très simple pour lire et parser le fichier &lt;code&gt;blogspot.bin&lt;/code&gt; que nous avons généré en PHP:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;package main
import &quot;io/ioutil&quot;
import &quot;github.com/golang/protobuf/proto&quot;
import &quot;blog/demo&quot;
import &quot;log&quot;

func main() {

    in, err := ioutil.ReadFile(&quot;blogpost.bin&quot;)
    if err != nil {
        log.Fatalf(&quot;Read File Error: %s &quot;, err.Error())
    }
    blogpost := &amp;amp;demo.BlogPost{}
    err2 := proto.Unmarshal(in, blogpost)
    if err2 != nil {
        log.Fatalf(&quot;DeSerialization error: %s&quot;, err.Error())
    }

    log.Printf(&quot;BlogPost: %s&quot;, blogpost.Title)
    log.Printf(&quot;Author is: %s&quot;, blogpost.Author.Name)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Lançons le :&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;go run demo.go

# BlogPost: Mon super billet
# Author is: Jean-François
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Notre programme en Go a bien lu le fichier serialisé par PHP, et a pu en extraire les informations, sans aucun problème.&lt;/p&gt;

&lt;h2 id=&quot;-conclusion&quot;&gt;🔥 Conclusion&lt;/h2&gt;

&lt;p&gt;Et voilà, nous avons fait passer de la donnée, structurée, de PHP vers Go. Dans les deux cas nous avons
pu utiliser des objets ou des structures typées. Si la donnée est déserialisée, c’est qu’elle est valide !&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Les avantages de ProtoBuf sont vraiment nombreux :&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;les données sont &lt;strong&gt;standardisées&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;on manipule des &lt;strong&gt;structures typées&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;il devient inutile d’ajouter des validateurs&lt;/li&gt;
  &lt;li&gt;on peut utiliser des types complexes&lt;/li&gt;
  &lt;li&gt;la sérialisation / déserialisation est &lt;strong&gt;performante&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Avec toutefois, de mon expérience, une réserve : &lt;strong&gt;la documentation mériterait d’être largement simplifiée&lt;/strong&gt;, pour la rendre 
plus abordable pour les débutants.&lt;/p&gt;

&lt;p&gt;En espérant vous avoir donné envie de tester cet outil, n’hésitez pas à faire part de votre expérience sur le sujet.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;💡
Pour aller plus loin&lt;/strong&gt;&lt;/p&gt;

  &lt;p&gt;vous pouvez également découvrir &lt;a href=&quot;./2022-10-28-bus-de-donnees-datapipeline.md&quot;&gt;un cas d’usage réel de production&lt;/a&gt; de ProtoBuf, 
dans le cadre d’un bus de données RabbitMQ&lt;/p&gt;
&lt;/blockquote&gt;
</content>
 </entry>
 
 <entry>
   <title>Un environnement complet de tests avec Docker - jour 2 (tests d'IHM)</title>
   <link href="https://blog.lepine.pro/environnement-test-docker-regression-ihm-gemini"/>
   <updated>2015-10-19T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/environnement-test-docker-regression-ihm-gemini</id>
   <content type="html">&lt;p&gt;Ce billet est le second d’une &lt;a href=&quot;/environnement-test-docker-behat/&quot;&gt;série sur la création d’un environnement de test pour des projets web&lt;/a&gt;. 
Aujourd’hui nous parlerons d’IHM, c’est-à-dire &lt;strong&gt;comment tester que l’interface (visuelle) de mon site n’est pas cassée&lt;/strong&gt; sous tel ou tel navigateur.&lt;/p&gt;

&lt;p&gt;Je rappelle que l’objectif ce ces billets / outils est de pouvoir dire “Vous n’avez pas de raison valable de ne pas tester” :)&lt;/p&gt;

&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;

&lt;p&gt;La question de &lt;strong&gt;tester une interface graphique est complexe&lt;/strong&gt; pour plusieurs raisons :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;manque de maturité&lt;/strong&gt; des outils&lt;/li&gt;
  &lt;li&gt;complexité de comparer l’incomparable (un site avec des publicités change à chaque rafraîchissement, ce n’est pas pour autant qu’il est cassé)&lt;/li&gt;
  &lt;li&gt;complexité de gérer les changements de contenus (une page d’accueil avec des articles change tout le temps)&lt;/li&gt;
  &lt;li&gt;gérer les &lt;strong&gt;faux-positifs&lt;/strong&gt; (liés, par exemple, au lissage des polices qui peuvent être différents pour un même navigateur)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;mon-retour-sur-quelques-outils-de-test-de-régression&quot;&gt;Mon retour sur quelques outils de test de régression&lt;/h3&gt;

&lt;p&gt;Au fil du temps, j’ai testé pas mal d’outils de test de régression. Tous s’appuient sur des comparaisons de captures d’écrans (du site en entier, ou de blocs (divs, etc.) du site).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Navigateurs headless :&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/Huddle/PhantomCSS&quot;&gt;PhantomCss&lt;/a&gt; : performant, mais souvent insuffisant car basé sur un navigateur virtuel. Le rendu, même sur des sites moyennement complexes, est souvent insatisfaisant et génère de nombreux faux-positifs)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://garris.github.io/BackstopJS/&quot;&gt;BackstopJs&lt;/a&gt; : idem&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Vrais navigateurs&lt;/strong&gt; (qui plus est ces outils ont des connecteurs vers des solutions de virtualisation externe, de type &lt;a href=&quot;https://saucelabs.com/&quot;&gt;SauceLabs&lt;/a&gt; ou &lt;a href=&quot;https://www.browserstack.com/&quot;&gt;BrowserStack&lt;/a&gt;) :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/bbc-news/wraith&quot;&gt;Wraith&lt;/a&gt; : développé par le BBC, efficace mais à l’usage la sélection de blocs est assez aléatoire (voire buggée). Cet outil génère une gallerie des screenshots très très pratique. Je n’ai jamais réussi à faire fonctionner &lt;a href=&quot;https://github.com/andrewccadman/wraith-selenium&quot;&gt;wraith-selenium&lt;/a&gt; correctement.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/facebookarchive/huxley&quot;&gt;Huxley&lt;/a&gt; : développé (mais abandonné) par Facebook, complexe à utiliser (on passe son temps à patcher le code source)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/bem/gemini&quot;&gt;Gemini&lt;/a&gt; : simple et efficace, c’est lui qui a ma préférence aujourd’hui. Permet, comme Wraith, de générer un rapport HTML&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Sass :&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://ghostinspector.com/&quot;&gt;ghostinspector.com/&lt;/a&gt; : très joli, mais tout se fait au clickdrome avec une extension Chrome, je n’ai pas trouvé de moyen de le piloter automatiquement ou d’écrire ses propres scripts.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://percy.io&quot;&gt;percy.io&lt;/a&gt; : j’aimerai bien tester, mais je n’ai pas d’invitation. Pour l’instant seul Firefox est supporté&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pour les curieux, ceux que je n’ai fait que survoler :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/xebia/VisualReview&quot;&gt;VisualReview&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/gabrielrotbart/gatling&quot;&gt;Gatling&lt;/a&gt; (non, pas LE gatling qu’on connaît tous)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.screenbeacon.com/&quot;&gt;ScreenBeacon&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://shoov.io/&quot;&gt;Shoov&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/webdriverio/webdrivercss&quot;&gt;WebDriverCss&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://browsershots.org/&quot;&gt;BrowserShots&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Bref, il existe pas mal d’outils différents, et ça bouge bien en ce moment.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;installation&quot;&gt;Installation&lt;/h2&gt;

&lt;p&gt;Nous allons donc utiliser Gemini. Voici un exemple de rapport HTML que l’on souhaite obtenir :&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2015-10-gemini.png&quot; alt=&quot; Tests de non régression responsive avec Gemini &quot; /&gt;&lt;/p&gt;

&lt;p&gt;Si le site a subi des régression, &lt;strong&gt;les écarts seront mis en surbrillance comme suit&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2015-10-gemini-fails.png&quot; alt=&quot; Tests de non régression responsive avec Gemini &quot; /&gt;&lt;/p&gt;

&lt;p&gt;Là encore, j’ai préparé une image Docker pour vous:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;docker pull qualiboo/testing-gemini
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;On va s’appuyer sur le fichier &lt;code&gt;docker-compose.yml&lt;/code&gt; que l’on a créé &lt;a href=&quot;/environnement-test-docker-behat/&quot;&gt;la dernière fois&lt;/a&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;gemini:
  image: roukmoute/testing-gemini
  links:
    - hub
    - firefox
    - chrome
  volumes:
    - ./gemini:/var/work
hub:
  image: qualiboo/testing-hub
  ports:
    - 4444:4444
firefox:
  image: qualiboo/testing-node-firefox
  ports:
    - 5900
  links:
    - hub
chrome:
  image: qualiboo/testing-node-chrome
  ports:
    - 5900
  links:
    - hub
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Vous constatez que l’&lt;strong&gt;on utilise Selenium&lt;/strong&gt;, avec des noeuds Chrome et Firefox.&lt;/p&gt;

&lt;p&gt;Vérifions l’installation. La commande suivante doit afficher la version de gemini:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;docker-compose run gemini version
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;premiers-tests&quot;&gt;Premiers tests&lt;/h2&gt;

&lt;p&gt;Nous allons procéder en deux temps :&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;faire les captures d’écrans qui serviront de référence pour les tests (&lt;code&gt;gemini capture&lt;/code&gt;) ;&lt;/li&gt;
  &lt;li&gt;comparer de nouvelles capture à ce référentiel pour voir s’il y a des régression (&lt;code&gt;gemini test&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Pour cela, créez un dossier &lt;code&gt;./gemini/suite&lt;/code&gt; et créez-y le fichier &lt;code&gt;premier-test.js&lt;/code&gt; avec le contenu suivant :&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;var gemini = require(&apos;gemini&apos;);
gemini.suite(&apos;homepage&apos;, function(suite) {
    suite
        .setUrl(&apos;/&apos;)
        .setCaptureElements(&apos;#nav-menu&apos;);
        .capture(&apos;menu&apos;);
});
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Le code (JavaScript) est assez simple : on capture la &lt;code&gt;div&lt;/code&gt; dont l’id est &lt;code&gt;#nav-menu&lt;/code&gt; depuis la racine du site.&lt;/p&gt;

&lt;p&gt;Maintenant, créez le fichier &lt;code&gt;./gemini/.gemini.yml&lt;/code&gt; qui va permettre de configurer Gemini :&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;rootUrl: http://qualiboo.com
gridUrl: http://hub:4444/wd/hub
browsers:
  chrome:
    desiredCapabilities:
      browserName: chrome
  firefox:
    desiredCapabilities:
      browserName: firefox
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Nous indiquons ici à Gemini l’URL de base du site, les navigateurs souhaités, et comment se connecter à Selenium Grid..&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lancez la capture du référentiel&lt;/strong&gt; (ça prend quelques secondes):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;docker-compose run gemini capture
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Vérifiez bien à chaque fois que vous faites une &lt;code&gt;capture&lt;/code&gt; que les images générées (dossier &lt;code&gt;./gemini/gemini/screens&lt;/code&gt;) correspondent à vos attentes. Elle serviront de référentiel pour les tests. 
Voilà, &lt;strong&gt;les tests peuvent être lancés&lt;/strong&gt; :&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;docker-compose run gemini test
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Après quelques secondes/minutes, vous voilà avec un “beau” rapport HTML dans &lt;code&gt;./gemini/gemini-report/index.html&lt;/code&gt;. Il ne reste plus qu’à l’ouvrir dans votre navigateur préferé…&lt;/p&gt;

&lt;h2 id=&quot;tester-le-responsive-design&quot;&gt;Tester le responsive design&lt;/h2&gt;

&lt;p&gt;Avant toute chose, il faut savoir que &lt;strong&gt;le monde de tests d’IHM n’est pas parfait&lt;/strong&gt;. La version du ChromeDriver actuelle pour Linux (2.20) ne permet pas de capturer un bloc qui est plus grand que le viewport 
du navigateur. Si vous voulez capturer toute la page (le &lt;code&gt;body&lt;/code&gt;), il faudra passer par Firefox.&lt;/p&gt;

&lt;p&gt;Il est simple avec Gemini de forcer certains tests à être exécutés uniquement dans Firefox ou Chrome. Dans le fichier &lt;code&gt;./gemini/.gemini.yml&lt;/code&gt; ajoutez le contenu suivant:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;(...)
sets:
  firefox:
    files:
     - suite/firefox-only
    browsers:
     - firefox
  chrome:
    files:
     - suite/chrome-only
    browsers:
     - chrome
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Tous les tests qui seront placés dans le dosser &lt;code&gt;./gemini/suite/firefox-only&lt;/code&gt; seront désormais lancés uniquement par firefox (et même chose pour &lt;code&gt;./gemini/suite/chrome-only&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Comme nous souhaitons capturer la page entière (le &lt;code&gt;body&lt;/code&gt;), plaçons notre fichier de test (&lt;code&gt;./gemini/suite/premier-test.yml&lt;/code&gt;) dans le dossier &lt;code&gt;firefox-only&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nous allons tester notre site sous des résolutions différentes&lt;/strong&gt; ; pour cela, modifiez le fichier &lt;code&gt;premier-test.yml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Nous allons changer la résolution de l’écran avant chaque test, et cette fois nous capturons donc le &lt;code&gt;body&lt;/code&gt; :&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;var gemini = require(&apos;gemini&apos;);
gemini.suite(&apos;homepage&apos;, function(suite) {
  suite
    .setUrl(&apos;/&apos;)
    .setCaptureElements(&apos;body&apos;)
    .capture(&apos;homepage&apos;, function (actions) {
        actions.setWindowSize(1200, 1024).wait(1000);
    })
    .capture(&apos;homepage tablet&apos;, function (actions) {
        actions.setWindowSize(768, 1024).wait(1000);
    })
    .capture(&apos;homepage mobile&apos;, function (actions) {
        actions.setWindowSize(320, 568).wait(1000)
    });
});
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;N’oubliez pas de mettre à jour le référentiel de screenshots :&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;docker-compose run gemini capture
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;A vous désormais de lancer les tests à votre guise (par exemple à chaque Push sur votre dépôt, à l’aide de Jenkins).&lt;/p&gt;

&lt;h2 id=&quot;aller-plus-loin-et-faire-abstraction-du-contenu-dynamique&quot;&gt;Aller plus loin et faire abstraction du contenu dynamique&lt;/h2&gt;

&lt;p&gt;Je l’ai évoqué, &lt;strong&gt;un problème récurrent des tests de régression d’IHM concerne le contenu dynamique&lt;/strong&gt; (publicités, actualités…). Il faut trouver un moyen de faire abstraction 
de ce type de contenu.&lt;/p&gt;

&lt;p&gt;Le seul moyen simple que j’ai trouvé consiste à remplacer ce contenu par du “Lorem ipsum”. Il faut alors, avant chaque test, exécuter un script JavaScript pour ce faire:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;suite
    .setUrl(&apos;/&apos;)
    .setCaptureElements(&apos;body&apos;)
    .before(function(actions, find) {
        actions.executeJS(function(window) {
            // replacing images
            $(&apos;img.my-class&apos;).attr(&apos;src&apos;, &apos;&apos;);
            
            // replacing some texts
            $(&apos;p.another-class, .title, (etc.)&apos;).html(&apos;Lorem ipsum dolor sit amet&apos;);
    
            // advertising
            $(&apos;.pub&apos;).html(&apos;PUBLICITE&apos;);
            
            // etc.
        })
    .capture(...)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Dès lors, toutes les images et contenus dynamiques sont remplacés. Il est assez simple de factoriser ce code dans un fichier à part pour le réutiliser partout.&lt;/p&gt;

&lt;p&gt;Le contenu du site a changé, donc, &lt;strong&gt;oui, nos tests sont alors incomplets et éloignés de la réalité&lt;/strong&gt;. Mais si on ne le fait pas, le risque est que ces tests ne soient jamais pertinents car fondés uniquement sur de faux-positifs. &lt;strong&gt;La suppression 
des publicités est un compromis pour être pragmatique&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Vous l’avez vu, &lt;strong&gt;vous avez là un moyen simple de tester l’IHM de votre site sous différentes résolutions&lt;/strong&gt;, à l’aide de Docker. Si vous avez un feedback, n’hésitez pas à laisser un commentaire ; et si 
ce billet vous plaît, n’hésitez pas à le partager :)&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Un environnement complet de tests avec Docker - jour 1 (Behat)</title>
   <link href="https://blog.lepine.pro/environnement-test-docker-behat"/>
   <updated>2015-10-12T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/environnement-test-docker-behat</id>
   <content type="html">&lt;p&gt;Ce billet est le premier d’une série sur la création d’un environnement de test pour des projets web. A la fin de cette série, vous disposerez d’un environnement 
Docker capable de lancer facilement :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;des tests fonctionnels avec Behat, Selenium Grid, Chrome, Firefox et PhantomJs&lt;/li&gt;
  &lt;li&gt;des tests de non régression d’interface utilisateur&lt;/li&gt;
  &lt;li&gt;des tests de performance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Aujourd’hui nous allons démarrer avec les tests Behat, exécutés dans un navigateur grâce à Selenium Grid et Docker.&lt;/p&gt;

&lt;h2 id=&quot;docker&quot;&gt;Docker&lt;/h2&gt;

&lt;p&gt;Je ne vais pas refaire un billet sur ce que d’autres ont fait avec bien &lt;a href=&quot;http://geoffrey.io/what-is-docker.html&quot;&gt;plus de clarté&lt;/a&gt; que je ne saurais faire. Sachez juste que Docker va vous permettre 
de “virtualiser” une partie de votre système, permettant donc d’installer plein d’outils sans pourrir votre machine, et sans risque de conflits de versions.&lt;/p&gt;

&lt;p&gt;Pour tous les billets, je suppose que Docker et Docker-Compose sont installés sur votre machine. Si ce n’est pas le cas, il est encore temps :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.docker.com/installation/mac/&quot;&gt;Mac OS X&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.docker.com/installation/ubuntulinux/&quot;&gt;Ubuntu&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.docker.com/installation/&quot;&gt;Autres systèmes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Et pour Docker-Compose :&lt;/p&gt;

&lt;p&gt;Linux:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;curl -L https://github.com/docker/compose/releases/download/1.4.2/docker-compose-`uname -s`-`uname -m` &amp;gt; /usr/local/bin/docker-compose
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;OSX:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Déjà installé avec Docker (au passage, je ne comprendrais jamais pourquoi on n&apos;a pas la même processus d&apos;installation sur Max oO)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Windows:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;curl -L https://github.com/docker/compose/releases/download/1.4.2/docker-compose-`uname -s`-`uname -m` &amp;gt; /usr/local/bin/docker-compose
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;behat&quot;&gt;Behat&lt;/h2&gt;

&lt;p&gt;En deux mots, Behat est un &lt;a href=&quot;http://blog.lepine.pro/php/behat-jour-1-comment-tester-son-produit-scrum/&quot;&gt;outil de test&lt;/a&gt;. Il permet de convertir des phrases (langues naturelles) en 
code source. &lt;strong&gt;Il est du coup possible d’associer ces phrases à des actions utilisateurs dans un navigateur&lt;/strong&gt; (Chrome, Firefox…) ou un émulateur de navigateur (on parle de “navigateur headless”).&lt;/p&gt;

&lt;p&gt;Pour vous simplifier la vie, j’ai préparé des images “prêtes à l’emploi”. Commencez par télécharger l’image principale (c’est l’heure du premier café):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;docker pull qualiboo/testing-behat
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Voilà, vous voilà avec un Behat prêt à l’emploi. Créez un dossier local &lt;code&gt;behat&lt;/code&gt; (et un sous-dossier &lt;code&gt;build&lt;/code&gt; pour les logs) pour stocker les tests Behat, puis demandons à Behat d’y créer l’arborescence de base, 
en montant un volume :&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;mkdir -p behat/build
docker run -v $(pwd)/behat:/var/work qualiboo/testing-behat --init 
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Vous voilà presque prêt(e) à démarrer.&lt;/p&gt;

&lt;p&gt;Behat interagit avec des Fonctionnalités, décrites dans des fichiers &lt;code&gt;*.features&lt;/code&gt;. &lt;strong&gt;Il est temps d’écrire notre premier test&lt;/strong&gt; ; 
créez le fichier &lt;code&gt;./behat/premier-pas.feature&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Feature: My first feature
  
  Scenario: Visitor can follow link to get information on blogpost
    Given I am on &quot;http://blog.lepine.pro&quot;
    When I follow &quot;Un outil pour améliorer la qualité d&apos;un projet web&quot;
    Then I should see &quot;qualiboo&quot;
    And the url should match &quot;/outil-mesure-qualite-projet-web/&quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Si vous lancez Behat tel quel, Behat ne saura pas quoi faire de ces phrases. Nous avons besoin d’importer Mink, une extension bien pratique 
qui permet d’utiliser des expressions liées à la navigation web. Ouvrez le fichier &lt;code&gt;./behat/bootstrap/FeatureContext.php&lt;/code&gt;, et ajoutez-y le code suivant:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;public function __construct(array $parameters)
{
    $this-&amp;gt;useContext(&apos;mink&apos;, new \Behat\MinkExtension\Context\MinkContext($parameters));
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Nous venons de dire à Behat que nous souhaitons utiliser l’extension Mink.&lt;/p&gt;

&lt;p&gt;Lançons les tests:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;docker run --rm -t -v $(pwd)/behat:/var/work qualiboo/testing-behat
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;Tout est vert&lt;/strong&gt; ; les tests sont donc passés (si vous ne me faites pas confiance essayez de modifier le fichier feature ;) )&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;En quelques minutes, nous disposons donc d’un robot capable de vérifier rapidement et à l’infini que mon blog est en ligne&lt;/strong&gt;, que je peux suivre un lien et que suivre ce lien me fait changer de page. Pas mal !&lt;/p&gt;

&lt;p&gt;N’oubliez pas que vous pouvez connaître la liste des expressions prêtes à l’emploi grâce à la commande suivante :&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;docker run --rm -t -v $(pwd)/behat:/var/work qualiboo/testing-behat -dl
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Pour aller plus loin sur la manière d’écrire des fonctionnalités ou sur les bonnes pratiques Behat, &lt;strong&gt;je vous invite à lire cet &lt;a href=&quot;http://communiquez.lepine.pro/download/developpement-pilote-par-le-comportement-tome2.pdf&quot;&gt;ebook&lt;/a&gt; Open Source que j’ai écrit il y a un moment sur le sujet&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;un-vrai-navigateur&quot;&gt;Un “vrai” navigateur&lt;/h2&gt;

&lt;p&gt;Bon, on a nos tests, mais en réalité jusqu’ici nous utilisons un navigateur “virtuel”. Ce navigateur est incapable d’interpréter du javascript. Il faut aller plus loin.&lt;/p&gt;

&lt;p&gt;Nous avons besoin maintenant de lancer nos tests dans un vrai navigateur. Nous pourrions utiliser le nôtre, mais n’oubliez pas 
que désormais on dispose de Docker, ce qui est un énorme avantage : &lt;strong&gt;grâce à Docker on peut fixer une version, avoir un navigateur qui n’est pas pollué 
par 20 extensions&lt;/strong&gt; qui faussent les résultats des tests (AdBblock &amp;amp; Co)…&lt;/p&gt;

&lt;p&gt;Nous allons utiliser Selenium, qui permet de piloter des navigateurs de manière très efficace, plus spécifiquement Selenium Grid, 
qui permet en plus de gérer une grille de navigateurs (dans des versions et résolutions différentes).&lt;/p&gt;

&lt;p&gt;Pour simplifier les choses, là encore je vous ai préparé des images Docker. Nous allons utiliser Docker-Compose pour les télécharger et les relier entre-elles.&lt;/p&gt;

&lt;p&gt;Il vous suffit de créer un fichier &lt;code&gt;docker-compose.yml&lt;/code&gt; pour les utiliser :&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;behat:
  image: qualiboo/testing-behat
  volumes:
    - ./behat:/var/work
  links:
      - hub
  environment:
    website: http://blog.lepine.pro
hub:
  image: qualiboo/testing-hub
  ports:
    - 4444:4444
firefox:
  image: qualiboo/testing-node-firefox
  ports:
    - 5900
  links:
    - hub
  environment:
    REMOTE_HOST_PARAM: &quot;-maxSession 3 -browser browserName=firefox,maxInstances=3&quot;
chrome:
  image: qualiboo/testing-node-chrome
  ports:
    - 5900
  links:
    - hub
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;C’est ce fichier que nous allons enrichir au fil des billets, pour obtenir un environnement de test complet&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Grâce à ce fichier, Docker Compose va télécharger les images:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;“qualiboo/testing-behat”, qui permet de lancer Behat&lt;/li&gt;
  &lt;li&gt;“qualiboo/testing-hub”, qui contient Selenium Grid&lt;/li&gt;
  &lt;li&gt;“qualiboo/testing-node-firefox”, qui contient Firefox&lt;/li&gt;
  &lt;li&gt;“qualiboo/testing-node-chrome”, qui contient Chrome&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Lançons Docker Compose (c’est l’heure du second café) :&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;docker-compose up -d
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Et là, la magie opère. Commencez par tagger la fonctionnalité avec &lt;code&gt;@javascript&lt;/code&gt; pour indiquer à Behat que nous voulons utiliser un vrai navigateur :&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;@javascript
Feature: My first feature
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Les images Docker que vous venez d’installer sont pré-configurées. Vous pouvez désormais choisir quel navigateur utiliser simplement en utilisant l’option &lt;code&gt;--profile=&amp;lt;navigateur&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Par exemple:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;docker-compose run behat --profile=firefox
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Notez que vous avez quatre navigateur à votre disposition:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Goutte (aucun paramètre)&lt;/li&gt;
  &lt;li&gt;PhantomJs (&lt;code&gt;--profile=phantomjs&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;Chrome (&lt;code&gt;--profile=chrome&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;Firefox (&lt;code&gt;--profile=firefox&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notez également que désormais Behat a été lancé par Docker Compose (commande &lt;code&gt;docker-compose&lt;/code&gt;) et non simplement Docker.&lt;/p&gt;

&lt;p&gt;Dernière chose: vous pouvez superviser vos noeuds Selenium à l’adresse &lt;a href=&quot;http://localhost:4444/grid/console&quot;&gt;http://localhost:4444/grid/console&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Ah au fait : si vous voulez plus de noeuds Selenium (par exemple, disposer de plusieurs Firefox pour paralléliser les tests), n’hésitez pas à utiliser les capacités de scaling de docker. 
Par exemple, pour avoir 5 firefox:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;docker-compose scale firefox=5
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Voilà, c’est tout pour ce premier billet. la prochaine fois, on parlera de tests de non régression visuelle.&lt;/p&gt;

&lt;p&gt;Si le sujet du test vous intéresse, ou si que vous voulez que j’ajoute d’autres types d’outils / tests à cette série de billets, 
n’hésitez pas à laisser un commentaire (ou même pour m’encourager ^^).&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Un outil pour améliorer la qualité d'un projet web</title>
   <link href="https://blog.lepine.pro/outil-mesure-qualite-projet-web"/>
   <updated>2015-09-14T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/outil-mesure-qualite-projet-web</id>
   <content type="html">&lt;p&gt;&lt;strong&gt;Le mot “qualité”, quand on parle d’un logiciel, est très ambigu&lt;/strong&gt; : parle-t-on de faible complexité du code source ? D’évolutivité, de performance, d’accessibilité ? Que
dire à propos des tests et de l’intégration continue ? La gestion des anomalies (Issue tracker) fait-elle partie de la qualité ? En bref, que siginifie “qualité logicielle” ? Et &lt;strong&gt;comment la mesurer ?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Les premières véritables tentatives de définitions datent des années 70, et de McCall et Boehm. Dès 1996, le standard &lt;a href=&quot;https://en.wikipedia.org/wiki/ISO/IEC_9126&quot;&gt;ISO 9126&lt;/a&gt;, régulièrement amélioré,
donne une liste de caractéristiques possibles, puis les standard &lt;a href=&quot;https://fr.wikipedia.org/wiki/S%C3%A9rie_des_normes_ISO_9000&quot;&gt;ISO 9000:2000&lt;/a&gt; viennent affiner les concepts. Une grille de facteurs de la
qualité logicielle est établie et mature.&lt;/p&gt;

&lt;h2 id=&quot;grille-de-facteurs-de-qualité&quot;&gt;Grille de facteurs de qualité&lt;/h2&gt;

&lt;p&gt;Personnellement, la grille que j’utilise dans mes stratégies de test est celle proposée par le groupe &lt;a href=&quot;http://www.istqb.org/&quot;&gt;ISTQB&lt;/a&gt; :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;** Exactitude**:  Degré de conformité par rapport aux spécifications&lt;/li&gt;
  &lt;li&gt;** Fiabilité**:  Capacité d’un programme à accomplir sa fonction sans défaillance&lt;/li&gt;
  &lt;li&gt;** Efficacité**:  Capacité du programme à exploiter efficacement (de manière optimale) les ressources (CPU, mémoire…) à disposition”&lt;/li&gt;
  &lt;li&gt;** Intégrité**:  Capacité de protéger le système et les données qu’il manipule&lt;/li&gt;
  &lt;li&gt;** Ergonomie**:  Capacité du programme à être utilisé avec peu d’efforts&lt;/li&gt;
  &lt;li&gt;** Maintenabilité**:  Capacité à faciliter la localisation et la correction d’anomalies&lt;/li&gt;
  &lt;li&gt;** Testabilité**:  Capacité du programme à se prêter à une vérification (tests) de bon fonctionnement&lt;/li&gt;
  &lt;li&gt;** Flexibilité**:  Capacité d’évolution et d’adaption à de nouvelles fonctionnalités&lt;/li&gt;
  &lt;li&gt;** Portabilité**:  Facilité de changement d’environnements (OS, matériel…)&lt;/li&gt;
  &lt;li&gt;** Réutilisabilité**:  Possibilité de réutilisation de composants dans d’autres projets&lt;/li&gt;
  &lt;li&gt;** Interopérabilité**:  Capacité du programme à être associé à un autre (échange de données, utilisation…)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Disposer d’une grille, c’est beau, c’est bien… Mais, et après ? On peut s’en servir pour élaborer une stratégie de test, mais ça reste très théorique. Comment mesurer tout ça ?&lt;/p&gt;

&lt;p&gt;Si pour la performance (au sens premier, à savoir l’exploitation efficace des ressources mises à disposition), un consensus peut facilement émerger
(mesure du temps de réponse, mesure de la &lt;a href=&quot;https://en.wikipedia.org/wiki/Asymptotic_computational_complexity&quot;&gt;complexité asymptotique&lt;/a&gt;), qu’en est-il des autres facteurs ?
Pas facile…&lt;/p&gt;

&lt;h2 id=&quot;mesurer-les-facteurs-de-qualité-prioritaires&quot;&gt;Mesurer les facteurs de qualité prioritaires&lt;/h2&gt;

&lt;p&gt;C’est un sujet que j’ai vraiment beaucoup (beaucoup beaucoup) étudié, et à force de confrontations à la réalité des projets, je me suis rendu compte que 
pour les projets web, &lt;strong&gt;certains facteurs sortent du lot&lt;/strong&gt;, car généralement considérés par les équipes techniques comme “plus importants” :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;la &lt;strong&gt;maintenabilité&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;l’&lt;strong&gt;évolutivité&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;la &lt;strong&gt;fiabilité&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Il existe des métriques logicielles, statiques ou dynamiques, qui tentent de mesurer ces aspects :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Maintainability&quot;&gt;Indice de maintenabilité&lt;/a&gt;, l’&lt;a href=&quot;https://en.wikipedia.org/wiki/Programming_complexity&quot;&gt;absence de cohésion des méthodes&lt;/a&gt; ou le &lt;a href=&quot;https://en.wikipedia.org/wiki/Cyclomatic_complexity&quot;&gt;nombre de complexité cyclomatique&lt;/a&gt; … pour la maintenabilité et l’évolutivité ;&lt;/li&gt;
  &lt;li&gt;le &lt;a href=&quot;http://www.artima.com/weblogs/viewpost.jsp?thread=210575&quot;&gt;CRAP&lt;/a&gt;, la couverture ou le taux de résistance aux mutations le taux de détection de bugs… pour la fiabilité ;&lt;/li&gt;
  &lt;li&gt;le &lt;a href=&quot;https://en.wikipedia.org/wiki/Software_package_metrics&quot;&gt;couplage&lt;/a&gt;, l’&lt;a href=&quot;https://en.wikipedia.org/wiki/Software_package_metrics&quot;&gt;abstraction&lt;/a&gt;… pour l’évolutivité et la réutilisabilité.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Malheureusement, &lt;strong&gt;les outils pour mesurer ces métriques sont rares&lt;/strong&gt; ; c’est pour ça que j’ai développé &lt;a href=&quot;http://phpmetrics.org&quot;&gt;PhpMetrics&lt;/a&gt;, ou encore
&lt;a href=&quot;https://github.com/Halleck45/MutaTesting&quot;&gt;MutaTesting&lt;/a&gt;, tous deux Open Source.&lt;/p&gt;

&lt;p&gt;Une fois que l’on sait collecter ponctuellement ces métriques, reste encore à rendre la collecte systématique ; pour ça on utilise souvent &lt;a href=&quot;http://www.sonarqube.org/&quot;&gt;Sonar&lt;/a&gt; ou &lt;a href=&quot;https://jenkins-ci.org/&quot;&gt;Jenkins&lt;/a&gt;. Or Sonar se focalise plus sur la
dette technique (&lt;a href=&quot;https://en.wikipedia.org/wiki/SQALE&quot;&gt;SQALE&lt;/a&gt;), et Jenkins n’est clairement pas fait pour ça (mais pour de l’intégration continue). &lt;strong&gt;Il faut une énergie immense dans tous les sens pour obtenir un résultat acceptable, à défaut de convenable&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Autre problème : quand bien même on a mis en place ces outils, il reste à rendre les résultats lisibles et clairs, ce qui (croyez-moi) pour le coup n’est vraiment pas simple.&lt;/p&gt;

&lt;p&gt;Encore un autre problème : on ne se focalise que sur un code source ; or je l’ai dit, un projet c’est bien plus : on a souvent plusieurs dépôts, un tracker de bugs… Si vraiment
on veut mesurer la maintenabilité d’un projet, &lt;strong&gt;on doit par exemple regarder l’évolution du nombre de tickets ouverts&lt;/strong&gt;, l’évolution du taux de détection des bugs (DDP), .
A ma connaissance, il n’existe que peu d’outils de mesure qui prennent
en compte un projet dans sa globalité (je pense à VisualStudio ou Cast software), et aucun pour PHP.&lt;/p&gt;

&lt;h2 id=&quot;qualiboocom-un-outil-de-mesure-en-ligne&quot;&gt;Qualiboo.com, un outil de mesure en ligne&lt;/h2&gt;

&lt;p&gt;C’est ce qui m’a amené à réflechir à la création d’un outil capable d’aider à mesurer un projet web dans son ensemble, en collectant un grand nombre de métriques variées. Bien
sûr cet outil ne pourrait pas mesurer la “qualité” d’un projet web au sens propre, mais aiderait à fournir des indicateurs. Ce serait un assistant à la qualité, ou à défaut à la détection d’éléments susceptibles de mener à la “non-qualité”.&lt;/p&gt;

&lt;p&gt;Il existe plusieurs très bons outils en ligne d’analyse de code pour PHP : &lt;a href=&quot;https://insight.sensiolabs.com/&quot;&gt;Sensio Insight&lt;/a&gt;, &lt;a href=&quot;https://scrutinizer-ci.com/&quot;&gt;Scrutinizer&lt;/a&gt;… Mais tous ont ces inconvénients que j’ai cités : ils ne se focalisent que sur le code source,
et qui plus est sur un seul dépôt de code (pas très pratique quand on fait du microservice par exemple, avec plein de dépôts Git pour un seul projet).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;C’est pour ces raisons que j’ai construit &lt;a href=&quot;https://www.qualiboo.com&quot;&gt;qualiboo.com&lt;/a&gt;&lt;/strong&gt;. C’est une application de suivi de la qualité d’un projet web :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;analyse du code&lt;/strong&gt; JavaScript et PHP&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;aggrégation&lt;/strong&gt; d’analyses sur plusieurs dépôts de code (Git, Gitlab et Github)&lt;/li&gt;
  &lt;li&gt;analyse du &lt;strong&gt;tracker de bugs&lt;/strong&gt; (Redmine et Github)&lt;/li&gt;
  &lt;li&gt;suivi de l’&lt;strong&gt;intégration continue&lt;/strong&gt; (Jenkins - bientôt Travis-ci)&lt;/li&gt;
  &lt;li&gt;suivi des &lt;strong&gt;dépendances&lt;/strong&gt; (licenses, vulnérabilités connues…)&lt;/li&gt;
  &lt;li&gt;suivi de l’&lt;strong&gt;activité&lt;/strong&gt; Git&lt;/li&gt;
  &lt;li&gt;rapports très graphiques en “mode TV”&lt;/li&gt;
  &lt;li&gt;et très vite bien plus :)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Voici un aperçu de l’application:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2015-09-qualiboo-overview.jpg&quot; alt=&quot; Aperçu de www.qualiboo.com &quot; /&gt;&lt;/p&gt;

&lt;p&gt;C’est un projet à la fois gratuit et payant : gratuit pour les projets Open Source, payant pour les entreprises. Payant ? Si vous me connaissez, vous savez que ça n’est pas fréquent chez moi ;
100% de mes projets sont Open source, libres et gratuits. Oui, mais cette fois j’ai des coûts d’exploitation (serveurs) élevés qu’il me faut rentabiliser, et surtout j’aimerai vraiment
améliorer sans cesse la qualité du service rendu par ce site, et donc pouvoir m’y investir pleinement, en prenant le temps qu’il faut pour y travailler efficacement, et pas seulement
“vite fait” le matin ou le soir.&lt;/p&gt;

&lt;p&gt;Le projet est encore en jeune, mais &lt;strong&gt;j’apprécierai vraiment &lt;a href=&quot;https://docs.google.com/forms/d/1fEO59O6z5UErPmZL1Fd_2X9WPfaf1zGj2jMSxIKatSo/viewform?usp=send_form&quot;&gt;votre feedback&lt;/a&gt;&lt;/strong&gt; : cet outil vous paraît-il utile ? Adapté ? Seriez-vous prêt à l’utiliser ? Que manque t-il à votre avis ?&lt;/p&gt;

&lt;p&gt;Et si vous pensez que qualiboo.com peut avoir un intérêt, n’hésitez pas à en parler autour de vous :) . Merci !&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>OSS : The Swiss Army Knife to manage your free media files.</title>
   <link href="https://blog.lepine.pro/en/open-source-assets/"/>
   <updated>2015-02-25T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/en/open-source-assets</id>
   <content type="html">&lt;p&gt;&lt;code&gt;Composer&lt;/code&gt;, &lt;code&gt;NPM&lt;/code&gt;… so many tools to manage the technical dependencies of our projects. That’s good. 
&lt;strong&gt;But what about managing the licenses of the downloaded files?&lt;/strong&gt; And what about the free or open source media (images, sounds, videos) that we use?&lt;/p&gt;

&lt;p&gt;For example, take this illustration that you version in your project. &lt;strong&gt;In 6 months, you won’t remember where it comes from, or under what license it is distributed.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Take the problem of media. There are tools (&lt;a href=&quot;https://www.openhub.net&quot;&gt;OpenHub&lt;/a&gt; for example), but nothing really related to the daily life of the developer. Until now I had a tendency to note the images I use in a text file. But this approach is a bit messy, and in the long run 
I get lost between the images that are really used on my site and those that I have downloaded “to test”.&lt;/p&gt;

&lt;h2 id=&quot;oss-a-tool-to-manage-the-free-media-of-your-project&quot;&gt;OSS, a tool to manage the free media of your project&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;a href=&quot;https://github.com/Halleck45/oss&quot;&gt;OSS&lt;/a&gt; is a simple binary, without dependencies, and Open Source. It allows you to manage the free media of your project.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is why I created a tool to help me manage the free media in a project: &lt;a href=&quot;https://github.com/Halleck45/OSS&quot;&gt;OSS&lt;/a&gt;. The objectives are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;to encourage developers to explicitly declare the free media they use;&lt;/li&gt;
  &lt;li&gt;to help developers to manage licenses;&lt;/li&gt;
  &lt;li&gt;to rationalize licenses using the &lt;a href=&quot;http://spdx.org/licenses/&quot;&gt;SPDX&lt;/a&gt; repository.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/Halleck45/oss/master/doc/overview.gif&quot; alt=&quot;OSS&quot; /&gt;&lt;/p&gt;

&lt;p&gt;OSS is a simple binary, written in Go, that you can download from &lt;a href=&quot;https://github.com/Halleck45/oss/releases/latest&quot;&gt;the latest release&lt;/a&gt;. It does not require any external dependencies.&lt;/p&gt;

&lt;p&gt;On first use, simply run the &lt;code&gt;oss init&lt;/code&gt; command. This will look for the SPDX repository and create the &lt;code&gt;.oss&lt;/code&gt; file at the root of your project.&lt;/p&gt;

&lt;p&gt;Then it’s quite simple; the commands are similar to those of Git:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;oss add &amp;lt;licence&amp;gt; &amp;lt;fichier&amp;gt;&lt;/code&gt; : reference a file&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;oss rm &amp;lt;fichier&amp;gt;&lt;/code&gt; : dereference a file&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;oss status&lt;/code&gt; : status of the repository, lists all referenced media&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;oss show&lt;/code&gt; &lt;fichier&gt; : information about a file&lt;/fichier&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A file appears in red when it is not found in the project.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2015-02-oss.png&quot; alt=&quot;Exemple de sortie de la commande oss status&quot; /&gt;&lt;/p&gt;

&lt;p&gt;One of the objectives is to help developers to manage licenses, the tool comes with the following commands:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;oss licenses&lt;/code&gt; : lists the licenses of the SPDX repository&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;oss search &amp;lt;licence&amp;gt;&lt;/code&gt; : search for a license&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the license does not exist when adding a media, the tool will suggest a license phonetically close.
&lt;strong&gt;It is impossible to add a media if its license is not part of the SPDX repository&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;this-will-not-be-enough-we-need-the-involvement-of-everyone&quot;&gt;This will not be enough: we need the involvement of everyone&lt;/h2&gt;

&lt;p&gt;I would like a tool capable of listing all the licenses of the bricks of a project. I would love to add to OSS a “scan” function, which would discover the licenses of Bower, Composer, Npm, Gem dependencies…&lt;/p&gt;

&lt;p&gt;Technically nothing complicated; the code is almost ready. No, the real problem comes from the developers. Indeed,
rare are the dependency management tools that require / encourage to declare a valid license. Licenses are often empty or unusable.&lt;/p&gt;

&lt;p&gt;And even if that were the case, a major problem comes from the dependency management tools themselves. Take Bower for example; it is possible
to obtain information about a package through the API. For example the HTTP request &lt;code&gt;http://bower.herokuapp.com/packages/jquery&lt;/code&gt; will give us:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;{&amp;quot;name&amp;quot;:&amp;quot;jquery&amp;quot;,&amp;quot;url&amp;quot;:&amp;quot;git://github.com/jquery/jquery.git&amp;quot;}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;But as you can see, there is no information about the license.&lt;/strong&gt; It then takes patches of patches to successfully retrieve the correct license in the &lt;code&gt;LICENSE&lt;/code&gt; file of the associated Git repository.&lt;/p&gt;

&lt;p&gt;And this is just one example! In short, the real problem is that &lt;strong&gt;developers, although fervent users of Open Source, are not yet used to interacting with free software&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example, a few days ago I intervened on a well-advanced project, which uses a specific NodeJs component. Curious, I opened
the &lt;code&gt;LICENSE&lt;/code&gt; file of the component in question; and there, surprise: the component was not necessarily so free of rights. When I
informed the technical team of this information, I had the right to the following answer:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“But yet it’s Github, we can get the source code, so it’s free”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;No! &lt;strong&gt;Everything on Github is not free&lt;/strong&gt;. Moreover &lt;strong&gt;by default, any project deposited on Github is proprietary&lt;/strong&gt;, unless otherwise stated in the sources.
Putting your project on Github is good, but let’s not forget to associate a real license, exploitable and clear.&lt;/p&gt;

&lt;p&gt;There are &lt;a href=&quot;http://spdx.org/licenses/&quot;&gt;comprehensive repositories&lt;/a&gt; ready to use; it’s time to make our tools compatible with the world of free software.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;OSS is a simple tool, but I hope it will help developers to better manage the free media of their projects.&lt;/p&gt;

&lt;p&gt;Feel free to share it, improve it… All ideas are welcome.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;💡 &lt;strong&gt;Tips&lt;/strong&gt;&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&quot;https://archive.fosdem.org/2014/schedule/event/spdx/&quot;&gt;Conférence Licensing and Packaging FOSS with SPDX&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://spdx.org/licenses/&quot;&gt;SPDX&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;
</content>
 </entry>
 
 <entry>
   <title>OSS : Le couteau suisse pour gérer vos fichiers externes libres de droit.</title>
   <link href="https://blog.lepine.pro/open-source-libre-gestion-des-medias"/>
   <updated>2015-02-25T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/open-source-libre-medias</id>
   <content type="html">&lt;p&gt;&lt;code&gt;Composer&lt;/code&gt;, &lt;code&gt;NPM&lt;/code&gt;… autant d’outils pour gérer les dépendances techniques de nos projets. C’est bien. 
&lt;strong&gt;Mais quid de la gestion des licenses des fichiers téléchargés ?&lt;/strong&gt; Et que 
faire des médias (images, sons, vidéos) libres ou open source que nous utilisons ?&lt;/p&gt;

&lt;p&gt;Par exemple, prenez cette illustration que vous versionnez dans votre projet. &lt;strong&gt;Dans 6 mois, vous ne vous souviendrez plus d’où elle vient, ni sous quelle licence elle est distribuée.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Prenons le problème des médias. Il existe des outils (&lt;a href=&quot;https://www.openhub.net&quot;&gt;OpenHub&lt;/a&gt; par exemple), mais rien de vraiment lié au quotidien du développeur. Jusqu’ici j’avais tendance à noter les images que j’utilise dans un fichier texte. Mais cette 
démarche est un peu brouillonne, et à long terme je m’y perd entre les images qui sont vraiment utilisées sur mon site et celles que j’ai 
téléchargées “pour tester”.&lt;/p&gt;

&lt;h2 id=&quot;oss-un-outil-pour-gérer-les-médias-libres-de-votre-projet&quot;&gt;OSS, un outil pour gérer les médias libres de votre projet&lt;/h2&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;a href=&quot;https://github.com/Halleck45/oss&quot;&gt;OSS&lt;/a&gt;, est un simple binaire, sans dépendance, et Open Source. Il vous permet de gérer les médias libres de droit de votre projet.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;D’où  mon idée de créer un outil pour me faciliter la gestion des médias libres dans un projet : &lt;a href=&quot;https://github.com/Halleck45/OSS&quot;&gt;OSS&lt;/a&gt;. Les objectifs sont :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;d’inciter les développeurs à déclarer explicitement les médias libres de droit qu’ils utilisent ;&lt;/li&gt;
  &lt;li&gt;d’aider les développeurs à s’y retrouver dans leur gestion des licences ;&lt;/li&gt;
  &lt;li&gt;de rationnaliser les licenses en utilisant le référentiel &lt;a href=&quot;http://spdx.org/licenses/&quot;&gt;SPDX&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/Halleck45/oss/master/doc/overview.gif&quot; alt=&quot;OSS&quot; /&gt;&lt;/p&gt;

&lt;p&gt;OSS est un simple binaire, écrit en Go, qu’il suffit de télécharger depuis &lt;a href=&quot;https://github.com/Halleck45/oss/releases/latest&quot;&gt;la dernière release&lt;/a&gt;. Il ne nécessite aucune dépendance externe.&lt;/p&gt;

&lt;p&gt;Au premier usage, lancez simplement la commande &lt;code&gt;oss init&lt;/code&gt;. Cette dernière va chercher le référentiel SPDX et va créer le fichier &lt;code&gt;.oss&lt;/code&gt; à la racine de votre projet. 
C’est ce fichier qui va désormais servir d’annuaire de vos médias.&lt;/p&gt;

&lt;p&gt;Ensuite c’est assez simple ; les commandes sont proches de celles de Git :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;oss add &amp;lt;licence&amp;gt; &amp;lt;fichier&amp;gt;&lt;/code&gt; : référencer un fichier&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;oss rm &amp;lt;fichier&amp;gt;&lt;/code&gt; : déréférencer un fichier&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;oss status&lt;/code&gt; : état du référentiel, liste l’ensemble des médias référencés&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;oss show&lt;/code&gt; &lt;fichier&gt; : informations sur un fichier&lt;/fichier&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Un fichier apparaît en rouge quand il n’est pas trouvé dans le projet.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2015-02-oss.png&quot; alt=&quot;Exemple de sortie de la commande oss status&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Un des objectifs étant d’aider les développeurs à s’y retrouver dans la gestion des licenses, l’outil vient avec les commandes suivantes :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;oss licenses&lt;/code&gt; : liste les licenses du référentiel SPDX&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;oss search &amp;lt;licence&amp;gt;&lt;/code&gt; : recherche une licence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Si la licence n’existe pas lors de l’ajout d’un média, l’outil suggerera une license phonétiquement proche. 
&lt;strong&gt;Il est impossible d’ajouter un média si sa licence ne fait pas partie du référentiel SPDX&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;ca-ne-suffira-pas--il-faut-limplication-de-chacun&quot;&gt;Ca ne suffira pas : il faut l’implication de chacun&lt;/h2&gt;

&lt;p&gt;J’aimerai un outil capable de répertorier l’ensemble des licenses des briques d’un projet. J’aimerai beaucoup 
ajouter à OSS une fonction “scan”, qui découvrirait les licences des dépendances Bower, Composer, Npm, Gem…&lt;/p&gt;

&lt;p&gt;Techniquement rien de bien compliqué ; le code est quasi prêt. Non, le vrai problème vient des développeurs. En effet, 
rares sont les outils de gestion de dépendances qui imposent / incitent à déclarer une licence valide. Les licenses sont souvent vides ou inexploitables.&lt;/p&gt;

&lt;p&gt;Et même si c’était le cas, un problème majeur vient des outils de gestion de dépendances eux-mêmes. Prenez Bower par exemple ; il est possible 
d’obtenir des informations sur un paquet grâce à l’API. Par exemple la requête HTTP &lt;code&gt;http://bower.herokuapp.com/packages/jquery&lt;/code&gt; nous donnera :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;{&amp;quot;name&amp;quot;:&amp;quot;jquery&amp;quot;,&amp;quot;url&amp;quot;:&amp;quot;git://github.com/jquery/jquery.git&amp;quot;}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;Mais comme vous le voyez, il n’y a aucune information sur la license.&lt;/strong&gt; Il faut alors des rustines de rustines pour réussir à récupérer la bonne licence dans le fichier &lt;code&gt;LICENSE&lt;/code&gt; du dépôt Git associé.&lt;/p&gt;

&lt;p&gt;Et ce n’est qu’un exemple ! Bref, le vrai problème, c’est que &lt;strong&gt;les développeurs, pourtant fervents utilisateurs de l’Open Source, ne sont pas encore 
habitués à intéragir avec le logiciel libre&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A titre d’exemple, il y a quelques jours je suis intervenu sur un projet bien entamé, qui utilise un composant NodeJs spécifique. Curieux, j’ai ouvert 
le fichier &lt;code&gt;LICENSE&lt;/code&gt; du composant en question ; et là, surprise : le composant n’était pas forcément si libre de droits que ça. Lorsque j’ai 
fait part de ces informations à l’équipe technique, j’ai eu le droit comme réponse à :&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“Mais pourtant c’est Github, on peut récupérer le code source, donc c’est gratuit”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Non ! &lt;strong&gt;Tout ce qui est sur Github n’est pas gratuit&lt;/strong&gt;. D’ailleurs &lt;strong&gt;par défaut, tout projet déposé sur Github est propriétaire&lt;/strong&gt;, sauf avis contraire dans les sources. 
Mettre son projet sur Github c’est bien, mais n’oublions pas d’y associer une vraie licence, exploitable et claire.&lt;/p&gt;

&lt;p&gt;Il existe des &lt;a href=&quot;http://spdx.org/licenses/&quot;&gt;référentiels&lt;/a&gt; assez complets et prêts à l’emploi ; il est temps de rendre nous outils compatibles avec monde du libre.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;OSS est un outil simple, mais qui je l’espère, pourra aider les développeurs à mieux gérer les médias libres de leurs projets.&lt;/p&gt;

&lt;p&gt;N’hésitez pas à le partager, l’améliorer… Toutes les idées sont les bienvenues.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;💡 &lt;strong&gt;Pour aller plus loin&lt;/strong&gt; :&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&quot;https://archive.fosdem.org/2014/schedule/event/spdx/&quot;&gt;Conférence Licensing and Packaging FOSS with SPDX&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://spdx.org/licenses/&quot;&gt;SPDX&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;
</content>
 </entry>
 
 <entry>
   <title>Semantic Versionning automatisé</title>
   <link href="https://blog.lepine.pro/semver-git-tags"/>
   <updated>2015-01-07T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/semver-automatique-au-build</id>
   <content type="html">&lt;p&gt;Pour résumer, le &lt;a href=&quot;http://semver.org/lang/fr/&quot;&gt;sémantique versionning&lt;/a&gt; est une logique de fabrication des numéros de version d’un produit 
où l’on identifie une version v1.2.3 telle que décrite sur le site officiel :&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Étant donné un numéro de version MAJEUR.MINEUR.CORRECTIF, il faut incrémenter :&lt;/p&gt;

  &lt;p&gt;le numéro de version MAJEUR quand il y a des changements rétro-incompatibles,
le numéro de version MINEUR quand il y a des changements rétro-compatibles,
le numéro de version de CORRECTIF quand il y a des corrections d’anomalies rétro-compatibles&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;La prise de décision d’incrémenter une version ne peut être que manuelle&lt;/strong&gt; : il faut un humain pour savoir si ce qui a été 
modifié concerne un correctif ou une nouvelle fonctionnalité…&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Par contre, il peut-être utile de répercuter cette prise de décision automatiquement&lt;/strong&gt;. Par exemple pour tagger un dépôt Git, 
ou encore pour changer un fichier de code source qui contiendrait la version actuelle en dur. C’est ce que j’ai fait pour &lt;a href=&quot;http://www.phpmetrics.org&quot;&gt;PhpMetrics&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;À l’heure où j’écris ces lignes, la version actuelle de PhpMetrics est la v1.2.0, comme nous le montre la commande suivante :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;php phpmetrics.phar --version&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;La version de l’application est stockée en dur dans le fichier de création du phar (&lt;code&gt;build.php&lt;/code&gt;):&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;$app = new Hal\...\PhpMetricsApplication(&amp;#39;...&amp;#39;, &amp;#39;1.2.1&amp;#39;);&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;À chaque montée de version, je dois donc penser à modifier ce fichier, puis à créer le tag Git ; ce que j’oublie tout le temps de faire… Il me faut donc l’automatiser.&lt;/p&gt;

&lt;p&gt;Première étape : trouver un moyen simple de stocker mon numéro de version actuelle et de l’incrémenter. Ma première idée 
était de me tourner vers les tags Git en parsant le résultat d’un &lt;code&gt;git describe --tags&lt;/code&gt;, mais j’ai trouvé plus simple : &lt;a href=&quot;https://github.com/flazz/semver/&quot;&gt;semver&lt;/a&gt;, un outil qui fait ça 
très bien. Pour incrémenter une version je n’ai plus qu’à lancer, au choix :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;semver inc major
semver inc minor
semver inc patch&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Et je récupère la version actuelle avec la commande &lt;code&gt;semver tag&lt;/code&gt;. (les infos sont stockées dans un fichier &lt;code&gt;.semver&lt;/code&gt; à la racine du projet).&lt;/p&gt;

&lt;p&gt;Au lieu de mettre en dur la version dans mes sources, j’utilise donc un tag:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;$app = new Hal\...\PhpMetricsApplication(&amp;#39;...&amp;#39;, &amp;#39;&amp;lt;VERSION&amp;gt;&amp;#39;);&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Il faut désormais remplacer ce tag par la vraie version récupérée avec &lt;code&gt;semver tag&lt;/code&gt;. &lt;code&gt;sed&lt;/code&gt; est mon meilleur allié :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;sed -i &amp;quot;s/&amp;lt;VERSION&amp;gt;/`semver tag`/g&amp;quot; build.php&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Reste enfin à créer un tag git pour cette release :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;git tag -a $(semver tag) -m &amp;quot;tagging $(semver tag)&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Et voilà le travail ! Un petit Makefile et le tour est joué. Voici pour les plus curieux le Makefile de PhpMetrics:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;REPLACE=`semver tag`
    
# Build phar
build: test
    @echo Copying sources
    @mkdir -p /tmp/phpmetrics-build
    @cp * -R /tmp/phpmetrics-build
    @rm -Rf /tmp/phpmetrics-build/vendor /tmp/phpmetrics-build/composer.lock
    
    @echo Releasing phar
    @sed -i &amp;quot;s/&amp;lt;VERSION&amp;gt;/`semver tag`/g&amp;quot; /tmp/phpmetrics-build/build.php
    
    @echo Installing dependencies
    @cd /tmp/phpmetrics-build &amp;amp;&amp;amp; composer.phar install --no-dev --optimize-autoloader --prefer-dist

    @echo Building phar
    @cd /tmp/phpmetrics-build &amp;amp;&amp;amp; php build.php
    @cp /tmp/phpmetrics-build/build/phpmetrics.phar build/phpmetrics.phar
    @rm -Rf /tmp/phpmetrics-build
    
    @echo Testing phar
    ./vendor/bin/phpunit -c phpunit.xml.dist --group=binary &amp;amp;&amp;amp;	echo &amp;quot;Done&amp;quot;


# Run unit tests
test:
    ./vendor/bin/phpunit -c phpunit.xml.dist


# Publish new release. Usage:
#   make tag VERSION=(major|minor|patch)
# You need to install https://github.com/flazz/semver/ before
tag:
    @semver inc $(VERSION)
    @echo &amp;quot;New release: `semver tag`&amp;quot;


# Tag git with last release
git_tag:
    git tag -a $(semver tag) -m &amp;quot;tagging $(semver tag)&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Lorsque je veux une nouvelle version, il ne me reste donc que deux lignes à taper pour construire mon phar avec la bonne version:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;make tag VERSION=minor
make
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;La commande &lt;code&gt;php phpmetrics.phar --version&lt;/code&gt; indique désormais la bonne version.&lt;/p&gt;

&lt;p&gt;Je sais qu’il existe des plugins Jenkins pour ça, mais je souhaitais pouvoir exécuter le processus manuellement si besoin, directement dans mon terminal.&lt;/p&gt;

&lt;p&gt;je me doute qu’il existe des solutions plus pertinentes que celle que je décris ici ; vos retours sont donc les bienvenus.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Intégration continue : utiliser le fichier .travis.yml dans Jenkins avec Docker</title>
   <link href="https://blog.lepine.pro/travis-ci-jenkins-et-docker"/>
   <updated>2014-05-29T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/travis-ci-jenkins-docker</id>
   <content type="html">&lt;p&gt;J’utilise massivement &lt;a href=&quot;https://travis-ci.org/&quot;&gt;travis-ci&lt;/a&gt; comme plate-forme d’intégration continue
pour mes projets open source.&lt;/p&gt;

&lt;p&gt;Pour cela, il suffit d’ajouter un simple fichier &lt;code&gt;.travis.yml&lt;/code&gt;. Voici par exemple celui que j’utilise pour
&lt;a href=&quot;https://github.com/Halleck45/PhpMetrics&quot;&gt;PhpMetrics&lt;/a&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;language: php
php:
  - 5.3
  - 5.4
before_script:
  - wget http://getcomposer.org/composer.phar
  - php composer.phar install --dev --prefer-dist

script:
  - ./vendor/bin/phpunit -c phpunit.xml.dist&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Ce qui est génial avec Travis-ci, c’est de pouvoir lancer son build sur plusieurs environnements (ici PHP 5.3 et PHP 5.4).&lt;/p&gt;

&lt;p&gt;J’ai également une PIC locale , pour laquelle j’utilise &lt;a href=&quot;http://jenkins-ci.org/&quot;&gt;Jenkins&lt;/a&gt;, et qui me permet de suivre
l’ensemble de ses projets personnels. Et je dois avouer que ça m’agace de configuer deux fois mon build : et dans Jenkins, et dans Travis.&lt;/p&gt;

&lt;p&gt;Voici donc un moyen minimaliste de builder un projet dans Jenkins, dans des environnements isolés (grâce à Docker), en
utilisant le même fichier &lt;code&gt;.travis.yml&lt;/code&gt; qu’avec Travis-ci.&lt;/p&gt;

&lt;h2 id=&quot;installation-de-docker-et-configuration-des-containers&quot;&gt;Installation de Docker et configuration des containers&lt;/h2&gt;

&lt;p&gt;Je ne vais pas m’attarder sur Docker : de nombreux tutoriels existent sur le net, et la documentation est assez bien faite.
Sachez juste que c’est ce qui va nous permettre de virtualiser nos environnements.&lt;/p&gt;

&lt;p&gt;Tout ce dont on a besoin pour l’installation (debian) tient sur ces quelques lignes :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;#!/bin/bash
# fichier install.sh

# Dépendances PIP (pour le Yaml)
apt-get update  -y -q
apt-get install python-pip -y
pip install shyaml

# Docker
apt-get install docker.io  -y
ln -sf /usr/bin/docker.io /usr/local/bin/docker

# Containers
docker build -t debian/5.3 - &amp;lt; Dockerfile53
docker build -t debian/5.4 - &amp;lt; Dockerfile54
docker build -t debian/5.5 - &amp;lt; Dockerfile55&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Les trois dernières lignes sont intéressantes. Ce sont elles qui vont créer nos environnements de test. Docker permet en
effet de provisionner et configurer un container à l’aide d’un fichier de configuration (le fameux &lt;code&gt;Dockerfile&lt;/code&gt;, mais ici qui s’appelle &lt;code&gt;Dockerfile53&lt;/code&gt;, &lt;code&gt;Dockerfile54&lt;/code&gt;, etc). Voici ceux que j’ai
utilisé pour cet exemple, libre à vous de les enrichir :&lt;/p&gt;

&lt;p&gt;pour PHP 5.3 :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;# fichier Dockerfile53
FROM debian:squeeze
RUN apt-get update -y -q
RUN apt-get install  php5-common php5-cli php5-curl php5-xdebug wget -y --force-yes
RUN apt-get clean &amp;amp;&amp;amp; rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;pour PHP 5.4 :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;# fichier Dockerfile54
FROM debian
RUN echo &amp;quot;deb http://packages.dotdeb.org wheezy all&amp;quot; |  tee -a /etc/apt/sources.list
RUN echo &amp;quot;deb-src http://packages.dotdeb.org wheezy all&amp;quot; |  tee -a /etc/apt/sources.list
RUN apt-get update -y -q
RUN apt-get install  php5-cli php5-curl php5-apc php5-xdebug wget -y --force-yes
RUN apt-get clean &amp;amp;&amp;amp; rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;pour PHP 5.5 :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;# fichier Dockerfile55
FROM debian
RUN echo &amp;quot;deb http://packages.dotdeb.org wheezy-php55 all&amp;quot; |  tee -a /etc/apt/sources.list
RUN echo &amp;quot;deb-src http://packages.dotdeb.org wheezy-php55 all&amp;quot; |  tee -a /etc/apt/sources.list
RUN apt-get update -y -q
RUN apt-get install  php5-cli php5-curl php5-apc php5-xdebug wget -y --force-yes
RUN apt-get clean &amp;amp;&amp;amp; rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Si vous lancez maintenant les commandes d’installation qui sont plus haut, vous vous retrouverez avec trois containers :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;debian/5.3&lt;/li&gt;
  &lt;li&gt;debian/5.4&lt;/li&gt;
  &lt;li&gt;debian/5.5&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Leurs noms sont très importants : ce sont eux qui vont nous permettre de faire le lien avec le fichier &lt;code&gt;.travis.yml&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;lancement-des-tests&quot;&gt;Lancement des tests&lt;/h2&gt;

&lt;p&gt;L’objectif est de ne plus avoir à configurer quoique ce soit dans Jenkins. Nous allons donc créer un script qui va
lire le fichier &lt;code&gt;.travis.yml&lt;/code&gt;, repérer les commandes à exécuter (noeuds &lt;code&gt;before_script&lt;/code&gt; et &lt;code&gt;script&lt;/code&gt;),
puis lancer ces commandes pour chaque environnement souhaité.&lt;/p&gt;

&lt;p&gt;Pour les plus pressés, voici ce script (et voici &lt;a href=&quot;https://gist.github.com/Halleck45/be9eb3270cea0c9c28ab&quot;&gt;le gist&lt;/a&gt; si vous souhaitez l’améliorer).&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;#!/bin/bash
EXIT_STATUS=0

echo &amp;quot;#!/bin/bash&amp;quot; &amp;gt; ./docker-travis-test.sh
cat .travis.yml | shyaml get-values before_script &amp;gt;&amp;gt; ./docker-travis-test.sh
cat .travis.yml | shyaml get-values script &amp;gt;&amp;gt; ./docker-travis-test.sh


PHP_VERSIONS=`cat .travis.yml | shyaml get-values php`
for PHP_VERSION in $PHP_VERSIONS
do

    echo
    echo &amp;quot;Running tests for PHP $PHP_VERSION&amp;quot;
    echo &amp;quot;===================================&amp;quot;


    DOCKER_IMG=debian/$PHP_VERSION


    DOCKER_ID=$(docker run -d -t $DOCKER_IMG /bin/bash)
    docker run  -v `pwd`:/project -w &amp;quot;/project&amp;quot; -t $DOCKER_IMG /bin/bash ./docker-travis-test.sh

    CODE=$?
    case &amp;quot;$CODE&amp;quot; in
     0) RESULT=&amp;quot;OK&amp;quot;
        OUTPUT=&amp;quot;${OUTPUT}PHP ${PHP_VERSION}: OK \n&amp;quot;
        ;;
     *)
        RESULT=&amp;quot;ERROR ($CODE)&amp;quot;
        OUTPUT=&amp;quot;${OUTPUT}PHP ${PHP_VERSION}: Error (code ${CODE}) \n&amp;quot;
        EXIT_STATUS=1
        ;;
    esac

    DOCKER_IDS=&amp;quot;${DOCKER_IDS} ${DOCKER_ID}&amp;quot;
done


docker restart $DOCKER_ID
$(docker wait ${DOCKER_IDS})

echo
echo &amp;quot;Results&amp;quot;
echo &amp;quot;===================================&amp;quot;
echo -e $OUTPUT;

exit $EXIT_STATUS&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Passons aux explications :)&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;echo &amp;quot;#!/bin/bash&amp;quot; &amp;gt; ./docker-travis-test.sh
cat .travis.yml | shyaml get-values before_script &amp;gt;&amp;gt; ./docker-travis-test.sh
cat .travis.yml | shyaml get-values script &amp;gt;&amp;gt; ./docker-travis-test.sh&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Ces quelques lignes permettent de concaténer les contenus des noeuds &lt;code&gt;before_script&lt;/code&gt; et &lt;code&gt;script&lt;/code&gt;  dans un fichier nommé
arbitrairement &lt;code&gt;./docker-travis-test.sh&lt;/code&gt;. Ce fichier sera donc executé pour lancer les tests. La lecture du fichier yaml
s’effectue grâce à &lt;a href=&quot;https://github.com/0k/shyaml&quot;&gt;shyaml&lt;/a&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;PHP_VERSIONS=`cat .travis.yml | shyaml get-values php`
for PHP_VERSION in $PHP_VERSIONS
do
    ...
done&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Ces lignes extraient du fichier &lt;code&gt;.travis.yml&lt;/code&gt; la liste des versions de PHP sur lesquelles ont souhaite exécuter nos tests, puis
itèrent sur chaque version ($PHP_VERSION vaut ici “5.3” puis “5.4”)&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;DOCKER_IMG=debian/$PHP_VERSION&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Cette ligne est très importante : c’est elle qui fait le lien entre la version souhaitée et un container Docker. Rappelez-vous
que l’on a nommé nos containers “debian/5.3”, “debian/5.4” et “debian/5.5”.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;DOCKER_ID=$(docker run -d -t $DOCKER_IMG /bin/bash)
docker run  -v `pwd`:/project -w &amp;quot;/project&amp;quot; -t $DOCKER_IMG /bin/bash ./docker-travis-test.sh&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;C’est là que tout se passe : on récupère l’ID de notre container (pour pouvoir travailler avec), puis on lance le script
&lt;code&gt;./docker-travis-test.sh&lt;/code&gt; que l’on a créé plus haut sur notre code.&lt;/p&gt;

&lt;p&gt;À noter :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;-w /project&lt;/code&gt; définit l’espace de travail du container&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;-v \~pwd\~:/project&lt;/code&gt; permet de partager le dossier courant avec le container&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;CODE=$?
case &amp;quot;$CODE&amp;quot; in
 0) RESULT=&amp;quot;OK&amp;quot;
    OUTPUT=&amp;quot;${OUTPUT}PHP ${PHP_VERSION}: OK \n&amp;quot;
    ;;
 *)
    RESULT=&amp;quot;ERROR ($CODE)&amp;quot;
    OUTPUT=&amp;quot;${OUTPUT}PHP ${PHP_VERSION}: Error (code ${CODE}) \n&amp;quot;
    EXIT_STATUS=1
    ;;
esac

...

echo
echo &amp;quot;Results&amp;quot;
echo &amp;quot;===================================&amp;quot;
echo -e $OUTPUT;

exit $EXIT_STATUS&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Ces lignes permettent de gérer les codes de retour d’erreur, et concatènent le résultat de chaque jeu de tests dans la variable
&lt;code&gt;OUTPUT&lt;/code&gt; afin de l’afficher à la fin du build.&lt;/p&gt;

&lt;h2 id=&quot;exemple-dans-jenkins&quot;&gt;Exemple dans Jenkins&lt;/h2&gt;

&lt;p&gt;Voici donc à quoi ressemblent mes builds Jenkins désormais&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2014-05-jenkins-docker-travis-exemple.png&quot; alt=&quot;Exemple de configuration Jenkins qui utilise Docker et le fichier .travis.yml&quot; /&gt;&lt;/p&gt;

&lt;p&gt;La prochaine étape serait la parallélisation (le plus simple me paraît d’utiliser &lt;a href=&quot;http://www.gnu.org/software/parallel/&quot;&gt;parallel&lt;/a&gt;),
et bien sûr de rendre tout ça un peu plus propre (rollback des containers, nettoyage…).
En attendant, à vous de jouer :)&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Qualité logicielle : comment fixer les valeurs limites ?</title>
   <link href="https://blog.lepine.pro/industrialisation/bornes-pour-les-indicateurs-et-metriques"/>
   <updated>2014-05-07T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/industrialisation/bornes-pour-les-indicateurs-et-metriques</id>
   <content type="html">&lt;p&gt;Plus j’avance dans la réalisation de &lt;a href=&quot;https://github.com/Halleck45/PhpMetrics&quot;&gt;PhpMetrics&lt;/a&gt;, plus une question se pose :
je dispose de métriques sur le code source, mais à partir de quel moment alerter l’utilisateur ? Quelle est la valeur idéale ?
Quelles sont les valeurs minimales et maximales ?&lt;/p&gt;

&lt;p&gt;Je vous propose un petit retour d’expérience sur la manière dont j’ai tenté de fixer ces valeurs pour PhpMetrics. Ce qu’il faut savoir,
c’est que j’ai procédé itérativement selon la manière suivante :&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;utilisation de valeurs théoriques&lt;/li&gt;
  &lt;li&gt;pondération de la théorie&lt;/li&gt;
  &lt;li&gt;analyse démographique&lt;/li&gt;
  &lt;li&gt;ajustement par l’expérience&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;étape-1--utilisation-de-valeurs-théoriques&quot;&gt;Étape 1 : Utilisation de valeurs théoriques&lt;/h2&gt;

&lt;p&gt;La première méthode est la plus simple : la plupart des métriques logicielles que j’ai intégrées sont très bien théorisées, et de
des suggestions de bornes maximales et minimales existent assez souvent.&lt;/p&gt;

&lt;p&gt;Prenez LCOM4 par exemple. Cet indicateur (Lack of cohesion of methods) tend à révéler la cohésion d’une classe :&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;?php
class Example {
    private $a;

    public function m1() {
        $this-&amp;gt;m2();
    }

    public function m2() {
        $this-&amp;gt;a = 1;
    }

    public function m3() {
        $this-&amp;gt;a = 1;
    }

    public function m4() {
        $this-&amp;gt;m5();
    }

    public function m5() {
        echo &apos;ok&apos;;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Examinons les corrélations des méthodes de cette classe :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;m1()&lt;/code&gt; appelle &lt;code&gt;m2()&lt;/code&gt;. &lt;code&gt;m2()&lt;/code&gt; partage avec &lt;code&gt;m3&lt;/code&gt; un attribut commun &lt;code&gt;a&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;m4()&lt;/code&gt; appelle &lt;code&gt;m5()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Il y a donc deux flux de code bien distincts dans cette classe. On dit alors que &lt;code&gt;LCOM4(Example) = 2&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Cet indicateur est à mettre en relation avec le &lt;code&gt;S&lt;/code&gt; des principes SOLID (Single reponsability).
Les méthodes d’une classe qui servent une besoin commun s’articulent ensemble : elles s’appellent les unes les autres ou
partagent des attributs communs. Il est donc probable ici que notre classe possède au moins deux reponsabilités.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;D’un point de vue purement théorique&lt;/strong&gt;, la valeur idéale de LCOM4 d’une classe est donc de 1.&lt;/p&gt;

&lt;h2 id=&quot;étape-2--pondération-de-la-théorie&quot;&gt;Étape 2 : Pondération de la théorie&lt;/h2&gt;

&lt;p&gt;Mais il ne faut pas oublier que ces indicateurs, bien que volontairement agnostiques, ont tout de même été fortement orientés par les
langages de programmation utilisés pour les théoriser / illustrer.&lt;/p&gt;

&lt;p&gt;Revenons à LCOM4, et à l’idéal théorique de &lt;code&gt;LCOM4(classe) = 1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Cet idéal signifie que toutes les méthodes d’une classes fonctionnent ensemble (principe de cohésion). Or la plupart des
classes PHP comportent un très grand nombre de getters/setters, souvent inutilisés à l’intérieur de la classe même.
Vu par un robot, les &lt;strong&gt;getters/setters méthodes semblent des méthdoes orphelines&lt;/strong&gt;, et augmentent donc artificiellement LCOM4.&lt;/p&gt;

&lt;p&gt;Cet aspect est propre aux langages qui ne comportent pas de sucre syntaxique pour les getters et setters.
En C#, on pourrait simplement écrire&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;public int X { get; set; }
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;En PHP ce n’est &lt;a href=&quot;https://wiki.php.net/rfc/propertygetsetsyntax&quot;&gt;pas encore le cas&lt;/a&gt;. il faut donc pondérer cet indicateur.&lt;/p&gt;

&lt;p&gt;Il en va de même pour la majorité des indicateurs : le code écrit en PHP est assez typique : typique par le langage même,
mais aussi par l’orientation des projets qui l’utilisent, assez souvent plus orientés production (court terme) plutôt qu’évolution (long terme).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Petite parenthèse utile : je ne cherche pas à polémiquer, juste à faire un constat. Ce constat pourrait tout à fait être identique pour d’autres langages de programmation.
Oui, je sais qu’il y a d’excellents projets PHP, orientés très long terme.
Mais je pense ici faire un constat d’ordre général selon &lt;strong&gt;mon&lt;/strong&gt; expérience. Bref, j’essaye d’être &lt;strong&gt;pragmatique&lt;/strong&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;étape-3--analyse-démographique&quot;&gt;Étape 3 : Analyse démographique&lt;/h2&gt;

&lt;p&gt;Il m’a donc fallu trouver un moyen de déterminer de nouvelles bornes, proches de la téhorie, mais plus adéquates à PHP.&lt;/p&gt;

&lt;p&gt;Le plus simple m’a semblé de faire une analyse démographique de projets PHP. Idéalement de projets représentatifs.
Coup de chance, packagist fournit la liste des paquets les plus utilisés. Je me suis donc mis à les analyser.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[Spoiler]&lt;/strong&gt; La première chose à comprendre est que cette analyse est biaisée :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;les projets sont open source. Ils sont fait par des volontaires, avec un processus souvent plus qualitatif (revue de code) que des projets d’entreprise&lt;/li&gt;
  &lt;li&gt;les projets recensés sur packagist sont ceux qui sont déclarés comme paquet Composer. Ils sont donc, pour la plupart, très récents.&lt;/li&gt;
  &lt;li&gt;les projets open source concernent généralement de l’outillage (composants, frameworks, librairies…). Ils ne sont pas représentatifs de projet fonctionnement complexes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;[/Spoiler]&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Je reviendrai sur ces aspects par la suite. En attendant, je pense que cette analyse, bien que biaisée, reste pertinente si on la prend pour ce qu’elle est, et rien de plus : une analyse de projets bien spécifiques, représentatifs d’une partie seulement des typologies de projets possibles.&lt;/p&gt;

&lt;p&gt;Le processus d’analyse est le suivant :&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Téléchargement des sources&lt;/li&gt;
  &lt;li&gt;Exécution de l’analyse sur un échantillon représentatif (environ 5 000 classes)&lt;/li&gt;
  &lt;li&gt;Aggrégat des résultats&lt;/li&gt;
  &lt;li&gt;Elimination des extrêmes (par écart interquartile)&lt;/li&gt;
  &lt;li&gt;Analyse des percentiles de chaque série&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Je me suis alors retrouvé avec des résultats de ce type :&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2014-05-phpmetrics-resultats-analyse-demographique.png&quot; alt=&quot;Résultat de l&apos;analyse démographique des métriques&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Par tâtonnements, il m’a paru judicieux de considérer comme :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;borne inférieure =&amp;gt; le trentième percentile&lt;/li&gt;
  &lt;li&gt;borne supérieure =&amp;gt; le quatre vingtième percentile&lt;/li&gt;
  &lt;li&gt;borne supérieure maximale =&amp;gt; le quatre vingt dixième percentile&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Dans le cas du nombre de lignes de code, &lt;strong&gt;il en ressortirai qu’un fichier doit contenir idéalement entre 65 et 130 lignes de code, et au maximum 154.&lt;/strong&gt;
Ces chiffres ne semblent pas absurdes.&lt;/p&gt;

&lt;p&gt;Voici quelques graphiques qui illustrent ces résultats:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2014-05-graph-lines-of-code.png&quot; alt=&quot;Résultat de l&apos;analyse démographique - Nombre de lignes de code &quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2014-05-graph-cyclomatic-complexity.png&quot; alt=&quot;Résultat de l&apos;analyse démographique - Complexity cyclomatique &quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2014-05-graph-maintenability-index.png&quot; alt=&quot;Résultat de l&apos;analyse démographique - Indice de maintenabilité &quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2014-05-graph-lcom.png&quot; alt=&quot;Résultat de l&apos;analyse démographique - Lack of cohesion of methods &quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;étape-4--ajustements-par-lexpérience&quot;&gt;Étape 4 : Ajustements par l’expérience&lt;/h2&gt;

&lt;p&gt;Je l’ai dit : l’analyse démographique, bien qu’intéressante, est faussée : elle ne considère que certains types de projets (généralement des outils).&lt;/p&gt;

&lt;p&gt;Dans la vraie vie, la plupart des codes sources sont “en dehors de clous”. &lt;strong&gt;Facile d’avoir une complexité cyclomatique faible quand on n’a pas de délais&lt;/strong&gt;,
qu’on ne gère pas de changements fonctionnels fréquents ou qu’on ne gère pas de règles métiers…&lt;/p&gt;

&lt;p&gt;J’ai donc confronté ces indicateurs à mon expérience. J’ai en effet la chance de réaliser des audits chez de nombreux clients, ce qui me
permet d’être confronté à des projets très variés et nombreux . J’ai donc systématiquement analysé le code des projets
auxquels j’ai été confronté, et ce depuis  que je suis développeur (ou presque).&lt;/p&gt;

&lt;p&gt;Attention, là encore l’analyse est biaisée : quand on fait appel à un consultant, c’est en général que le projet subit des avaries, souvent dûes
à des problèmes techniques (code peu évolutif, architecture mal adaptée…). Le code de ces projets est donc généralement moins maintenable que sur des
 projets où tout se passe bien (même si ce n’est pas systématiquement le cas). On est donc dans la situation inverse des projets Open Source précédemment mentionnés.&lt;/p&gt;

&lt;p&gt;J’ai donc réajusté les indicateurs en fonction de ces analyses, et continue de les réajuster au fur-et-à-mesure.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Enfin, un point important reste à soulever. C’est d’ailleurs cet aspect qui m’a fait hésiter à fournir des indicateurs visuels
(rouge pour “critique”, jaune pour “à examiner” et vert pour “probablement correct”) : aucune borne, aucune limite n’est universelle.&lt;/p&gt;

&lt;p&gt;Lorsqu’on analyse un Controlleur, il est évident qu’on ne s’attend pas à avoir le même Vocabulaire que dans un Service. Une
Entité n’aura pas le même Indice de maintenabilité qu’un Validateur…&lt;/p&gt;

&lt;p&gt;La conclusion de ces analyses est donc assez triviale : c’est à l’humain de pondérer les chiffres. Un outil d’analyse ne fournit que des indicateurs, pas des vérités.&lt;/p&gt;

&lt;p&gt;Attention, il ne faut pas pour autant rejeter tout outil d’analyse statique (comme on le fait malheureusement trop souvent). Ces
indicateurs sont pratiques et indispensables si on sait les interpréter et les prendre pour ce qu’ils sont : &lt;strong&gt;des indicateurs&lt;/strong&gt;.
Ce qui compte, ce n’est pas de savoir si telle classe ou telle fichier a une complexité trop élevée. Non, ce qui est important c’est
de surveiller et de tenir compte de l’&lt;strong&gt;accumulation d’indicateurs et de leur orientation générale&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Si une classe a un peu trop de lignes de code ce n’est pas grave ; si les classes d’un paquet ont une Complexité cyclomatique
élevée, une LCOM élevée, un Indice de maintenabilité faible… là il faudra écouter les tendances des indicateurs, et se
pencher sur le code en question pour comprendre ce qui peut causer ces remontées.&lt;/p&gt;

&lt;quote&gt;**L&apos;analyse statique n&apos;est pas une diseuse de vérité, mais un un outil d&apos;alerte**&lt;/quote&gt;
</content>
 </entry>
 
 <entry>
   <title>Indice de maintenabilité d'un projet PHP et Jenkins</title>
   <link href="https://blog.lepine.pro/industrialisation/indice-de-maintenabilite-dun-projet-php-et-jenkins"/>
   <updated>2014-02-06T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/industrialisation/indice-de-maintenabilite-dun-projet-php-et-jenkins</id>
   <content type="html">&lt;p&gt;Dans mon &lt;a href=&quot;/php/la-maintenabilite-dun-projet-php-en-images&quot; title=&quot;La maintenabilité d’un projet PHP en images&quot;&gt;dernier billet&lt;/a&gt; je vous avais présenté un outil sur lequel je travaille : &lt;a href=&quot;https://github.com/Halleck45/PhpMetrics&quot;&gt;PhpMetrics&lt;/a&gt;. Cet outil permet de calculer différents indicateurs sur le code source, dont l&apos;Indice de Maintenabilité, le Poids des commentaires, la Difficulté d&apos;un code, etc.&lt;/p&gt;

&lt;p&gt;Je pense qu&apos;avoir une vision régulière de ce type d&apos;indicateurs est important pour assurer la qualité d&apos;un projet, ou au moins détecter les écarts de qualité assez rapidement. Bien que ce ne soit pas vraiment le rôle de Jenkins d&apos;assurer un tel suivi (mais plutôt d&apos;un outil comme Sonar, par exemple), mais vu que Jenkins reste un outils assez répandu aujourd&apos;hui, je vous propose de voir comment intégrer ces indicateurs de code dans Jenkins.&lt;/p&gt;

&lt;a href=&quot;https://blog.lepine.pro/images/2014-02-phpmetrics-chart.png&quot;&gt;&lt;img src=&quot;https://blog.lepine.pro/images/2014-02-phpmetrics-chart.png&quot; alt=&quot;Graphique PhpMetrics dans Jenkins&quot; width=&quot;740&quot; height=&quot;342&quot; class=&quot;aligncenter size-full wp-image-804&quot; /&gt;&lt;/a&gt;

&lt;p&gt;Si vous n&apos;avez jamais utilisé Jenkins, je vous invite à lire les excellents billets de Pascal Martin sur l&apos;&lt;a href=&quot;http://blog.pascal-martin.fr/post/integration-continue-jenkins-installation-configuration&quot;&gt;installation&lt;/a&gt; et la &lt;a href=&quot;http://blog.pascal-martin.fr/post/integration-continue-jenkins-projet-php&quot;&gt;configuration&lt;/a&gt; d&apos;un premier projet PHP. Et si vous êtes un peu fainéant, vous pouvez aussi directement utiliser la procédure d&apos;installation expliquée sur sur &lt;a href=&quot;http://jenkins-php.org&quot;&gt;jenkins-php.org&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Je pars du principe pour la suite que vous avez un job Jenkins fonctionnel.&lt;/p&gt;

&lt;h2&gt;Première étape: lancer l&apos;analyse. &lt;/h2&gt;

&lt;p&gt;Différentes options s&apos;offrent à vous. Si avoir Java sur votre machine d&apos;intégration ne vous dérange pas, vous pouvez utiliser &lt;a href=&quot;http://ant.apache.org/&quot;&gt;Ant&lt;/a&gt;. Voilà à quoi ressemblera votre fichier build.xml (par exemple):&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; ?&amp;gt;
&amp;lt;project name=&amp;quot;demo&amp;quot; default=&amp;quot;phpmetrics&amp;quot; basedir=&amp;quot;./&amp;quot;&amp;gt;

    &amp;lt;!-- source to analyze --&amp;gt;
    &amp;lt;property name=&amp;quot;src&amp;quot; value=&amp;quot;${basedir}/src&amp;quot;/&amp;gt;

    &amp;lt;target name=&amp;quot;phpmetrics&amp;quot;&amp;gt;
        &amp;lt;exec command=&amp;quot;wget https://github.com/Halleck45/PhpMetrics/raw/master/build/metrics.phar&amp;quot;/&amp;gt;
        &amp;lt;exec command=&amp;quot;php metrics.phar --summary-xml=phpmetrics.xml --summary-html=phpmetrics.html ${src}&amp;quot;/&amp;gt;
    &amp;lt;/target&amp;gt;
&amp;lt;/project&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Si vous pensez que, finalement, ce n&apos;est peut être pas si pertinent d&apos;avoir [troll]une énorme usine à gaz juste pour lancer deux commandes[/troll], vous pouvez simplement configurer le job pour qu&apos;il exécute les commandes en question directement :&lt;/p&gt;

&lt;a href=&quot;https://blog.lepine.pro/images/2014-02-build-shell.png&quot;&gt;&lt;img src=&quot;https://blog.lepine.pro/images/2014-02-build-shell.png&quot; alt=&quot;Jenkins et PhpMetrics : confiuration du build - shell&quot; width=&quot;699&quot; height=&quot;237&quot; class=&quot;aligncenter size-full wp-image-805&quot; /&gt;&lt;/a&gt;

&lt;h2&gt;Deuxième étape: le graphique&lt;/h2&gt;

&lt;p&gt;Nous allons utiliser le plugin de Jenkins &lt;a href=&quot;https://wiki.jenkins-ci.org/display/JENKINS/Plot+Plugin&quot;&gt;Plot&lt;/a&gt;, dont c&apos;est justement la spécialité; Il suffit de configurer une étape Post-Build pour ajouter le résultat de la dernière analyse au graphique :&lt;/p&gt;

&lt;a href=&quot;https://blog.lepine.pro/images/2014-02-jenkins-phpmetrics-plot-build.png&quot;&gt;&lt;img src=&quot;https://blog.lepine.pro/images/2014-02-jenkins-phpmetrics-plot-build.png&quot; alt=&quot;Jenkins et PhpMetrics : configuration de plot&quot; width=&quot;708&quot; height=&quot;655&quot; class=&quot;aligncenter size-full wp-image-806&quot; /&gt;&lt;/a&gt;

&lt;h3&gt;Bonus: le rapport HTML&lt;/h3&gt;

&lt;p&gt;PhpMetrics fournit un &lt;a href=&quot;http://halleck45.github.io/PhpMetrics/report/demo/v0.0.3/index.html&quot;&gt;rapport Html&lt;/a&gt; qui offre, je l&apos;espère, un peu de recul sur un code source. N&apos;hésitez pas à installer le plugin &lt;a href=&quot;https://wiki.jenkins-ci.org/display/JENKINS/HTML+Publisher+Plugin&quot;&gt;Html Publisher&lt;/a&gt;, qui vous permettra d&apos;ajouter un lien vers ce rapport HTML dans le menu de votre projet :&lt;/p&gt;

&lt;a href=&quot;https://blog.lepine.pro/images/2014-02-jenkins-phpmetrics-htmlreport.png&quot;&gt;&lt;img src=&quot;https://blog.lepine.pro/images/2014-02-jenkins-phpmetrics-htmlreport.png&quot; alt=&quot;Jenkins et PhpMetrics : HTML Publisher&quot; width=&quot;871&quot; height=&quot;178&quot; class=&quot;aligncenter size-full wp-image-807&quot; /&gt;&lt;/a&gt;

&lt;h2&gt;Le mot de la fin&lt;/h2&gt;

&lt;p&gt;Bien évidemment vous pouvez intégrer d&apos;autres courbes: Volume du code, Difficulté... La démarche sera la même que celle présentée ci-dessus.&lt;/p&gt;

&lt;p&gt;Je vous rappelle que PhpMetrics est Open Source, et a besoin d&apos;être éprouvé, tant sur les indicateurs fournis que sur la stabilité de l&apos;outils. N&apos;hésitez pas à [lien github issue]remonter les anomalies que vous rencontrerez, ou simplement à me dire si vous l&apos;utilisez : ça me motivera encore plus pour l&apos;enrichir et l&apos;améliorer ;)&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>La maintenabilité d'un projet PHP en images</title>
   <link href="https://blog.lepine.pro/php/la-maintenabilite-dun-projet-php-en-images"/>
   <updated>2013-12-18T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/php/la-maintenabilite-dun-projet-php-en-images</id>
   <content type="html">&lt;p&gt;Ça faisait longtemps que je cherchais un outil capable de me fournir un aperçu, très général et visuel, de la maintenabilité d&apos;un projet. Ne trouvant finalement que peu de chose sur le net, je me suis décidé à en coder un rapidement.
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Pour illustrer le fonctionnement de cet outil, voici les rapport pour les sources de différents projets PHP :&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Symfony2 Component :&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;a href=&quot;http://halleck45.github.io/PhpMetrics/report/symfony2-component/index.html&quot;&gt;&lt;img src=&quot;https://blog.lepine.pro/images/2013-12-18-preview-symfony2-component.png&quot; alt=&quot;Aperçu du rapport pour Symfony2 Component&quot; /&gt;&lt;/a&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Zend Framework 2 :&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;a href=&quot;http://halleck45.github.io/PhpMetrics/report/zendframework2/index.html&quot;&gt;&lt;img src=&quot;https://blog.lepine.pro/images/2013-12-18-preview-zendframework2.png&quot; alt=&quot;Aperçu du rapport pour Zend Framework 2&quot; /&gt;&lt;/a&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Drupal 7 :&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;a href=&quot;http://halleck45.github.io/PhpMetrics/report/drupal7/index.html&quot;&gt;&lt;img src=&quot;https://blog.lepine.pro/images/2013-12-18-preview-drupal7.png&quot; alt=&quot;Aperçu du rapport pour Drupal&quot; /&gt;&lt;/a&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;CakePHP 2 :&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;a href=&quot;http://halleck45.github.io/PhpMetrics/report/cakephp2/index.html&quot;&gt;&lt;img src=&quot;https://blog.lepine.pro/images/2013-12-18-preview-cakephp.png&quot; alt=&quot;Aperçu du rapport pour CakePHP&quot; /&gt;&lt;/a&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;WordPress 3 2 :&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;a href=&quot;http://halleck45.github.io/PhpMetrics/report/wordpress3/index.html&quot;&gt;&lt;img src=&quot;https://blog.lepine.pro/images/2013-12-18-preview-wordpress3.png&quot; alt=&quot;Aperçu du rapport pour WordPress&quot; /&gt;&lt;/a&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Chaque cercle représente un fichier. En schématisant, plus le cercle est gros, plus le code est complexe ; la couleur, elle, est représentative de l&apos;indice de maintenabilité.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Pour les plus pressés, &lt;a href=&quot;http://halleck45.github.io/PhpMetrics/&quot;&gt;l&apos;outil en question est disponible sur Github&lt;/a&gt;.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Pour les autres, voici quelques explications.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Avant de commencer, il faut être conscient que &lt;strong&gt;la notion de maintenabilité applicative est bien plus complexe que ne sait le résoudre un tel outil&lt;/strong&gt;. Elle dépend de facteurs humains (équipe en charge du projet), environnementaux (contexte de l&apos;entreprise, contraintes de délais), du code source (clarté, simplicité), du choix des outils (présence d&apos;un support, possibilité d&apos;obtenir des formations), etc.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Cet outil ne prétend en aucun cas fournir des chiffres précis sur l&apos;ensemble de ces critères. Non, l&apos;objet de cet outil est de fournir des &lt;strong&gt;indicateurs&lt;/strong&gt; à partir d&apos;une analyse statique du code source.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Je n&apos;ai rien inventé : les algorithmes utilisés pour calculer ce type d&apos;indicateurs existent depuis la fin des années 70, et de nombreux IDE (&lt;a href=&quot;http://blogs.msdn.com/b/zainnab/archive/2011/05/26/code-metrics-maintainability-index.aspx&quot;&gt;Visual Studio par exemple&lt;/a&gt;) intègrent nativement des modules de supervision du code basés sur ces mêmes algoritmes pour différents langages de programmation.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;L&apos;outil commence d&apos;abord par convertir le code source en tokens, pour calculer le premier indicateur utilisé : les &lt;a href=&quot;http://fr.wikipedia.org/wiki/M%C3%A9triques_d&apos;Halstead&quot;&gt;métriques d&apos;Ahlstead&lt;/a&gt;. Cet indicateur fourni des informations à partir d&apos;un ratio entre le volume du code, la variété et le type de structures manipulées dans un code source. Il fournit :&lt;/p&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;la &lt;strong&gt;taille&lt;/strong&gt; du programme (N) ;
&lt;li&gt;le &lt;strong&gt;vocabulaire&lt;/strong&gt; (n) : en gros, plus le code emploie de fonctions, variables, opérateurs... différents, plus le vocabulaire est riche ;&lt;/li&gt;
&lt;li&gt;le &lt;strong&gt;volume&lt;/strong&gt; du programme (V) : ratio entre la taille et le vocabulaire ;&lt;/li&gt;
&lt;li&gt;le niveau de &lt;strong&gt;difficulté&lt;/strong&gt; (D) : propension d&apos;erreurs du programme ;&lt;/li&gt;
&lt;li&gt;l&apos;&lt;strong&gt;effort&lt;/strong&gt; d&apos;implémentation fourni (E) ;&lt;/li&gt;
&lt;li&gt;le &lt;strong&gt;temps&lt;/strong&gt; d&apos;implémentation estimé en secondes (T) ;&lt;/li&gt;
&lt;li&gt;le nombre de &lt;strong&gt;bugs&lt;/strong&gt; délivrés (B) : il s&apos;agit d&apos;une estimation du nombre de bugs dans le code source. Ce nombre est idéalement inférieur à 2.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
&lt;p&gt;La seconde étape consiste à calculer la &lt;strong&gt;complexité cyclomatique&lt;/strong&gt; du code source. De ce côté, je n&apos;ai pas cherché à réinventer la route : j&apos;utilise l&apos;excellent &lt;a href=&quot;https://github.com/sebastianbergmann/phploc&quot;&gt;phploc&lt;/a&gt;.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
À partir de ces informations, nous pouvons calculer l&apos;&lt;a href=&quot;http://fr.wikipedia.org/wiki/Maintenabilit%C3%A9&quot;&gt;&lt;strong&gt;Indice de maintenabilité&lt;/strong&gt;&lt;/a&gt;. Cet indicateur fourni une note, de 0 à 171, où 0 représente un code très difficilement maintenable, et 171 un code maintenable.
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Le code source est disponible sur &lt;a href=&quot;http://halleck45.github.io/PhpMetrics/&quot;&gt;Github: Halleck45/PhpMetrics&lt;/a&gt;. Cet outil reste à affiner ; n&apos;hésitez donc surtout pas à proposer des améliorations ou à signaler des anomalies.&lt;/p&gt;
&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Le Développement piloté par le comportement : 2 ebooks gratuits et libres</title>
   <link href="https://blog.lepine.pro/php/ressources-tutos-php/ebooks-open-source-sur-le-developpement-pilote-par-le-comportement"/>
   <updated>2013-10-04T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/php/ressources-tutos-php/ebooks-open-source-sur-le-developpement-pilote-par-le-comportement</id>
   <content type="html">&lt;p&gt;
Ça faisait longtemps que je les avais rédigés (plus de 6 mois), je viens de les retrouver au fond de mon placard. Voici deux ebooks open source et gratuits sur le développement piloté par le comportement (Behavior Driven Development).
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Ces deux livres courts (50 pages) sont, plutôt que des documentations complètes, des invitations : il ne s&apos;agit en aucun cas d&apos;expliquer techniquement ce qu&apos;est le Développement piloté par le comportement, mais bien de comprendre la philosophie qui anime cette démarche. Ils s&apos;adressent bien spécifiquement à deux cibles : les fonctionnels (clients, product owner...) et les techniques (développeurs, testeurs...).&lt;/p&gt;
&lt;/p&gt;

&lt;h2&gt;Tome 1 : Communiquez avec vos développeurs&lt;/h2&gt;

&lt;p&gt;
&lt;a href=&quot;https://blog.lepine.pro/images/2013-10-cover1-small.jpg&quot;&gt;&lt;img src=&quot;https://blog.lepine.pro/images/2013-10-cover1-small.jpg&quot; alt=&quot;Tome 1 : Communiquez avec les développeurs&quot; width=&quot;250&quot; height=&quot;333&quot; class=&quot;size-full wp-image-772&quot; /&gt;&lt;/a&gt; Tome 1 : Communiquez avec les développeurs
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Ce tome, comme son titre l&apos;indique, explique aux chefs de projets fonctionnels (et clients, product owners...) les avantages qu&apos;ils auraient à utiliser la méthodologie du développement piloté par le comportement : économies de communication, gain de vélocité, efficacité et stabilité... Sans jamais aborder les aspects techniques de la démarche.&lt;/p&gt;
&lt;/p&gt;

&lt;h2&gt;Tome 2 : Développeurs, faites-vous plaisir !&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.lepine.pro/images/2013-10-cover2-small.jpg&quot;&gt;&lt;img src=&quot;https://blog.lepine.pro/images/2013-10-cover2-small.jpg&quot; alt=&quot;Tome 2 : Développeurs, faites-vous plaisir&quot; width=&quot;250&quot; height=&quot;333&quot; class=&quot;size-full wp-image-773&quot; /&gt;&lt;/a&gt; Tome 2 : Développeurs, faites-vous plaisir
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Ce tome, lui, s&apos;adresse aux développeurs soucieux de la qualité de leurs projets, ou fatigués de faire des va-et-vients entre les demandes toujours changeantes de leurs clients. Il ne s&apos;adresse pas spécifiquement aux développeurs PHP, même si les exemples utilisés utilisent &lt;a href=&quot;http://behat.org/&quot;&gt;Behat&lt;/a&gt; comme outil de test.&lt;/p&gt;
&lt;/p&gt;
&lt;h2&gt;Open Source ?&lt;/h2&gt;
&lt;p&gt;
&lt;p&gt;Ces deux livres, publiés avec l&apos;application PHP &lt;a href=&quot;https://github.com/javiereguiluz/easybook&quot;&gt;easyBook&lt;/a&gt;, sont open source. Les fichiers MarkDown utilisés sont accessibles sur &lt;a href=&quot;https://github.com/Halleck45/livre-developpement-pilote-comportement&quot;&gt;mon Github&lt;/a&gt;. N&apos;hésitez pas, si le coeur vous en dit, à y contribuer :-)&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Mon idée première était de ne faire qu&apos;un seul livre, à double destination (fonctionnels et techniques), puis de contacter un éditeur pour une publication classique. Plusieurs éléments m&apos;ont convaincus du contraire :&lt;/p&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Au départ le projet a été lancé en collaboration avec une startup nantaise bien connue. Différents changements d&apos;organisation là-bas ont ralenti la finalisation du livre. Il est temps pour moi de le publier, le plus simple reste d&apos;en faire des ebooks accessibles à tous.&lt;/li&gt;
&lt;li&gt;Plusieurs éditeurs m&apos;ont pertinemment fait remarquer qu&apos;un livre doit choisir une, et une seule seule, cible. Sinon les revendeurs ne sauront jamais comment communiquer pour vendre le livre.&lt;/li&gt;
&lt;li&gt;Je me sers déjà depuis plusieurs mois du tome 1 auprès de mes clients pour leur expliquer les avantages du Développement piloté par le comportement et leur expliquer comment formaliser leur besoin efficacement. Ça l&apos;air plutôt efficace, j&apos;ai donc envie que le maximum de personnes puissent faire de même en introduisant cette pratique dans leur société.&lt;/li&gt;
&lt;li&gt;Pourquoi pas ? :) . Je ne travaille qu&apos;avec des produits open source, pourquoi un livre ne le serait-il pas ? Surtout qu&apos;il a été rédigé en MarkDown language, ce qui facilite d&apos;autant plus la contribution...&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
&lt;p&gt;Je vous invite donc à visiter &lt;strong&gt;&lt;a href=&quot;http://communiquez.lepine.pro&quot;&gt;communiquez.lepine.pro&lt;/a&gt;&lt;/strong&gt; pour télécharger ces ebooks (PDF ou ePub). Les sources sont, elles, hébergés sur &lt;a href=&quot;https://github.com/Halleck45/livre-developpement-pilote-comportement&quot;&gt;Github&lt;/a&gt;.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Je suis preneur de tout retour, toute remarque ou suggestion. Les livres ne sont pas figés, ils sont faits pour évoluer. Merci à vous !&lt;/p&gt;
&lt;/p&gt;</content>
 </entry>
 
 <entry>
   <title>Mutation Testing en PHP : pour la qualité des tests unitaires</title>
   <link href="https://blog.lepine.pro/php/mutation-testing-en-php-indicateurs-de-qualite-des-tests-unitaires"/>
   <updated>2013-06-03T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/php/mutation-testing-en-php-indicateurs-de-qualite-des-tests-unitaires</id>
   <content type="html">&lt;p&gt;
&lt;p&gt;Aujourd&apos;hui, dans l&apos;écosystème PHP, on ne se pose enfin plus la question de savoir ce qu&apos;est un test unitaire. Les tests unitaires sont devenus une pratique courante, et il existe des frameworks de tests matures, comme PHPUnit ou atoum.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;L&apos;engouement pour la qualité logicielle a poussé la communauté à progresser et à proposer de nouvelles pratiques, de nouveaux outils... Cependant, il reste encore exceptionnel de contrôler la qualité des tests unitaires produits.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Attention, par contrôle de qualité des tests, je ne parle pas d&apos;indicateurs de couverture de code. Non, car une couverture de code à 100 % est contre-productive, impossible et surtout totalement fausse. Et voici pourquoi.&lt;/p&gt;
&lt;/p&gt;

&lt;h2&gt;Couverture de nœuds et couverture de chemins&lt;/h2&gt;

&lt;p&gt;
&lt;p&gt;Les outils de couverture actuelle ont un défaut majeur, inhérent à leur fonctionnement : ils indiquent uniquement si des portions de code ont été exécutées lors des tests unitaires.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Or, prenons un exemple simple :&lt;/p&gt;
&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php
class Foo {
    public function bar($x, $y) {
        if($x == 1) {
            echo &amp;#39;A&amp;#39;;
        } else {
            echo &amp;#39;B&amp;#39;;
        }
        
        if($y == 1) {
            echo &amp;#39;C&amp;#39;;
        } else {
            echo &amp;#39;D&amp;#39;;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;et le test unitaire suivant :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php
&amp;lt;p&amp;gt;require_once __DIR__.&amp;#39;/Foo.php&amp;#39;;&amp;lt;/p&amp;gt;
class FooTest extends PHPUnit_Framework_TestCase
{
    public function testFoo1()
    {
        $foo = new Foo;
        $foo-&amp;gt;bar(1, 2);
        $foo-&amp;gt;bar(2, 1);
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;
&lt;p&gt;Le premier appel de bar() affiche &apos;AD&apos;, le second affiche &apos;BC&apos;. Je suis donc passé partout dans mon code source, la couverture de code de ma suite de test est de 100 % :&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;a href=&quot;https://blog.lepine.pro/images/2013-06-couverture-code-100.png&quot;  &gt;&lt;img src=&quot;https://blog.lepine.pro/images/2013-06-couverture-code-100.png&quot; alt=&quot;Couverture de code PHP à 100%&quot; width=&quot;471&quot; height=&quot;291&quot; class=&quot;aligncenter size-full wp-image-755&quot; /&gt;&lt;/a&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Super ! 100 % ! J&apos;ai couvert tout mon code ! En plus c&apos;est vert, c&apos;est donc que tout va bien.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Et bien non… ou plutôt, je suis passé à chaque nœud de mon code source, mais je n&apos;ai pas couvert tous les chemins possibles de code. Car avec ces deux if(), le code peut faire :&lt;/p&gt;
&lt;/p&gt;&lt;ul&gt;&lt;li&gt;A, C&lt;/li&gt;
&lt;li&gt;A, D&lt;/li&gt;
&lt;li&gt;B, C&lt;/li&gt;
&lt;li&gt;B, D&lt;/li&gt;&lt;/ul&gt;

        &lt;p&gt;
&lt;p&gt;Quatre chemins possibles donc, alors que la couverture de code est de 100 % en à peine deux tests. La couverture de code est donc un indicateur assez peu fiable, puisqu&apos;elle nous trompe allègrement : dans mon exemple, très simple, je n&apos;ai en réalité couvert que 50 % des chemins possibles ; et encore, uniquement pour cette portion de code. Et le code est très simple : imaginez un switch() avec des if() imbriqués, vous verrez qu&apos;en réalité la différence entre la réalité et la couverture de code indiquée est exponentielle.&lt;/p&gt;
&lt;/p&gt;
&lt;h2&gt;Pire : les test unitaires qui ne testent rien&lt;/h2&gt;

&lt;p&gt;
&lt;p&gt;Attendez, j&apos;ai donc ici une couverture de code de 100 %, mais, en réalité, mes tests unitaires ne font strictement rien. Il n&apos;y a même pas d&apos;assertion !&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Il arrive en effet très fréquemment que les tests unitaires ne servent à rien. Oui, même dans la vraie vie, même sur de gros projets. C&apos;est encore plus vrai lorsqu&apos;on commence à &quot; sur-mocker &quot; tout et n&apos;importe quoi : un mock par-ci, un mock par-là... Il m&apos;arrive de voir des tests unitaires où les assertions portent sur des mocks ; autant ne pas écrire de test unitaire.&lt;/p&gt;
&lt;/p&gt;

&lt;h2&gt;Quel indicateur de qualité alors pour les tests unitaires?&lt;/h2&gt;

        &lt;p&gt;
&lt;p&gt;Vous l&apos;avez compris, il est difficile d&apos;obtenir des indicateurs fiables de qualité pour des tests unitaires. Et encore, on ne cherche que des indicateurs...&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;C&apos;est ici qu&apos;intervient le Mutation Testing. Dès les années 70 (oui, c&apos;est vieux), s&apos;est posée la question de savoir comment résoudre cette question de la qualité de tests.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;L&apos;idée du Mutation Testing consiste à introduire des bugs dans le code source, puis à vérifier si les tests unitaires ont bien détecté ces bugs. Autrement dit, nous introduisons des mutations dans le code source (on parle de &quot; mutants &quot;), les tests unitaires sont sensés les détecter (on dit alors que le mutant en question est &quot; tué &quot;).&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Si tous les bugs ont bien été détectés, c&apos;est sans doute que les tests unitaires sont fiables. Si aucun bug n&apos;est détecté, les tests unitaires ne servent à rien. Dire que tous les mutants ont été tués est donc un indicateur de la bonne qualité de tests unitaires.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Les avantages du Mutation Testing sont nombreux :&lt;/p&gt;
&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;c&apos;est efficace&lt;/li&gt;
&lt;li&gt;c&apos;est très simple et ne requiert pas de développement spécifique en plus des tests unitaires&lt;/li&gt;
&lt;li&gt;c&apos;est assez précis.&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt;
&lt;p&gt;Par contre,  le Mutation Testing a un certain nombre d&apos;inconvénients :&lt;/p&gt;
&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;C&apos;est très très long à s&apos;exécuter (le volume de mutations possibles est exponentielle)&lt;/li&gt;
&lt;li&gt;Il arrive qu&apos;il y ait des mutants impossible à tuer&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt;
&lt;p&gt;Maintenant que les bases sont posées, parlons outils. Il n&apos;existait en PHP, à ma connaissance, qu&apos;un seul outil pour le Mutation Testing : &lt;a href=&quot;https://github.com/padraic/mutagenesis&quot; target=&quot;_blank&quot;&gt;Mutagenesis&lt;/a&gt;. Malheureusement, cet outil, pourtant très intéressant, a, malgré ses avantages, trois inconvénients :&lt;/p&gt;
&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;il est sorti trop tôt, à une époque où tester son code restait encore une pratique anecdotique en PHP&lt;/li&gt;
&lt;li&gt;le dernier commit date de plus d&apos;un an&lt;/li&gt;
&lt;li&gt;il nécessite d&apos;installer une extension PHP assez lourde : RunKit&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt;
&lt;p&gt;Partant de ce constat, j&apos;ai réfléchi à une solution pour proposer un outil plus moderne et moins lourd. J&apos;ai donc démarré le développement de &lt;a href=&quot;https://github.com/Halleck45/MutaTesting/&quot; target=&quot;_blank&quot;&gt;MutaTesting&lt;/a&gt;. A titre personnel, je dois avouer que ça m&apos;intéressait beaucoup de voir comment résoudre les difficultés inhérentes à cet outil sans devoir passer par l&apos;installation de RunKit ou de xDebug.&lt;/p&gt;
&lt;/p&gt;
        
&lt;h2&gt;MutaTesting, un nouvel outil pour la qualité PHP&lt;/h2&gt;

&lt;p&gt;
&lt;a href=&quot;https://github.com/Halleck45/MutaTesting/&quot; target=&quot;_blank&quot;&gt;MutaTesting&lt;/a&gt;, c&apos;est quoi ? C&apos;est un outil PHP qui crée des mutants à partir votre code source puis lance vos tests unitaires pour voir s&apos;il est possible de tuer ces mutants.
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Mon idée première a été de faire un outil très simple : pas besoin d&apos;extension PHP, pas besoin de configuration compliquée ; il suffit, en ligne de commande, d&apos;indiquer trois choses :&lt;/p&gt;
&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;le framework de test utilisé&lt;/li&gt;
&lt;li&gt;le chemin du binaire à exécuter pour lancer les tests&lt;/li&gt;
&lt;li&gt;le dossier des tests unitaires&lt;/li&gt;
&lt;/ul&gt;

        &lt;p&gt;
&lt;p&gt;Par exemple, pour une suite de tests PHPUnit :&lt;/p&gt;
&lt;/p&gt;
        
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;./bin/mutatesting phpunit phpunit.phar myTestFolder&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;
&lt;p&gt;ou pour atoum :&lt;/p&gt;
&lt;/p&gt;
        
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;./bin/mutatesting atoum mageekguy.atoum.phar myTestFolder&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;
&lt;p&gt;C&apos;est tout. A partir de là, MutaTesting va procéder à un certain nombre de processus :&lt;/p&gt;
&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;les tests vont être lancés une première fois&lt;/li&gt;
&lt;li&gt;chaque suite de test va être isolée, puis relancée pour déterminer quelles sources PHP elle permet de tester&lt;/li&gt;
&lt;li&gt;le code source est converti en tokens, puis chaque token transformable est transformé en mutant&lt;/li&gt;
&lt;li&gt;chaque suite de test va être relancée sur chaque mutation de code&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Bien entendu, votre code source n&apos;est jamais modifié. En réalité, l&apos;outil joue avec un StreamWrapper spécifique pour le flux de fichier standard (file://) pour substituer la mutation à votre code originel.&lt;/p&gt;

&lt;p&gt;Voici quelques exemples de bugs qui peuvent être introduits :&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;remplacer un test d&apos;égalité (&quot; == &quot;) par un test de non-égalite (&quot; != &quot;)&lt;/li&gt;
&lt;li&gt;remplacer &quot; true &quot; par &quot; false &quot;&lt;/li&gt;
&lt;li&gt;supprimer un bloc &quot; else &quot;&lt;/li&gt;
...
&lt;/ul&gt;

&lt;p&gt;Voici le résultat de la mutation pour les tests de MutaTesting même :&lt;/p&gt;
&lt;p&gt;
&lt;img src=&quot;https://blog.lepine.pro/images/2013-06-mutation-result-console.png&quot; alt=&quot;Résulat global de la mutation&quot; width=&quot;394&quot; height=&quot;258&quot; class=&quot;aligncenter size-full wp-image-756&quot; /&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;L&apos;analyse du code source a donc permis de créer 46 mutants, donc 26 ont survécu. Le score des tests est donc de 43 %.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Les tests unitaires ont donc des anomalies. Relançons le même outil pour obtenir un compte rendu au format HTML, plus complet (option –format=html). Voici un aperçu de ce qui est obtenu :&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;a href=&quot;https://blog.lepine.pro/images/2013-06-mutation-result-html.png&quot;&gt;&lt;img src=&quot;https://blog.lepine.pro/images/2013-06-mutation-result-html.png&quot; alt=&quot;Rapport détaillé HTML de la mutation&quot; width=&quot;476&quot; height=&quot;359&quot; class=&quot;aligncenter size-full wp-image-757&quot; /&gt;&lt;/a&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Cette fois-ci on a plus de détails : les mutants sont détaillés et regroupés par fichier de source.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Ce qui est intéressant, c&apos;est que les sources qui ressortent comme les moins bien testées sont justement celles que je n&apos;ai pas développées par TDD. L&apos;analyse de ce rapport va donc me permettre de me focaliser sur les tests unitaires qui concernent les sources sur lesquelles le plus de mutations a survécu.&lt;/p&gt;
&lt;/p&gt;

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

&lt;p&gt;
&lt;p&gt;Le Mutation Testing est donc finalement assez simple : on introduit des bugs dans un code source, puis on vérifie qu&apos;ils sont bien détectés par les tests unitaires.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Ce qui est surtout intéressant c&apos;est que cette pratique ne nécessite aucun développement en plus, mais simplement l&apos;utilisation d&apos;un outil, automatisé&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Concernant MutaTesting même, que j&apos;ai développé et qui est disponible sur Github, je pense que c&apos;est un outil que j&apos;espère simple à utiliser et à étendre. Bien que fonctionnel, il peut largement être amélioré. Les Pull Requests sont les bienvenues :-) .&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;De la même façons, vos retours sur les bienvenus : sur vos pratiques, vos attentes en terme d&apos;outils...si l&apos;envie vous prend de l&apos;essayer, voire d&apos;y contribuer.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;PS: j&apos;ai eu pas mal de remarques sur la lisibilité du thème de ce blog. J&apos;ai changé de thème ; c&apos;est mieux ? :p&lt;/p&gt;
&lt;/p&gt;</content>
 </entry>
 
 <entry>
   <title>Mais... on peut faire "ça" en PHP ? Mais c'est horrible !</title>
   <link href="https://blog.lepine.pro/php/mais-on-peut-faire-ca-en-php-mais-cest-horrible"/>
   <updated>2013-03-26T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/php/mais-on-peut-faire-ca-en-php-mais-cest-horrible</id>
   <content type="html"> &lt;p&gt;Pour changer, je ne vais pas parler de ce qui est super avec PHP, mais plutôt de ce qui pue dans PHP. Et oui...&lt;/p&gt;
        &lt;p&gt;
            Attention, qu&apos;on ne me fasse pas dire ce que je n&apos;ai pas dit : j&apos;adore PHP ! C&apos;est la techno que j&apos;utilise tous les jours,  c&apos;est un beau langage, riche et puissant, mais aussi très souple, et il est facile de faire n&apos;importe quoi avec. Les codes suivants sont des exemples de ce qu&apos;on peut faire grâce (ou à cause) de cette souplesse.
        &lt;/p&gt;
        &lt;p&gt;
            Ces techniques sont vraiment crades, mais peuvent parfois, en dernier recours, être utiles. Après, si vous voyez un code qui les utilise, posez-vous la question : est-ce PHP qui est crade, ou bien n&apos;est-ce pas plutôt le développeur derrière qui ne s&apos;est pas posé assez de questions ?
        &lt;/p&gt;
        
        
        &lt;h2&gt;Redéfinir la portée d&apos;un attribut&lt;/h2&gt;
        &lt;p&gt;Commençons doucement. Un attribut privé est un attribut qui n&apos;est accessible que par la classe qui le possède (par définition)... Ah bon ? Et si on essaye de changer sa portée ?&lt;/p&gt;

        &lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;class Foo
{

    private $bar = 5;

    public function bar()
    {
        return $this-&amp;gt;bar;
    }

}

$foo = new Foo;


$attribute = new ReflectionProperty($foo, &amp;#39;bar&amp;#39;);
$attribute-&amp;gt;setAccessible(true);
$attribute-&amp;gt;setValue($foo, &amp;#39;nouvelle valeur&amp;#39;);

var_dump($foo-&amp;gt;bar());
// string(15) &amp;quot;nouvelle valeur&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Nom de zeus ! On vient de modifier la valeur d&apos;un attribut privé !&lt;/p&gt;

        &lt;h2&gt;Lire des attributs privés&lt;/h2&gt;
        &lt;p&gt;On a pu changer la portée d&apos;une variable, mais l&apos;honneur est sauf : il n&apos;est pas possible de lire un attribut privé, tant qu&apos;il reste privé.&lt;/p&gt;
        &lt;p&gt;Bon, il doit bien y avoir une solution, non ? Essayons avec un petit coup de serialize().&lt;/p&gt;
        &lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;class Foo {
    private $bar = 5;
}

$foo = new Foo;
$serialized = serialize($foo);

preg_match(&amp;#39;!bar&amp;quot;;(.):(.*);!&amp;#39;, $serialized, $matches);
list(,$type,$value) = $matches;

var_dump(&amp;quot;bar vaut $value&amp;quot;);
// bar vaut 5&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

        Facile ! Bien sûr, l&apos;exemple est trivial, il existe des &lt;a href=&quot;http://www.sitepoint.com/how-to-expose-phps-private-parts/&quot; target=&quot;_blank&quot;&gt;solutions plus complètes&lt;/a&gt;.


        &lt;h2&gt;Hériter... directement de ses grand-parents&lt;/h2&gt;

        &lt;p&gt;L&apos;héritage c&apos;est pratique ; un objet hérite de son parent, qui lui-même hérite d&apos;un autre objet, le tout dans une harmonie parfaite.&lt;/p&gt;
        &lt;p&gt;Mais attendez, saviez-vous qu&apos;il est possible d&apos;hériter de sa classe grand-mère, sans passer par sa classe Mère ? (par pitié, n&apos;essayez pas de vous  représenter ce que ça ferait dans la vraie vie)&lt;/p&gt;

        &lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;class GrandMother {
    public $bar;
    public function foo() {
        return $this-&amp;gt;bar. &amp;#39; in GrandMother&amp;#39;;
    }
}

class Mother extends GrandMother {
    public function foo() {
        return $this-&amp;gt;bar. &amp;#39; in Mother&amp;#39;;
    }
}

class Child extends Mother {
    public function foo() {
        return GrandMother::foo();
    }
}

$child = new Child;
$child-&amp;gt;bar = &amp;#39;abc&amp;#39;;
var_dump($child-&amp;gt;foo());
// string(18) &amp;quot;abc in GrandMother&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

        &lt;p&gt;Il suffit donc de ne pas utiliser le mot-clef &quot;parent&quot;, mais directement le nom de la classe Grand-mère. Le contexte ($this) est bel et bien préservé. C&apos;est un reliquat de PHP 4...&lt;/p&gt;


        &lt;h2&gt;Ne pas donner de nom à une variable&lt;/h2&gt;

        &lt;p&gt;Toute variable possède un nom : $i, $foo, $bar... Que se passe t-il si vous tenez de déclarer une variable sans lui donner de nom ?&lt;/p&gt;

        &lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;$ = 5;
// PHP Parse error:  syntax error, unexpected &amp;#39;=&amp;#39;, expecting variable&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

        &lt;p&gt;Arf... Mais attendez, ne peut-on pas utiliser l&apos;évaluation dynamique des noms de variables pour créer une variable dont le nom serait une chaîne vide ? Essayons :&lt;/p&gt;

        &lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;$foo = &amp;#39;&amp;#39;;
${$foo} = 5;

print_r(get_defined_vars());
//    ...
//    [foo] =&amp;gt;
//    [] =&amp;gt; 5
//)&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

        &lt;p&gt;
            Et bien si, on peut. Constatez au passage que la variable est bien visible dans le get_defined_vars().
        &lt;/p&gt;
        &lt;p&gt;
            Du coup, on peut s&apos;amuser à faire plein de trucs pas très utiles, comme nommer sa varaible $0 par exemple :
        &lt;/p&gt;
        &lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;echo $0;
// syntax error, unexpected &amp;#39;0&amp;#39; ...

echo ${0};
// ok&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;


        &lt;h2&gt;Transtyper un objet&lt;/h2&gt;
        &lt;p&gt;Un objet est d&apos;une classe donnée. Chaque classe possède un contexte propre (des attributs) et son propre comportement.&lt;/p&gt;
        &lt;p&gt;Du coup il ne devrait pas être possible de réutiliser un objet A pour en faire un objet B. Et pourtant ...&lt;/p&gt;

        &lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;class Foo
{

    public $a = 5;
    public $b = 6;

}

class Bar
{

    public $a = &amp;#39;abc&amp;#39;;
    public $b = &amp;#39;def&amp;#39;;

}

$foo = new Foo;
$bar = new Bar;


$object = convert($foo, &amp;#39;Bar&amp;#39;);
print_r($object);
//Bar Object
//(
//    [a] =&amp;gt; 5
//    [b] =&amp;gt; 6
//)&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

        &lt;p&gt;Comment a t-on fait ? C&apos;est assez simple, il suffit une fois de plus de sérializer notre objet, de modifier une valeur dans la chaîne obtenue, puis  de créer un nouvel objet.&lt;/p&gt;

        &lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;function convert($object, $class)
{
    $serialized = serialize($object);

    $className = get_class($object);
    $len = strlen($className);
    $start = $len + strlen($len) + 6;


    $serializedInfos = &amp;#39;O:&amp;#39; . strlen($class) . &amp;#39;:&amp;quot;&amp;#39; . $class . &amp;#39;&amp;quot;:&amp;#39;;
    $serializedInfos .= substr($serialized, $start);

    return unserialize($serializedInfos);
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

        &lt;p&gt;Vous trouverez de &lt;a href=&quot;http://www.php.net/manual/en/language.types.type-juggling.php#100460&quot; target=&quot;_blank&quot;&gt;nombreux exemples&lt;/a&gt; sur le net.&lt;/p&gt;

        &lt;h2&gt;&quot;Ecouter&quot; les changements d&apos;une variable&lt;/h2&gt;
        &lt;p&gt;
            Vous connaissez sûrement la fonction JavaScript &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/watch&quot; target=&quot;_blank&quot;&gt;watch()&lt;/a&gt;, très pratique, qui permet d&apos;écouter les changements qui peuvent survenir sur une variable.
        &lt;/p&gt;
        &lt;p&gt;
            Vous me voyez venir : il est possible (à grands frais : lenteurs, charge mémoire, etc !) de faire la même chose en PHP. Je vous aurai prévenu : ne faites pas ça en prod !
        &lt;/p&gt;
        &lt;p&gt;
            L&apos;idée consiste à utiliser une &lt;a href=&quot;http://www.php.net/manual/fr/function.register-tick-function.php&quot; target=&quot;_blank&quot;&gt;fonction qui va être exécutée à chaque tick&lt;/a&gt;, pour observer si la variable que l&apos;on souhaite &quot;écouter&quot; à été modifiée ou non. Si c&apos;est le cas, un simple appel à &lt;a target=&quot;_blank&quot; href=&quot;http://www.php.net/manual/fr/function.debug-backtrace.php&quot;&gt;debug_baktrace()&lt;/a&gt; nous permettra de savoir comment cette variable a été modifiée.
        &lt;/p&gt;

        &lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;$var = &amp;#39;abc&amp;#39;;

function tick()
{
    global $var, $expectedVar;
    if (isset($var)) {
        if (isset($expectedVar) &amp;amp;&amp;amp; $var !== $expectedVar) {

            //
            // La variable a été modifiée
            $context = debug_backtrace();
            $where = (isset($context[1][&amp;#39;class&amp;#39;]) ? $context[1][&amp;#39;class&amp;#39;] . &amp;#39;::&amp;#39; : &amp;#39;&amp;#39;)
                    . $context[1][&amp;#39;function&amp;#39;] . &amp;#39;()&amp;#39;;

            printf(&amp;#39;la variable $var a été modifiée par %s (fichier %s, ligne %d), et vaut désormais &amp;quot;%s&amp;quot;&amp;#39;
                    , $where, $context[1][&amp;#39;file&amp;#39;], $context[1][&amp;#39;line&amp;#39;], $var);
        }
        $expectedVar = $var;
    }
}

//
// Enregistrons notre pseudo &amp;quot;écouteur&amp;quot;
register_tick_function(&amp;quot;tick&amp;quot;);
declare(ticks = 1);&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

        &lt;p&gt;Vérifions :&lt;/p&gt;

        &lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;//
// L&amp;#39;heure du test
function foo()
{
    global $var;
    $var = &amp;#39;def&amp;#39;;
}

foo();
//
// Affiche :
//
// la variable $var a été modifiée par foo()
// (fichier /home/data/www/jeff/misc/php-berk/watch-var.php, ligne 39),
// et vaut désormais &amp;quot;def&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;


        &lt;h2&gt;C&apos;est tout ?&lt;/h2&gt;
        &lt;p&gt;J&apos;hésite à aller plus loin : on pourrait parler encore de ce qu&apos;il est possible de faire avec les __PHP_Incomplete_Class, de la confusion possible à utiliser des fonctions comme &lt;a target=&quot;_blank&quot; href=&quot;http://php.net/class_alias&quot;&gt;class_alias()&lt;/a&gt; (&lt;a target=&quot;_blank&quot; href=&quot;http://blog.mageekbox.net/?post/2013/01/17/Modifier-un-espace-de-nom-sans-casser-la-r%C3%A9tro-compatibilit%C3%A9&quot;&gt;quoique parfois utile&lt;/a&gt;), de ce qu&apos;il est affreusement possible de faire avec &lt;a target=&quot;_blank&quot; href=&quot;http://php.net/runkit&quot;&gt;runkit&lt;/a&gt;, dela possibilité de remplacer $_GET par une valeur de notre choix... Mais je crois que ça suffira là :) .&lt;/p&gt;

        &lt;p&gt;
            Bref, vous l&apos;aurez compris, si je suis vraiment convaincu que PHP est un super langage, je vois passer beaucoup de mauvais code, de mauvais développeurs et de mauvaises pratiques, qui me font quotidiennement prendre conscience qu&apos;il est possible de faire vraiment n&apos;importe quoi en développement. Lorsque l&apos;on sort des sentiers battus, on doit avoir une bonne raison et comprendre pourquoi on le fait.&lt;/p&gt;&lt;p&gt;PHP est un beau langage, ce n&apos;est pas parce qu&apos;il est &quot;facile&quot; de débuter en PHP qu&apos;il faut en faire n&apos;importe quoi. Heureusement, on peut aussi faire de très belles choses avec ! Et c&apos;est justement ça le job d&apos;un développeur.
        &lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Behat - créer des tests solides et efficaces</title>
   <link href="https://blog.lepine.pro/php/ressources-tutos-php/behat-creer-des-tests-solides-et-efficaces"/>
   <updated>2013-02-25T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/php/ressources-tutos-php/behat-creer-des-tests-solides-et-efficaces</id>
   <content type="html">&lt;p&gt;
&lt;p&gt;Ca commence à faire déjà quelques temps que j&apos;utilise Behat, et j&apos;ai eu la chance de pouvoir l&apos;utiliser sur différents projets, gros et petits. Je commence donc à avoir un peu plus de recul sur la chose, et à avoir accumulé pas mal de mauvaises pratiques.&lt;/p&gt;
&lt;/p&gt;
&lt;p&gt;Par manque de chance (ou pas ^^), il a fallu que je commence à utiliser Behat sur un projet assez important. C&apos;est donc là que j&apos;ai commencé à faire mes plus grosses erreurs. Je vous propose de voir quelles ont été ces erreurs, et comment éviter de les reproduire.
&lt;/p&gt;

&lt;h2&gt;Utiliser Behat sans faire du Développement piloté par le comportement&lt;/h2&gt;

&lt;p&gt;C&apos;est évident, mais il faut le dire : Behat n&apos;est qu&apos;un outil, ce qui compte vraiment c&apos;est le Développement piloté par le comportement. Et oui ! Behat n&apos;est ni un outil de test, ni un outil de spécification. Ce n&apos;est &quot;rien de plus&quot; qu&apos;un bonus par rapport à une démarche de travail.&lt;/p&gt;

&lt;p&gt;
&lt;p&gt;Si on prend Behat, comme il m&apos;est arrivé de le faire, comme simple outil de test, on fonce dans le mur : au bout de plusieurs mois on risque d&apos;avoir substitué Behat à des outils de tests unitaires (PHPUnit, atoum). Behat permet (entre autres) de tester du besoin fonctionnel. Encore faut-il que ce besoin soit exprimé !&lt;/p&gt;
&lt;/p&gt;
        
&lt;h2&gt;Faire du refractoring de code plutôt que du refractoring de phrases&lt;/h2&gt;

        
&lt;p&gt;Allez, créons une méthode privée réutilisable dans notre Contexte de définition. Ah, et puis passons la publique le jour où il nous faudra l&apos;utiliser dans un autre Contexte...&lt;/p&gt; 

&lt;p&gt;Au final on se retrouve avec une application dans l&apos;application. Quand c&apos;est le cas, et ça m&apos;est arrivé, c&apos;est qu&apos;on confond refractoring de code et refractoring de définitions.&lt;/p&gt;

&lt;p&gt;Les Contextes de définition servent à traduire des expressions en code source. C&apos;est donc sur ces expressions qu&apos;il faut se focaliser. L&apos;idéal est de réussi à parvenir à découper chaque expression en unités atomiques fondamentales.&lt;/p&gt;

&lt;p&gt;Par exemple :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-gherkin&quot; data-lang=&quot;gherkin&quot;&gt;Quand que j&amp;#39;ajoute dans mon panier &amp;quot;Télévision Sony&amp;quot; depuis le catalogue produits&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;On peut découper cette expression en sous-expressions (étapes)&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-gherkin&quot; data-lang=&quot;gherkin&quot;&gt;Etant donné que je suis sur la page du catalogue produit
Quand j&amp;#39;ajoute &amp;quot;Télévision Sony&amp;quot; au panier&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Et au final on peut arriver à des expressions atomiques simples :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-gherkin&quot; data-lang=&quot;gherkin&quot;&gt;Etant donné que je suis sur &amp;quot;/catalogue/produits&amp;quot;
Quand je sélectionne &amp;quot;Télévision Sony&amp;quot;
Et je clique &amp;quot;Ajouter au panier&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Vous voyez que là on fait du refractoring de phrases. L&apos;avantage est que c&apos;est hyper simple avec Behat :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php
use Behat\Behat\Context\Step;

/**
* @Given /^que je consulte le catalogue produit$/
*/
public function queJeConsulteLeCatalogueProduit()
{
	return array(
	    new Step\Given(&amp;#39;que je suis sur la page &amp;quot;/catalogue/produits&amp;quot;&amp;#39;)
	);
}

// etc&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;


&lt;p&gt;Il suffit d&apos;utiliser des sous-appels d&apos;étapes dans la définitions.

&lt;/p&gt;&lt;p&gt;C&apos;est la seule manière que j&apos;ai trouvé pour se retrouver avec des Contextes de définition simples et surtout stables dans le temps. Sinon on passe notre temps à les réécrire dans leur ensemble.

&lt;/p&gt;&lt;p&gt;Par contre ça oblige à avoir plein de définitions, d&apos;où le point suivant :&lt;/p&gt;

&lt;h2&gt;Utiliser Behat sans couche d&apos;isolation&lt;/h2&gt;

&lt;p&gt;Au départ, quand on utilise Behat, on a tendance à se créer 2 ou 3 contextes de définition, et puis c&apos;est tout. C&apos;est à mon avis une erreur. Aujourd&apos;hui, je démarre chaque projet qui utilise Behat avec au moins ce découpage :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Contextes de définition métiers&lt;/li&gt;
&lt;li&gt;Contexte de définition de vue (web)&lt;/li&gt;
&lt;li&gt;Couche d&apos;isolation pour la persistance des données si besoin&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
&lt;p&gt;Pourquoi ? Tout simplement parce que je me suis rendu compte d&apos;une chose : il arrive souvent de devoir repasser sur des définitions ! Et oui. Et c&apos;est long, laborieux et, disons-le, très très démotivant.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Il faut au moins regrouper les définitions qui concernent l&apos;interface graphique dans un contexte spécifique. Par défaut, si vous utilisez Mink, c&apos;est ce qui fait plus ou moins implicitement lorsque vous utilisez des définitions toute-prêtes de Mink.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Mais que se passera t-il quand le client vous annoncera qu&apos;il sort une appli mobile ? Normalement, appli mobile ou web, aucune spécification ne devrait changer. Par contre, si vous avez mal organisés vos Contextes de définition, vous allez galérer.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Au contraire, si vous regroupez tout ce qui concerne l&apos;UI dans un Contexte de vue, mettons par exemple &quot;View\WebContext.php&quot;, il vous suffit simplement de gérer vos sous-contextes :&lt;/p&gt;
&lt;/p&gt;
        
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;public function __construct(array $parameters)
{
    if ($parameters[&amp;#39;view&amp;#39;] == &amp;#39;mobile&amp;#39;) {
        $this-&amp;gt;useContext(&amp;#39;view&amp;#39;, new View\MobileContext($parameters));
    } else {
        $this-&amp;gt;useContext(&amp;#39;view&amp;#39;, new View\WebContext($parameters));
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;C&apos;est aussi simple que ça : il vous suffit de regrouper tout ce qui concerne l&apos;affichage dans un Contexte spécifique.&lt;/p&gt;

&lt;p&gt;Bien entendu, pour les très grosses applications on peut aller plus loin, et d&apos;ailleurs ça devient très vite intéressant : créer un Contexte par page. Par exemple:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;$this-&amp;gt;useContext(&amp;#39;view.catalogue&amp;#39;, new View\Web\CatalogueContext($parameters));
$this-&amp;gt;useContext(&amp;#39;view.panier&amp;#39;, new View\Web\PanierContext($parameters));&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Ca peut paraître extrémiste, mais dans le cas où votre application est conséquente, c&apos;est très utile. Il faut bien se dire que tout ce travail consiste à solidifier votre architecture de test. De la même manière qu&apos;il faut découper une application en blocs de code (modules), il faut découper les Contextes de définition selon leur... contexte.&lt;/p&gt;

&lt;p&gt;Après, il faut prendre la juste mesure des choses, et trouver un découpage adapté :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;aux besoins&lt;/li&gt;
&lt;li&gt;à la taille du projet&lt;/li&gt;
&lt;li&gt;au temps disponible&lt;/li&gt;
&lt;li&gt;à la compétence de l&apos;équipe&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Exactement comme on le ferait pour un applicatif.&lt;/p&gt;


&lt;p&gt;Voilà pour ces quelques retours que je souhaitais partager avec vous. Après, ils sont basés sur mon expérience personnelle, et je serai très curieux de savoir ce que vous en pensez, et surtout comment vous vous avez fait pour gérer la croissance de vos projets utilisant Behat / Cucumber / etc.. N&apos;hésitez pas à partager votre expérience...&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>dependency.me, pour savoir si vos dépendances composer sont à jour</title>
   <link href="https://blog.lepine.pro/php/actus-php/dependency-me-un-service-pour-savoir-si-vos-dependances-composer-sont-a-jour"/>
   <updated>2013-01-30T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/php/actus-php/dependency-me-un-service-pour-savoir-si-vos-dependances-composer-sont-a-jour</id>
   <content type="html">&lt;p&gt;Ça faisait pas mal de temps que l&apos;idée me trottait en tête : un service pour savoir si l&apos;on utilise des dépendances composer à jour ou non.&lt;/p&gt;

&lt;p&gt;Tel que je le voyais, je trouvais optimal de pouvoir intégrer une image à notre readme github, exactement comme pour &lt;a href=&quot;https://travis-ci.org/&quot;&gt;Travis-Ci&lt;/a&gt;. Bien entendu, j&apos;ai cherché sur le net ce que je pouvais trouver, et j&apos;ai été assez surpris de trouver pas mal d&apos;outils pour Ruby, Python, NodeJs... mais rien pour les dépendances de PHP avec composer !&lt;/p&gt;

&lt;p&gt;Prenant mon courage à deux mains, je me suis donc décidé à coder (très rapidement) &lt;a href=&quot;http://dependency.me&quot;&gt;http://dependency.me&lt;/a&gt;. Il vous suffit de vous connecter avec votre compte Github et de sélectionner les repositories pour lesquels vous souhaitez activer le service, vous aurez en temps (quasi) réel une idée de l&apos;état de vos dépendances, branche par branche :&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://blog.lepine.pro/images/2013-01-capture-composer-dependency-owner.jpg&quot;&gt;&lt;img src=&quot;https://blog.lepine.pro/images/2013-01-capture-composer-dependency-owner.jpg&quot; alt=&quot;Vue de l&amp;#039;état des dépendances Composer par propriétaire&quot; width=&quot;400&quot; height=&quot;184&quot; class=&quot;aligncenter size-full wp-image-690&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Bien entendu, il est tout à fait possible d&apos;avoir le détail pour chaque branche :&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://blog.lepine.pro/images/2013-01-capture-composer-dependency-branche.jpg&quot;&gt;&lt;img src=&quot;https://blog.lepine.pro/images/2013-01-capture-composer-dependency-branche.jpg&quot; alt=&quot;Vue de l&amp;#039;état des dépendances Composer par branche&quot; width=&quot;400&quot; height=&quot;115&quot; class=&quot;aligncenter size-full wp-image-691&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Le site vous donne pour chaque dépôt le Markdown à copier-coller dans votre readme.md (par exemple) pour afficher une image mise automatiquement à jour selon l&apos;état de vos dépendances :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.lepine.pro/images/2013-01-build-status-latest.png&quot;&gt;&lt;img src=&quot;https://blog.lepine.pro/images/2013-01-build-status-latest.png&quot; alt=&quot;build-status-latest&quot; width=&quot;170&quot; height=&quot;13&quot; class=&quot;aligncenter size-full wp-image-693&quot; /&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.lepine.pro/images/2013-01-build-status-outofdate.png&quot;&gt;&lt;img src=&quot;https://blog.lepine.pro/images/2013-01-build-status-outofdate.png&quot; alt=&quot;build-status-outofdate&quot; width=&quot;170&quot; height=&quot;13&quot; class=&quot;aligncenter size-medium wp-image-694&quot; /&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.lepine.pro/images/2013-01-build-status-recent.png&quot;&gt;&lt;img src=&quot;https://blog.lepine.pro/images/2013-01-build-status-recent.png&quot; alt=&quot;build-status-recent&quot; width=&quot;170&quot; height=&quot;13&quot; class=&quot;aligncenter size-medium wp-image-695&quot; /&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.lepine.pro/images/2013-01-build-status-unknown.png&quot;&gt;&lt;img src=&quot;https://blog.lepine.pro/images/2013-01-build-status-unknown.png&quot; alt=&quot;build-status-unknown&quot; width=&quot;170&quot; height=&quot;13&quot; class=&quot;aligncenter size-medium wp-image-696&quot; /&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bien entendu, le site est Open Source, et est &lt;a href=&quot;https://github.com/DependencyMe/dependency.me&quot;&gt;hébergé sur Github&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Toutes les bonnes âmes souhaitant m&apos;aider sont les bienvenues : qu&apos;il s&apos;agisse de signaler les bugs, me faire retours, et pourquoi pas directement à faire évoluer le code concerné. Je pense qu&apos;on peut faire mieux : actuellement le service ne gère pas les dépôts privés par exemple... Et un coup de pouce pour l&apos;anglais serait le bienvenue !&lt;/p&gt;

&lt;p&gt;Je suis également preneur de toute remarque, positive ou négative, sur ce service.&lt;/p&gt;

&lt;p&gt;Merci à vous !&lt;/&gt;
</content>
 </entry>
 
 <entry>
   <title>Doctrine n'est pas un ORM ?</title>
   <link href="https://blog.lepine.pro/php/doctrine-nest-pas-un-orm"/>
   <updated>2013-01-04T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/php/doctrine-nest-pas-un-orm</id>
   <content type="html">&lt;p&gt;Pour bien commencer l&apos;année, je vous propose de troller un peu sur les ORM. Ce qui suit est mon point de vue et n&apos;engage que moi :)&lt;/p&gt;

&lt;p&gt;Selon moi, Doctrine 2 représente un tournant majeur dans le monde de PHP, sans doute plus encore que Symfony2 ou Zend Framework 2. Mais j&apos;ai le sentiment que Doctrine n&apos;est pas utilisé à sa juste valeur...&lt;/p&gt;
&lt;h2&gt;Les ORM classiques&lt;/h2&gt;
&lt;p&gt;Prenons un peu de recul ; qu&apos;est-ce qu&apos;un ORM ? Traditionnellement, un ORM est une technique pour représenter des enregistrements (et leurs relations) sous forme d&apos;objets. Généralement, on implémente pour cela principalement ces patterns :&lt;/p&gt;

&lt;dl&gt;&lt;dd&gt;&lt;strong&gt;Table Data Gateway&lt;/strong&gt;:&lt;/dd&gt;&lt;dt&gt;Un objet est une passerelle vers une table&lt;/dt&gt;&lt;dd&gt;&lt;strong&gt;Active Record&lt;/strong&gt;:&lt;/dd&gt;&lt;dt&gt;Un objet représente un enregistrement et dispose du comportement nécessaire pour implémenter la logique métier de cet enregistrement&lt;/dt&gt;&lt;dd&gt;&lt;strong&gt;Data Access Object&lt;/strong&gt;:&lt;/dd&gt;&lt;dt&gt;Un objet est utilisé pour encapsuler tous les accès vers une source de donnée&lt;/dt&gt;&lt;/dl&gt;

&lt;br /&gt;
&lt;p&gt;C&apos;est ce qui se passe depuis des années, par exemple avec Zend Framework 1 :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php
// Table Data Gateway
class TableUsers extends Zend_Db_Table_Abstract {
    // nom de la table et de la base
    protected $_name = &amp;#39;tbl_users&amp;#39;;
    protected $_schema = &amp;#39;database_name&amp;#39;;
}

$table = new TableUsers;
// Rowset : jeu de résultat Traversable
$users = $table-&amp;gt;find(1);
?&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;



&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php
class User extends Zend_Db_Table_Row_Abstract {
    
    public function resetPassword() {
        $this-&amp;gt;pwd = md5(uniqid());
    }
    
}

$user = $table-&amp;gt;fetchRow(array(&amp;#39;id&amp;#39; =&amp;gt; 1));
$user-&amp;gt;name = &amp;#39;Jean-François&amp;#39;;
$user-&amp;gt;resetPassword();
$user-&amp;gt;save();
?&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h2&gt;Et les principes SOLID ?&lt;/h2&gt;
&lt;p&gt;Bon, vous allez me dire que des milliers de projets ont été faits de cette manière, et que ça fonctionne.&lt;/p&gt;

&lt;p&gt;Oui, ça fonctionne, mais ça tombe en marche : les coûts de maintenance sont nécessairement élevés, vu que &lt;strong&gt;le code ne respecte en rien les principes SOLID&lt;/strong&gt;. Et oui, &lt;strong&gt;notre objet User a plusieurs responsabilités&lt;/strong&gt; ! Il a la responsabilité de représenter une information, de gérer ses règles métiers, de s&apos;enregistrer... Bref, c&apos;est une God Class (un objet dieu), ce n&apos;est pas de l&apos;orienté objet... Et on fonce dans le mur.&lt;/p&gt;

&lt;p&gt;En réalité, &lt;strong&gt;un ORM ne peut pas partir du postulat qu&apos;on va représenter des enregistrements par des objets. Non, il doit faire l&apos;inverse !&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Je m&apos;explique : le principe de la programmation orientée objet est de manipuler des objets. Il existe différents types d&apos;objet : des agrégats, des objets valeurs... Pour conserver un code propre et maintenable, il ne faut se focaliser que sur le comportement des objets que l&apos;on souhaite utiliser). La manière dont ils sont stockés n&apos;a aucune importance. Vraiment aucune ! Si si; j&apos;insiste : aucune ! Après tout, qu&apos;ils soient stockés sous forme de fichiers, d&apos;une base relationnelle, d&apos;un document... tout cela n&apos;impacte pas le comportement de votre application. Non, ça n&apos;aura un impact que sur la couche basse d&apos;infrastructure de votre application, exclusivement.&lt;/p&gt;

&lt;h2&gt;L&apos;apport du Domain Driven Design&lt;/h2&gt;
&lt;p&gt;Le Domain Driven Design est une approche qui tente de focaliser la conduite d&apos;un projet informatique sur le besoin métier exclusivement. Très grossièrement, l&apos;idée est de maximiser la communication entre les équipes techniques et les équipes fonctionnelles pour faciliter l&apos;expression du besoin, puis de calquer le code source sur cette expression de besoin. De cette manière, le code source est en permanence le reflet exact du besoin tel qu&apos;il a été exprimé par les fonctionnels.&lt;/p&gt;


&lt;h3&gt;Identité et entité&lt;/h3&gt;
&lt;a href=&quot;/php/doctrine-nest-pas-un-orm/images/identity_crisis_cat_by_sebreg-d5fcofy-resized.jpg&quot; rel=&quot;attachment wp-att-662&quot;&gt;&lt;img class=&quot;aligncenter size-full wp-image-662&quot; alt=&quot;Crise d&apos;identité - Domain Driven Design&quot; src=&quot;https://blog.lepine.pro/images/2013-01-identity_crisis_cat_by_sebreg-d5fcofy.resized.jpg&quot; width=&quot;200&quot; height=&quot;193&quot; /&gt;&lt;/a&gt;


&lt;p&gt;Que nous dit le DDD (Domain Driven Design) ? &lt;strong&gt;Que chaque projet manipule des concepts, des noms communs, qui semblent plus importants que les autres&lt;/strong&gt;. Ce pourra être par exemple les mots &quot;acheteur&quot;, &quot;panier&quot; et &quot;produit&quot; dans le cas d&apos;une boutique en ligne.&lt;/p&gt;

&lt;p&gt;Quelle est la spécificité de ces mots ? C&apos;est qu&apos;ils représente des choses différentiables les unes des autres de manière certaine : chaque produit est unique, chaque acheteur se distingue des autres acheteurs ; bref, chacune des ces choses est unique. On dira dans ce cas que chaque acheteur, chaque produit, &lt;strong&gt;ont une identité propre&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Cette notion d&apos;identité est fondamentale : une identité est unique, et est marquée par des informations d&apos;état de l&apos;objet à laquelle elle s&apos;applique. Par exemple, mon identité pourrait être marquée par mon numéro de sécurité sociale.&lt;/p&gt;

&lt;p&gt;En informatique, on a longtemps oublié cette notion d&apos;identité, en utilisant un &quot;identifiant unique&quot; (id) à la place d&apos;une vraie information. Souvent, par exemple, on stocke des informations sur des comptes bancaires dans une table qui a une colonne &quot;id&quot; et une autre &quot;account_number&quot;. &lt;/p&gt;

&lt;p&gt;C&apos;est une erreur conceptuelle : la colonne &quot;id&quot; n&apos;a aucune valeur fonctionnelle. Ce qui fait l&apos;identité du compte, c&apos;est son numéro de compte. C&apos;est donc la colonne &quot;account_number&quot; qui doit être utilisée pour distinguer un compte d&apos;un autre ! Je conçois qu&apos;il est pratique d&apos;ajouter une colonne &quot;id&quot; pour des raisons d&apos;index et de performance dans une table, mais cet &quot;id&quot; ne doit pas être utilisé dans le code source, puisqu&apos;il ne représente rien en terme de métier (fonctionnellement donc).&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php
class Account  {
    private $accountNumber = &amp;#39;xxxxxxxxxx&amp;#39;;
}
?&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Ces choses, qui ont une identité, qui sont absolument uniques, sont désignées par le terme d&apos;entités, ou &quot;entity&quot; en anglais dans le monde du Domain Driven Design.&lt;/p&gt;

&lt;p&gt;Ah, comme dans Doctrine ? Oui, comme dans Doctrine... Les entités sont des objets qui ont une identité. Simplement, et uniquement.&lt;/p&gt;

&lt;p&gt;Dans le DDD, il existe des collections d&apos;entités, que l&apos;on appelle des Agrégats (Aggregate). Oui, toujours comme dans Doctrine :)&lt;/p&gt;


&lt;h3&gt;Les services&lt;/h3&gt;

&lt;p&gt;On a vu qu&apos;il existait des concepts importants dans tout projet informatique, concepts représentés par des noms communs.&lt;/p&gt;

&lt;strong&gt;Il existe d&apos;autres concepts très importants, qui sont eux représentés par des verbes&lt;/strong&gt; : &quot;acheter&quot;, &quot;vendre&quot;, &quot;ajouter dans le panier&quot;...

&lt;p&gt;Comment représenter techniquement ces concepts ? En créant, nous dit le DDD, des objets &quot;Services&quot; :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php
class ServicePanier {
    
    public function ajouterProduit(ProduitInterface $produit, PanierInterface $panier) {
        // ...
    }
}
?&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Ces objets services sont justement les objets que votre application doit manipuler. Ils constituent le comportement global de votre application. C&apos;est donc eux qui doivent être utilisés dans les Contrôleurs, et eux seuls.&lt;/p&gt;

&lt;h3&gt;Les dépôts / Repositories&lt;/h3&gt;

&lt;p&gt;Un projet informatique nécessite souvent de retrouver des informations. Ou plutôt, de retrouver ce qui est désigné fonctionnellement par des noms communs (&quot;acheteur&quot;, &quot;panier&quot;, &quot;produit&quot;...) et qui possède une identité.&lt;/p&gt;

&lt;p&gt;C&apos;est le rôle des dépôts (Repository). Un Repository ne sert qu&apos;à retrouver des informations. Et uniquement selon des critères fonctionnels ; on ne doit pas appeler un repository en passant des tableaux de paramètres... Non, il faut continuer de penser SOLID :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php
class RepositoryProduit {
    
    public function listerProduitsDuPanier(PanierInterface $panier) {
        // ...
    }
    
    public function recupererProduit($identite) {
        // ...
    }
}
?&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Attendez, on a aussi le concept de Repository dans le DDD, comme dans Doctrine ?! Et oui ! Vous voyez pourquoi je fais tout ce détour par le DDD pour parler de Doctrine :-)&lt;/p&gt;


&lt;h3&gt;Et bien plus ...&lt;/h3&gt;

&lt;p&gt;Il y a encore plein de choses dans le DDD, et j&apos;ai schématisé très grossièrement, mais vous voyez où je veux en venir : &lt;strong&gt;Doctrine n&apos;est pas un ORM classique&lt;/strong&gt; (et ne doit pas être utilisé  comme tel), Doctrine est un super outil pour pratiquer l&apos;approche du Domain Driven Design.&lt;/p&gt;

&lt;strong&gt;Surtout, la démarche de Doctrine n&apos;est pas de mapper une base de données sur des objets. Non, c&apos;est exactement l&apos;inverse : on part des objets (et donc du besoin fonctionnel) pour ensuite les faire persister.&lt;/strong&gt;

&lt;p&gt;De nombreux concepts du DDD sont présents dans Doctrine. Du coup, pourquoi ne pas penser Domain Driven Design dans vos projets Doctrine ? Qu&apos;il s&apos;agisse de projets Symfony 2, Zend Framework 2 (avec le module Doctrine), ou autre... ?&lt;/p&gt;

&lt;p&gt;Et dans ce cas, si les entités sont des concepts fonctionnels importants (un panier, un produit, un acheteur...) qui ont une identité... &lt;strong&gt;par pitié ne créez pas d&apos;entités pour vos relations&lt;/strong&gt; dans Doctrine. Une table intermédiaire n&apos;est pas une entité. D&apos;ailleurs ça n&apos;existe même pas : il n&apos;existe que des relations entre les objets, tout le reste n&apos;a aucun sens en terme de Programmation Orientée Objet.&lt;/p&gt;

&lt;p&gt;Et si vous voulez me répondre que créer des entités-relations dans Doctrine reste pratique vu que ça permet de gérer et contrôler plus facilement certaines relations, je vous répondrai que ce n&apos;est pas à PHP de vérifier la cohérence d&apos;une donnée, mais à un SGBD relationnel, et que le SGBD sera bien meilleur vu qu&apos;il est conçu pour ça.&lt;/p&gt;

&lt;p&gt;Je crois que ça devrait être la résolution pour ce début d&apos;année 2013 de tous les développeurs PHP : &lt;strong&gt;arrêtez de voir les ORM comme des solutions pour manipuler des bases de données.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Voyez plutôt des objets, qui ont des responsabilités, des comportements, parfois même une identité ; et considérez qu&apos;il s&apos;agit plutôt d&apos;un accident quand on doit les persister.&lt;/p&gt;

&lt;p&gt;Après tout, si on faisait pas du web, le besoin fonctionnel resterait le même, mais on n&apos;aurait pas besoin de persister nos &quot;acheteurs&quot;, &quot;produits&quot; et &quot;paniers&quot;. Il suffirait de &quot;livrer&quot;, &quot;commander&quot;... Il n&apos;y a que ça qui compte. Pas vrai ? :-)&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Automatisation des tâches avec Phing</title>
   <link href="https://blog.lepine.pro/php/automatisation-des-taches-avec-phing"/>
   <updated>2012-11-16T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/php/automatisation-des-taches-avec-phing</id>
   <content type="html">&lt;p&gt;Je ne vais pas vous convaincre qu&apos;un bon développeur est un développeur faignant, c&apos;est à dire qui sait employer suffisamment d&apos;énergie à un moment donner pour en gagner plein par la suite... Non, par contre, pour ceux qui ne l&apos;utilisent pas encore, je vais vous montrer qu&apos;on n&apos;a pas besoin de se compliquer la vie lorsqu&apos;on veut automatiser. On peut même s&apos;organiser !&lt;/p&gt;

&lt;p&gt;Phing, c&apos;est quoi ? Les mauvaises langues diront que c&apos;est une version instable de Ant. C&apos;est pas totalement faux, mais c&apos;est loin d&apos;être vrai. Phing, c&apos;est un outil d&apos;automatisation de tâches, mais spécialisé et conçu &lt;u&gt;&lt;strong&gt;pour&lt;/strong&gt;&lt;/u&gt; PHP, ce qui change vraiment tout, comme vous allez le voir. &lt;/p&gt;

&lt;p&gt;Ce qu&apos;il faut noter également avant de commencer c&apos;est que &lt;a href=&quot;http://www.phing.info/docs/guide/stable/&quot;&gt;la documentation est très riche&lt;/a&gt;, même si elle n&apos;est pas toujours 100% pratique.&lt;/p&gt;

&lt;h2&gt;Premiers pas avec Phing&lt;/h2&gt;
&lt;p&gt;L&apos;installation de phing est simple :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&amp;lt;p&amp;gt;pear channel-discover pear.phing.info&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;pear install phing/phingdocs&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Il suffit ensuite de créer un fichier build.xml, qui va décrire un certain nombre de tâches que l&apos;on souhaite exécuter :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; ?&amp;gt;
&amp;lt;project name=&amp;quot;demo&amp;quot; basedir=&amp;quot;.&amp;quot; default=&amp;quot;example&amp;quot;&amp;gt;
    &amp;lt;target name=&amp;quot;example&amp;quot;&amp;gt;
        &amp;lt;echo message=&amp;quot;yep, I will create a directory&amp;quot; /&amp;gt;
        &amp;lt;mkdir dir=&amp;quot;/my/path/&amp;quot; /&amp;gt;
    &amp;lt;/target&amp;gt;
&amp;lt;/project&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Rien de bien compliqué : un projet est composé de n tâches (noeud target), identifiées par un nom (attribut name). Si j&apos;ai besoin de créer le dossier &quot;/my/path&quot;, il ne me reste plus qu&apos;à exécuter :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;phing&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Pas besoin de paramètre : vous voyez l&apos;attribut &quot;default&quot; du noeud &quot;project&quot; : c&apos;est la tâche à lancer par défaut si rien n&apos;est précisé, ce qui est le cas ici.&lt;/p&gt;

&lt;h2&gt;Un peu plus loin&lt;/h2&gt;
&lt;p&gt;Bon, ok, c&apos;est rigolo... Mais on peut faire plein de choses : il n&apos;y a aucune limite. Vous avez vu le noeud &quot;mkdir&quot;. Il en existe des tas : &quot;echo&quot;, mais aussi &quot;phpunit&quot;, &quot;ftpdeploy&quot;, &quot;gitpull&quot;, &quot;phpdepend&quot;... Vous commencez à voir pourquoi utiliser Phing est intéressant : il gravite autour des utils PHP. Il existe enfin le noeud &quot;exec&quot;, qui permet de lancer une commande classique. &lt;/p&gt;

&lt;p&gt;Un truc bien pratique aussi c&apos;est l&apos;utilisation de variable. Un variable c&apos;est :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;un noeud &quot;property&quot; dans votre fichier xml&lt;/li&gt;
&lt;li&gt;une déclaration dans un fichier de propriété&lt;/li&gt;
&lt;li&gt;un paramètre que vous fournissez lorsque vous lancez phing, avec l&apos;option -D&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Chaque variable est réutilisable, il suffit de l&apos;écrire sous la forme ${maVariable}. Soit :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&amp;lt;property name=&amp;quot;out&amp;quot; value=&amp;quot;/my/path/&amp;quot; /&amp;gt;

&amp;lt;target name=&amp;quot;prepare&amp;quot;&amp;gt;
    &amp;lt;mkdir dir=&amp;quot;${out}&amp;quot; /&amp;gt;
&amp;lt;/target&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Je vais pouvoir écraser la valeur de ${out} en lançant phing :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;phing -Dout=/another/path&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Je pourrai également importer un fichier de variables :&lt;/p&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&amp;lt;!-- file phing.xml --&amp;gt;
&amp;lt;property file=&amp;quot;./my.properties&amp;quot;/&amp;gt;
&amp;lt;!-- file my.properties --&amp;gt;
out=/another/path
var2=xxxx
var3=xxxx&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Vous trouverez un &lt;a href=&quot;https://gist.github.com/3794330&quot;&gt;exemple d&apos;utilisation basique de phing&lt;/a&gt; pour générer un rapport minimal de vos sources : CodeSniffer pour vos conventions, PHPUnit pour vos tests, phpMessDetector pour le &quot;smell code&quot;, rats pour la sécurité, CopyPasteDetector pour les  copier-collers, CodeBrowser pour intégrer tout ça dans un navigateur... &lt;/p&gt;

&lt;p&gt;Vous voyez qu&apos;en quelques lignes de xml on se retrouve à ne plus jamais avoir besoin de lancer ces outils à la main. En un mot : on automatise !&lt;/p&gt;

&lt;p&gt;Notez qu&apos;il est possible de gérer la dépendance entre différentes tâches :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&amp;lt;target name=&amp;quot;t1&amp;quot; depends=&amp;quot;t2,t3&amp;quot;&amp;gt;
    &amp;lt;!-- (...) --&amp;gt;
&amp;lt;/target&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;Toute exécution de la tâche t1 lancera les tâches t2 et t3.&lt;/p&gt;

&lt;h2&gt;Gérer le plan de tâches&lt;/h2&gt;
&lt;p&gt;Ce qui est chouette avec Phing, c&apos;est qu&apos;on peut faire plein de choses. Et même gérer le déroulement de nos tâches... Comment ? Et bien par exemple avec des si et des alors. C&apos;est possible !&lt;/p&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&amp;lt;if&amp;gt;
   &amp;lt;equals arg1=&amp;quot;123456&amp;quot; arg2=&amp;quot;${valueToCompare}&amp;quot; /&amp;gt;
   &amp;lt;then&amp;gt;
       &amp;lt;echo message=&amp;quot;${valueToCompare}&amp;quot; est égal à 123456&amp;quot; /&amp;gt;
   &amp;lt;/then&amp;gt;
   &amp;lt;else&amp;gt;
       &amp;lt;echo message=&amp;quot;${valueToCompare}&amp;quot; est différent de 123456&amp;quot; /&amp;gt;
   &amp;lt;/else&amp;gt;
&amp;lt;/if&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt; Pas mal non ? Bien sûr, on peut faire tout ce qui est classique : égal, différent, plus grand que, elseif...

&lt;p&gt;Un autre moyen de gérer notre déroulement est de demander à phing d&apos;interrompre le processus à moins que... à moins qu&apos;une variable donnée soit vraie. Par exemple :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&amp;lt;fail unless=&amp;quot;myVariable&amp;quot; message=&amp;quot;On stoppe tout!&amp;quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2&gt;Structurer, découper et organiser les tâches Phing&lt;/h2&gt;
&lt;p&gt;Le risque de phing est de se retrouver à devoir copier-coller plein de tâches pour gérer les différents cas . Il existe un moyen simple d&apos;éviter cela : chaque tâche est réutilisable par une autre (un peu à l&apos;image d&apos;une fonction), grâce au noeud &quot;phingCall&quot;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&amp;lt;target name=&amp;quot;myTask1&amp;quot;&amp;gt;
    &amp;lt;!-- (...) --&amp;gt;
&amp;lt;/target&amp;gt;

&amp;lt;target name=&amp;quot;myTask2&amp;quot;&amp;gt;
    &amp;lt;!-- we call myTask1 --&amp;gt;
    &amp;lt;phingCall target=&amp;quot;myTask1&amp;quot; /&amp;gt;
&amp;lt;/target&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Vous me direz : &quot;Et les variables dans tout ça ? On peut les passer en paramètre ?&quot;. Bien sûr :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&amp;lt;target name=&amp;quot;myTask1&amp;quot;&amp;gt;
    &amp;lt;echo message=&amp;quot;ma variable vaut ${myVar}.&amp;quot; /&amp;gt;
&amp;lt;/target&amp;gt;

&amp;lt;target name=&amp;quot;myTask2&amp;quot;&amp;gt;
    &amp;lt;!-- we call myTask1 --&amp;gt;
    &amp;lt;phingCall target=&amp;quot;myTask1&amp;quot;&amp;gt;
        &amp;lt;property name=&amp;quot;myVar&amp;quot; value=&amp;quot;abcd&amp;quot; /&amp;gt;
    &amp;lt;/phingCall&amp;gt;
&amp;lt;/target&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Pas mal non ?&lt;/p&gt;

&lt;p&gt;Pour organiser un peu mieux vos tâches, il est également possible de découper votre fichier xml en plusieurs sous fichiers, et de les importer (à la manière d&apos;un require en PHP) :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&amp;lt;import file=&amp;quot;./another-file1.xml&amp;quot;/&amp;gt;
&amp;lt;import file=&amp;quot;./another-file2.xml&amp;quot;/&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Les tâches ne sont pas exécutées, mais sont disponibles à l&apos;utilisation lorsque l&apos;on fait un phingCall. Comme un &quot;require&quot; donc...&lt;/p&gt;

&lt;h2&gt;Evaluer du PHP&lt;/h2&gt;
&lt;p&gt;Ce qui me plaît le plus avec Phing, c&apos;est la possibilité d&apos;évaluer du code PHP directement, sans se compliquer. Oui c&apos;est possible, je vous ai dit que Phing était justement fait pour PHP.&lt;/p&gt;

&lt;p&gt;En plus c&apos;est simple : l&apos;évaluation de l&apos;expression est directement mise dans la variable déclarée dans l&apos;attribut &quot;returnProperty&quot; :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&amp;lt;php expression=&amp;quot;in_array(&amp;#39;curl&amp;#39;, get_loaded_extensions())&amp;quot; returnProperty=&amp;quot;curlEnabled&amp;quot;/&amp;gt;
&amp;lt;fail unless=&amp;quot;curlEnabled&amp;quot; message=&amp;quot;You need cUrl to use it&amp;quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2&gt;Pour finir&lt;/h2&gt;
&lt;p&gt;Vous le voyez, Phing mérite d&apos;être utilisé. Si vous voulez voir un projet plus complet (et pourquoi pas y contribuer, si si, j&apos;insiste), je vous invite à regarder &lt;a href=&quot;http://goo.gl/CerSr&quot;&gt;PHPStarter&lt;/a&gt;, qui est un simple automate d&apos;installation de sites (téléchargement des sources du framework utilisé, création des virtual hosts...), et qui synthétise tout ce que je viens de présenter.&lt;/p&gt;

&lt;p&gt;Enfin, une petite astuce que m&apos;a donnée mon collègue &lt;a href=&quot;https://fr.twitter.com/gaspaio&quot;&gt;@gaspaio&lt;/a&gt;, et à laquelle je n&apos;avais pas pensé : il suffit de définir comme tâche par défaut une tâche qui affiche de l&apos;aide, et vous voici avec un outil encore plus structuré et simple d&apos;utilisation. C&apos;est ce qui se passe &lt;a href=&quot;https://github.com/Halleck45/PhpStarter/blob/master/build.xml#L15-L50&quot;&gt;par exemple ici&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Phing n&apos;est pas parfait bien entendu, surtout que c&apos;est finalement assez peu utilisé par rapport à son utilité, même si pas mal de monde l&apos;utilise. D&apos;ailleurs je suis curieux : qui l&apos;utilise au quotidien ? De temps en temps ? Jamais ? Et parmi la dernière catégorie, qui compte l&apos;utiliser maintenant ?&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Industrialiser le Contrat dans un projet PHP - les Slides</title>
   <link href="https://blog.lepine.pro/industrialisation/industrialiser-le-contrat-dans-un-projet-php-slides"/>
   <updated>2012-10-31T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/industrialisation/industrialiser-le-contrat-dans-un-projet-php-slides</id>
   <content type="html">&lt;p&gt;Suite à l&apos;invitation de l&apos;antenne nantaise de l&apos;AFUP pour un rendez-vous PHP (très sympa au passage!), j&apos;ai eu envie de parler de la notion de Contrat dans un projet PHP, et surtout des outils pour s&apos;assurer qu&apos;un contrat, quel qu&apos;il soit, soit respecté.&lt;/p&gt;

&lt;p&gt;En effet, mon opinion est que TOUT, absolument tout, est Comportement : une application est un comportement vis-à-vis d&apos;une donnée entrante (requête HTTP) pour fournir une information (réponse HTTP) ; de la même façon une fonction est un comportement vis-à-vis d&apos;une donnée entrante (paramètre) pour fournir une information (valeur de retour) ; etc.&lt;/p&gt;

&lt;p&gt;Or, qui dit &quot;comportement&quot;, dit &quot;contrat&quot; pour s&apos;assurer que le comportement souhaité est bel et bien appliqué. Je vous propose donc dans ces slides &lt;a href=&quot;/php/ressources-tutos-php/communiquer-a-travers-internet&quot;&gt;de retrouver&lt;/a&gt; différents niveaux de contrat (code source, travail en équipe, besoin fonctionnel...), et à chaque fois différents outils pour s&apos;assurer automatiquement qu&apos;il sont respectés.&lt;/p&gt;

&lt;p&gt;Je suis curieux de vos retours sur ces notions de Comportement/Contrat, et aussi curieux de savoir quels outils vous avez l&apos;habitude d&apos;utiliser parmi ceux cités. N&apos;hésitez pas à nous dire ça par commentaire :-)&lt;/p&gt;

  &lt;div style=&quot;margin-bottom:5px&quot;&gt; &lt;strong&gt; &lt;a href=&quot;http://fr.slideshare.net/halleck45/industrialiser-le-contrat-dans-un-projet-php&quot; title=&quot;Industrialiser le contrat dans un projet PHP&quot; target=&quot;_blank&quot;&gt;Industrialiser le contrat dans un projet PHP&lt;/a&gt; &lt;/strong&gt; from &lt;strong&gt;&lt;a href=&quot;http://fr.slideshare.net/halleck45&quot; target=&quot;_blank&quot;&gt;halleck45&lt;/a&gt;&lt;/strong&gt; &lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>Design Pattern : pour des règles métiers complexes et/ou changeantes</title>
   <link href="https://blog.lepine.pro/php/gerer-des-regles-metiers-complexes-etou-changeantes"/>
   <updated>2012-09-27T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/php/gerer-des-regles-metiers-complexes-etou-changeantes</id>
   <content type="html">&lt;p&gt;Désolé d&apos;avance pour la longueur de ce billet ; comme ça fait longtemps que mon blog n&apos;a pas été mis à jour, j&apos;en profite pour faire un mini-tutoriel sur un sujet qui me tient à coeur : comment gérer les règles métier, autrement dit les Spécifications fonctionnelles, dans un projet php ?&lt;/p&gt;

&lt;p&gt;
&lt;p&gt;Je pense qu&apos;on a tous (enfin, on devrait :-) ) avoir en tête les principes &lt;a href=&quot;http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29&quot;&gt;SOLID&lt;/a&gt;. Ces principes sont étroitement liées à la notion de Complexité Cyclomatique, qui elle, est moins connue.&lt;/p&gt;
&lt;/p&gt;

&lt;p&gt;
&lt;p&gt;Derrière ce terme barbare, que vous connaissez bien si vous faites du test unitaire ou si vous utilisez des outils tels que &lt;a href=&quot;http://pdepend.org/&quot;&gt;PHPDepend&lt;/a&gt;, se cache en réalité quelque chose de simple : si chaque bloc conditionnel de votre projet est un noeud, la complexité cyclomatique est la somme de l&apos;ensemble des chemins empruntable dans votre projet.&lt;/p&gt;
&lt;/p&gt;

&lt;p&gt;
&lt;p&gt;De cette manière, le code suivant :&lt;/p&gt;
&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php
&amp;lt;p&amp;gt;if c1&amp;lt;/p&amp;gt;
    a()
&amp;lt;p&amp;gt;else&amp;lt;/p&amp;gt;
    b()

&amp;lt;p&amp;gt;if c2&amp;lt;/p&amp;gt;
    c()
&amp;lt;p&amp;gt;else&amp;lt;/p&amp;gt;
    d()&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;
&lt;p&gt;a une complexité cyclomatique plus élevée que&lt;/p&gt;
&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php
&amp;lt;p&amp;gt;if c1&amp;lt;/p&amp;gt;
    a()
&amp;lt;p&amp;gt;else&amp;lt;/p&amp;gt;
    b()&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;
&lt;p&gt;Un programme maintenable est en général un programme dont la complexité cyclomatique est la plus faible possible. Et c&apos;est justement l&apos;enjeu de la POO de nous fournir un moyen de concevoir des applications de Complexité cyclomatique faible.&lt;/p&gt;
&lt;/p&gt;

&lt;p&gt;
&lt;p&gt;Bon, ça c&apos;est pour comprendre le principe. Après on me dit souvent : &quot;&lt;strong&gt;oui, mais moi comment je fais pour gérer mes différents cas possibles si je dois limiter mes if() ?&lt;/strong&gt;&quot;&lt;/p&gt;
&lt;/p&gt;

&lt;p&gt;
&lt;p&gt;Prenez cet exemple :&lt;/p&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;on a un panier de produits&lt;/li&gt;
&lt;li&gt;on ne peut ajouter que des produits en stock&lt;/li&gt;
&lt;li&gt;on ne peut ajouter que 20 produits maximum&lt;/li&gt;
&lt;li&gt;la règle ci-dessus ne s&apos;applique pas en période de fêtes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;les règles ci-dessus sont susceptibles de changer souvent&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
&lt;p&gt;Générallement on imbrique des if(), du coup on se retrouve avec un arbre applicatif assez large, c&apos;est-à-dire de nombreux chemins possibles. A terme :&lt;/p&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;on a un risque de changement du code source très important (à chaque fois qu&apos;on ajoute une règle métier)&lt;/li&gt;
&lt;li&gt;on gère mal l&apos;ajout de nouvelles règles&lt;/li&gt;
&lt;li&gt;très vite l&apos;algorithme devient imbuvable car trop complexe&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
&lt;p&gt;C&apos;est là qu&apos;intervient la notion &lt;strong&gt;Specification&lt;/strong&gt; (bon, je sais il est temps, l&apos;intro était longue ;-) )&lt;/p&gt;
&lt;/p&gt;

&lt;p&gt;
&lt;p&gt;Ce pattern répond à : &quot;Comment gérer mes règles métier dans mon projet&quot;. Il est généralement associé au DDD (Domain Driven Design), mais on peut l&apos;appliquer dans n&apos;importe quel contexte qui s&apos;y prête.&lt;/p&gt;
&lt;/p&gt;

&lt;p&gt;
&lt;p&gt;L&apos;idée est la suivante : chaque règle métier va être représentée par un objet (une Spécification), à qui l&apos;on va demander si la règle est respectée :&lt;/p&gt;
&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;$anyObject = new StdClass;
$specification = new MySpecification;
$isOk = $specification-&amp;gt;isSatisfedBy($anyObject);&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;
&lt;p&gt;Là où ça devient puissant, c&apos;est qu&apos;on va pouvoir créer des Spécifications composites pour créer des règles métiers complexes à partir d&apos;un ensemble de règles simples :&lt;/p&gt;
&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;$anyObject = new StdClass;
$specification =
    new MySpecification1()
    -&amp;gt;and(new MySpecification2())
    -&amp;gt;and(
        new MySpecification3()
        -&amp;gt;or(new MySpecification4())
    );
;
$isOk = $specification-&amp;gt;isSatisfedBy($anyObject);&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;
&lt;p&gt;Vous voyez les avantages : vous pouvez désormais appliquer n&apos;importe quelle règle métier sans avoir à imbriquer plein de if() ; si vous souhaitez tester unitairement une règle, vous pouvez mocker les autres ; vos règles sont facilement évolutives...&lt;/p&gt;
&lt;/p&gt;

&lt;p&gt;
&lt;p&gt;Bon concrètement comment ça se passe ? Il faut commencer par créer notre contrat pour le fonctionnement de nos Spécifications :&lt;/p&gt;
&lt;/p&gt;


&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;interface SpecificationInterface {

    public function isSatisfiedBy($object);

    public function andSpec(SpecificationInterface $specification);

    public function orSpec(SpecificationInterface $specification);

    public function notSpec(SpecificationInterface $specification);
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;
&lt;p&gt;Ensuite, pour permettre la création de Spécification composite il faut créer une classe abstraite générique pour nos spécifications.&lt;/p&gt;
&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;abstract class Specification implements SpecificationInterface {

    public function andSpec(SpecificationInterface $specification) {
        return new AndSpecification($this, $specification);
    }

    public function orSpec(SpecificationInterface $specification) {
        return new OrSpecification($this, $specification);
    }

    public function notSpec(SpecificationInterface $specification) {
        return new NotSpecification($this);
    }

}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;
&lt;p&gt;Il ne nous reste plus qu&apos;à déterminer le comportement de chacunes de nos structures de contrôle :&lt;/p&gt;
&lt;/p&gt;

&lt;p&gt;Pour le &quot;et&quot;:&lt;/p&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;class AndSpecification extends Specification implements SpecificationInterface {

    private $specification1;
    private $specification2;

    function __construct(SpecificationInterface $specification1, SpecificationInterface $specification2) {
        $this-&amp;gt;specification1 = $specification1;
        $this-&amp;gt;specification2 = $specification2;
    }

    public function isSatisfiedBy($object) {
        return $this-&amp;gt;specification1-&amp;gt;isSatisfiedBy($object)
                &amp;amp;&amp;amp; $this-&amp;gt;specification2-&amp;gt;isSatisfiedBy($object);
    }

}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;
&lt;p&gt;Pour le &quot;ou&quot; :&lt;/p&gt;
&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;class OrSpecification extends Specification implements SpecificationInterface {

    private $specification1;
    private $specification2;

    function __construct(SpecificationInterface $specification1, SpecificationInterface $specification2) {
        $this-&amp;gt;specification1 = $specification1;
        $this-&amp;gt;specification2 = $specification2;
    }

    public function isSatisfiedBy($object) {
        return $this-&amp;gt;specification1-&amp;gt;isSatisfiedBy($object)
                ||  $this-&amp;gt;specification2-&amp;gt;isSatisfiedBy($object);
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;
&lt;p&gt;Et enfin pour le &quot;non&quot; :&lt;/p&gt;
&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;class NotSpecification extends Specification implements SpecificationInterface {

    private $specification;

    public function __construct($specification) {
        $this-&amp;gt;specification = $specification;
    }

    public function isSatisfiedBy($object) {
        return !$this-&amp;gt;specification-&amp;gt;isSatisfiedBy($object);
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;
&lt;p&gt;Ca y est, on vient de se créer le minimum vital pour gérer nos règles métiers. Ca, c&apos;est fait une bonne fois pour toute...&lt;/p&gt;
&lt;/p&gt;

&lt;p&gt;
&lt;p&gt;Maintenant dans notre projet il suffit de faire hériter nos règles de la classe Specification. Par exemple :&lt;/p&gt;
&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;class SpecLePannierPeutEtreRempli extends Specification {
    public function isSatisfiedBy($customer) {
        return(boolean)  $x; // la condition de notre règle ici
    }
}

class SpecOnEstEnPeriodeDeFetes extends Specification {
    public function isSatisfiedBy($customer) {
        return (boolean) $x; // la condition de notre règle ici
    }
}

class SpecLeProduitEstEnStock extends Specification {
    public function isSatisfiedBy($customer) {
        return (boolean) $x; // la condition de notre règle ici
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;
&lt;p&gt;Et après il suffit simplement d&apos;utiliser nos règles. Depuis PHP 5.4 on peut utiliser une interface fluide sur nos constructeurs, nous voici donc avec :&lt;/p&gt;
&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;$specification =
    new SpecLeProduitEstEnStock()
    -&amp;gt;and(SpecLePannierPeutEtreRempli())
    -&amp;gt;and(
        new SpecIlResteDeLaPlaceDansLePannier()
        -&amp;gt;or(new SpecOnEstEnPeriodeDeFetes())
    );

if($specification-&amp;gt;isSatisfedBy(specification)) {
    $panier-&amp;gt;ajouterProduit($produit);
} else {
    throw new ArticleNePeutPasEtreMisDansLePannierException(&amp;#39;...&amp;#39;);
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;
&lt;p&gt;Pour synthétiser :&lt;/p&gt;
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;la Spécification permet de gérer des règles métiers&lt;/li&gt;
&lt;li&gt;elle permet de combiner n règles métiers dynamiquement&lt;/li&gt;
&lt;li&gt;elle facilite la gestion du changement fonctionnel&lt;/li&gt;
&lt;li&gt;elle facile la lisibilité des règles&lt;/li&gt;
&lt;li&gt;elle vous permet de mocker certaines parties des règles métier&lt;/li&gt;
&lt;li&gt;ce n&apos;est pas un remède miracle, mais elle mérite d&apos;être plus utilisé ^^&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
&lt;p&gt;N&apos;hésitez pas à laisser vos retours sur ce pattern Specification, je suis curieux de savoir s&apos;il est utilisé massivement ou non ? Ou peut-être utilisez-vous déjà un framework de gestion de règles métier, comme ceux qu&apos;il existe dans le monde du Java ?&lt;/p&gt;
&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Behat - Une interface graphique pour rédiger / lister ses fonctionnalités</title>
   <link href="https://blog.lepine.pro/php/actus-php/behat-une-interface-graphique-pour-rediger-lister-ses-fonctionnalites"/>
   <updated>2012-07-31T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/php/actus-php/behat-une-interface-graphique-pour-rediger-lister-ses-fonctionnalites</id>
   <content type="html">&lt;p&gt;Encore du Behat, mais du neuf cette fois !&lt;/p&gt;

&lt;p&gt;Ca fait maintenant pas mal de temps que je planche sur une interface graphique pour utiliser Behat. Pourquoi ? Tout simplement parce que je trouve qu&apos;un client / product owner n&apos;a pas à plonger dans des fichiers textes imbuvables pour rédiger ses spécifications. C&apos;est à mon avis le grand problème de Behat : seuls les développeurs savent l&apos;utiliser.&lt;/p&gt;

&lt;p&gt;Pour rappel, &lt;strong&gt;Behat permet d&apos;écrire des spécifications&lt;/strong&gt; / tests de recette dans une syntaxe précise (Gherkin), puis de lancer une recette automatisée de ces spécifications, afin d&apos;en ressortir la liste des fonctionnalités finies / en cours de développement / en échec.&lt;/p&gt;

&lt;p&gt;Comme je le disais, j&apos;ai donc travaillé sur une &lt;a href=&quot;http://halleck45.github.com/BehatWizardBundle/demo/behat/wizard/list.html&quot; target=&quot;_blank&quot;&gt;interface graphique&lt;/a&gt; pour &lt;strong&gt;simplifier la vie des clients et des Products Owners&lt;/strong&gt; qui souhaitent fournir des spécifications à leurs développeurs. Ca a été un travail intéressant pour pas mal de raisons.&lt;/p&gt;

&lt;strong&gt;La première de ces raisons est technique&lt;/strong&gt; : je suis originaire du monde Zend Framework, cet outil a été pour moi l&apos;occassion d&apos;approfondir un peu mes connaissances de Symfony 2.

&lt;p&gt;Une autre raison, sans doute la plus importante, concerne l&apos;&lt;strong&gt;ergonomie de ce logiciel&lt;/strong&gt;. C&apos;est sans doute ce qui m&apos;a pris le plus de temps : réussir à me plonger dans la tête d&apos;un utilisateur non technique pour offrir une expérience utilisateur agréable à n&apos;importe qui, même non développeur. Pas si simple, et peut-être pas si réussi, mais je suis tout de même assez satisfait, même si je me doute que cette appli va pas mal évoluer au fil du temps (j&apos;attends vos retours là-dessus hein :-) ! ).&lt;/p&gt;

&lt;p&gt;Enfin, et c&apos;est pas négligeable, &lt;strong&gt;c&apos;est mon premier projet open-source !&lt;/strong&gt; Certes j&apos;ai déjà contribué par-ci par là à des projets open source, mais je n&apos;avais jamais comme ça jusqu&apos;à maintenant. Ce qui me pousse (et m&apos;a poussé à consacrer pas mal de temps à ce projet) c&apos;est l&apos;envie de voir plus utilisé cet outil (Behat) que je trouve pertinent et adapté à la vie en entreprise, mais malheureusement trop difficile d&apos;accès pour les décideurs. J&apos;espère que cet outil sera un pas de plus pour l&apos;appropriation du &lt;acronym title=&quot;Behaviour Driven Development&quot;&gt;BDD&lt;/acronym&gt; par les entreprises.&lt;/p&gt;

&lt;p&gt;Quoi qu&apos;il en soit, je rappelle je vous invite à &lt;a href=&quot;http://halleck45.github.com/BehatWizardBundle/demo/behat/wizard/list.html&quot; target=&quot;_blank&quot;&gt;regarder la démo de ce produit&lt;/a&gt; pour me dire ce que vous en pensez, et pourquoi pas à contribuer à son amélioration en forkant ce projet et en y ajoutant votre pierre. L&apos;installation est, j&apos;espère, on ne peut plus simple, vu que c&apos;est un &lt;a href=&quot;https://github.com/Halleck45/BehatWizardBundle#behatwizardbundle&quot; target=&quot;_blank&quot;&gt;simple Bundle pour Symfony 2&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Ce n&apos;est pas la &lt;a href=&quot;http://www.jubianchi.fr/2012/04/10/teasing-behat-viewer/&quot; target=&quot;_blank&quot;&gt;seule tentative&lt;/a&gt;, et comme les autres ce n&apos;est pas encore stable. Mais j&apos;espère qu&apos;on arrivera ensemble à faire quelque chose de bien pour proposer un outil digne de ce nom.&lt;/p&gt;

&lt;p&gt;J&apos;attends vos retours et idées d&apos;amélioration avec impatience ! :-p&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Exploiter PHP 5 - les slides</title>
   <link href="https://blog.lepine.pro/php/ressources-tutos-php/slides-afup-orleans-exploiter-php-5"/>
   <updated>2012-07-09T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/php/ressources-tutos-php/slides-afup-orleans-exploiter-php-5</id>
   <content type="html">&lt;p&gt;Comme promis à ceux qui étaient présents, voici mes slides de notre rendez-vous du 5 juillet à Orléans, durant lequel on a parlé &quot;outils&quot; et &quot;SPL&quot;. &lt;/p&gt;

&lt;p&gt;Bien sûr, les slides ne sont pas exhaustifs, mais j&apos;espère avoir réussi à convaincre que PHP était un outil riche, très riche même, et qui mérite largement qu&apos;on aille explorer un peu plus loin la documentation pour éviter de réinventer la roue à chaque développement :-)&lt;/p&gt;

&lt;p&gt;Par rapport aux questions qu&apos;il y a pu avoir, voici un lien pour obtenir des informations sur la &lt;a href=&quot;https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md&quot; target=&quot;_blank&quot;&gt;PSR-0&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Merci à tous ceux qui sont venus, j&apos;ai pris plaisir à faire cette petite présentation ;-)&lt;/p&gt;

&lt;div style=&quot;width:425px&quot; id=&quot;__ss_13568786&quot;&gt;&lt;strong style=&quot;display:block;margin:12px 0 4px&quot;&gt;&lt;a href=&quot;http://www.slideshare.net/halleck45/exploiter-php-5&quot; title=&quot;Exploiter php 5&quot;&gt;Exploiter php 5&lt;/a&gt;&lt;/strong&gt;&lt;object id=&quot;__sse13568786&quot; width=&quot;425&quot; height=&quot;355&quot;&gt;&lt;param name=&quot;movie&quot; value=&quot;http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=afupexploiterphp5-120707033002-phpapp01&amp;stripped_title=exploiter-php-5&amp;userName=halleck45&quot; /&gt;&lt;param name=&quot;allowFullScreen&quot; value=&quot;true&quot;/&gt;&lt;param name=&quot;allowScriptAccess&quot; value=&quot;always&quot;/&gt;&lt;param name=&quot;wmode&quot; value=&quot;transparent&quot;/&gt;&lt;embed name=&quot;__sse13568786&quot; src=&quot;http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=afupexploiterphp5-120707033002-phpapp01&amp;stripped_title=exploiter-php-5&amp;userName=halleck45&quot; type=&quot;application/x-shockwave-flash&quot; allowscriptaccess=&quot;always&quot; allowfullscreen=&quot;true&quot; wmode=&quot;transparent&quot; width=&quot;425&quot; height=&quot;355&quot;&gt;&lt;/embed&gt;&lt;/object&gt;&lt;div style=&quot;padding:5px 0 12px&quot;&gt;View more &lt;a href=&quot;http://www.slideshare.net/&quot;&gt;presentations&lt;/a&gt; from &lt;a href=&quot;http://www.slideshare.net/halleck45&quot;&gt;halleck45&lt;/a&gt;.&lt;/div&gt;&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>BDD : produit mal pensé, développement raté</title>
   <link href="https://blog.lepine.pro/php/bdd-produit-mal-pense-developpement-rate"/>
   <updated>2012-07-05T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/php/bdd-produit-mal-pense-developpement-rate</id>
   <content type="html">&lt;p&gt;Ce billet, non technique, peut surprendre, mais je pense qu&apos;en tant que développeur il faut comprendre un minimum ce que doit être une spécification, surtout si on veut faire du Développement Piloté par le Comportement. Cela fait quelques temps que j&apos;avais en tête de rédiger ce petit billet, alors allons-y :-)&lt;/p&gt;

&lt;p&gt;Rédiger des tests d&apos;acceptation / tests de recette n&apos;est certes pas mon métier, mais à force de l&apos;expliquer, de le décrire, bref, à force de tenter de former des gens, je crois que je commencer à comprendre ce qui est si difficile dans ce travail de clarification.&lt;/p&gt;

&lt;p&gt;En un mot : la majorité des gens est déformée par son expérience professionnelle passée ; trop habitués aux SFG, aux SFD et autres specs bien verbeuses. Fournir une spécification claire et précise, alors que cela devrait être naturelle et facile, semble au contraire contre intuitif.&lt;/p&gt;

&lt;p&gt;Au risque de dire des choses évidentes, lorsqu&apos;on fournit des spécifications à un développeur, ce n&apos;est pas pour lui dire comment il doit faire son travail, mais pour lui décrire ce qu&apos;il souhaite possible pour l&apos;utilisateur. Une application, sans utilisateur pour la manipuler, est une coquille vide.&lt;/p&gt;

&lt;p&gt;Un produit, au contraire, place les utilisateurs (&lt;strong&gt;qui ont donc un rôle&lt;/strong&gt;) au centre. Le produit est donc constitué par ce que l&apos;utilisateur peur faire avec (son &lt;strong&gt;comportement&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;J&apos;insiste, l&apos;application n&apos;a en aucun cas un comportement ; non, c&apos;est la manière dont l&apos;utilisateur interagit avec l&apos;application qui constitue un comportement. On ne dit pas &quot;cette application peut&quot; mais &quot;cette application permet&quot;...&lt;/p&gt;

&lt;p&gt;Bref, tout ça pour dire quoi ? Et bien que si la personne qui rédige les spécifications n&apos;a pas cette vision du produit, l&apos;utilisation de tests automatisés (Behat) risque d&apos;être contre productive.&lt;/p&gt;

&lt;h2&gt;Un peu de concret&lt;/h2&gt;

&lt;p&gt;Je vais prendre un exemple que j&apos;ai pu voir récemment. Il s&apos;agit de spécifier un import de données dans une application :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-gherkin&quot; data-lang=&quot;gherkin&quot;&gt;Fonctionnalité: pouvoir importer des événement
  Afin d&amp;#39;ajouter des événements dans l&amp;#39;application depuis un fichier d&amp;#39;import
  En tant que Batch
  Je dois pouvoir importer un événement

  Scénario :
    Etant donné que je reçois un fichier d&amp;#39;import valide
    Et que je reçois un événement valide
    En tant que Batch
    Je dois pouvoir importer un événement&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Au premier coup d&apos;oeil, le développeur voit que quelque chose cloche dans cette fonctionnalité. En tant que Batch ?! Hum, bizarre, mais oui c&apos;est vrai, c&apos;est bien un batch qui va s&apos;en charger. Je reçois cette spéc, elle me paraît logique, je vais donc l&apos;implémenter.&lt;/p&gt;

&lt;p&gt;Oui, mais NON. Si on implémente cette fonctionnalité on fonce dans le mur ! Que se passera t-il ? Tout d&apos;abord on est tributaire du moyen technique utilisé. On pourrait très bien envoyer l&apos;info &lt;a href=&quot;http://www.rfc1149.net/rfc1149.html&quot; target=&quot;_blank&quot;&gt;par pigeon voyageur&lt;/a&gt; pour une raison x ou y, et dans ce cas la fonctionnalité deviendrait invalide.&lt;/p&gt;

&lt;p&gt;Autre souci : quel bénéfice l&apos;utilisateur tire t-il ? Aucun. Non non, aucun. On se moque que l&apos;application puisse importer de la donnée, ça c&apos;est le moyen. Non, la vrai fonctionnalité c&apos;est de pouvoir intégrer des nouvelles données dans l&apos;application. Et là j&apos;ai un bénéfice pour l&apos;utilisateur. J&apos;ai donc aussitôt proposé ceci :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-gherkin&quot; data-lang=&quot;gherkin&quot;&gt;Fonctionnalité: pouvoir intégrer mes nombreux événements dans l&amp;#39;application
  Afin d&amp;#39;ajouter rapidement plusieurs événements
  En tant qu&amp;#39;utilisateur qui a le droit d&amp;#39;ajouter par lots des événements
  Je dois pouvoir intégrer de nouveaux événements rapidement

  Scénario :
    Etant donné que je fournis une liste d&amp;#39;événements
    Et que ces événements sont valides
    Quand je demande à intégrer cette liste d&amp;#39;événements
    Alors je dois pouvoir constater que ces événements ont bien été ajoutés&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Pas grand chose n&apos;est changé, mais tout change. Certes, ce n&apos;est toujours pas parfait, mais désormais, quelque soit le moyen technique utilisé (transfert par pigeons voyageurs, clef USB, FTP...), la spécification de la fonctionnalité restera valable.&lt;/p&gt;

&lt;p&gt;Plus encore, le développeur comprend tout de suite le souhait du client. Après c&apos;est son rôle à lui, développeur (ou un ergonome, un architecte, mais surtout pas au fonctionnel) de déterminer comment satisfaire la demande du client. Et là, utiliser Behat a un sens.&lt;/p&gt;

&lt;p&gt;Un autre exemple : le taxi. On peut dire que ça consiste à demander de l&apos;argent à un client afin de le transporter dans une voiture. Mais on peut aussi dire qu&apos;il s&apos;agit de transporter une ou plusieurs personnes d&apos;un point A à un point B. Et là ça marche, car en tant que client, mon souhait c&apos;est bel et bien de me rendre quelque part. Et là surtout, la spécification reste valable qu&apos;on parle de taxi moto, de calèche...&lt;/p&gt;

&lt;h2&gt;Impact sur les dates de livraison du projet&lt;/h2&gt;

&lt;p&gt;Un autre avantage fondamental est le contrôle que cela offre au propriétaire sur la date de livraison. En découpant le produit en fonctionnalités fondamentales, claires et simples, il devient facile de mettre de côté certaines fonctionnalités pour gagner un peu de temps, quitte à les ajouter plus tard.&lt;/p&gt;

&lt;p&gt;Dans l&apos;exemple du taxi, si je monte ma compagnie, je vais pouvoir dire ceci : &quot;On du retard. Nous allons donc laisser de côté la fonctionnalité de paiement. Certes je vais perdre de l&apos;argent un temps, mais au moins je serai visible. Nous ajouterons la fonctionnalité de paiement, qui n&apos;est pas fondamentale pour l&apos;instant, dans 15 jours.&quot;&lt;/p&gt;

&lt;p&gt;A mon avis, les développeurs doivent en être conscients, au risque sinon d&apos;aboutir à un échec du projet. &lt;strong&gt;Pour faire de Développement Piloté par le Comportement, il FAUT un comportement&lt;/strong&gt;, et donc un produit clair dont le coeur est l&apos;utilisateur.&lt;/p&gt;

&lt;p&gt;Après, c&apos;est mon ressenti, je serai curieux de connaître le vôtre sur cette question :-)&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Les principales causes d'échec du BDD</title>
   <link href="https://blog.lepine.pro/php/les-principales-causes-dechec-du-bdd"/>
   <updated>2012-05-24T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/php/les-principales-causes-dechec-du-bdd</id>
   <content type="html">&lt;p&gt;Bonjour à tous ! Alors bien évidemment, il serait absurde de vouloir lister toute les erreurs possibles, et totalement illusoire de croire que j&apos;en n&apos;en fait plus ; mais je crois pouvoir donner quelques exemples de ce qu&apos;il faut éviter à tout prix lorsque l&apos;on fait du développement piloté par le comportement avec Behat.&lt;/p&gt;

&lt;p&gt;Petit rappel : Behat, c&apos;est quoi ? En un mot, c&apos;est un outil qui va vous permettre de pratiquer du BDD (Behavior Driven Development) en PHP. En d&apos;autres mots, il va vous permettre de tester automatiquement si le développement d&apos;un produit correspond aux spécifications qu&apos;en a donné le client. Vous trouverez une &lt;a title=&quot;Behat – jour 1 : comment tester son produit SCRUM ?&quot; href=&quot;/php/behat-jour-1-comment-tester-son-produit-scrum&quot;&gt;description beaucoup plus complète ici&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Bref, c&apos;est génial, c&apos;est simple à utiliser... mais c&apos;est extrêmement difficile à utiliser correctement. Et c&apos;est catastrophique si c&apos;est mal mal utilisé ! Pourquoi ?&lt;/p&gt;

&lt;p&gt;Behat a deux côtés : un côté fonctionnel (rédacteur), rédigé avec la syntaxe de Gherkin, un côté &quot;développeur&quot;, en PHP.&lt;/p&gt;

&lt;p&gt;De ce que je vois, le plus souvent &lt;span style=&quot;text-decoration: underline;&quot;&gt;le fonctionnel&lt;/span&gt; (au choix) :&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;&lt;strong&gt;ne dispose pas du temps nécessaire&lt;/strong&gt; pour se consacrer à la rédaction des tests d&apos;acceptation (fonctionnalité)&lt;/li&gt;
	&lt;li&gt;n&apos;a &lt;strong&gt;pas une vision assez clair de son produit&lt;/strong&gt; pour pouvoir le découper fonctionnellement&lt;/li&gt;
	&lt;li&gt;&lt;strong&gt;confond interface&lt;/strong&gt; (ergonomie, disposition...) &lt;strong&gt;et comportement&lt;/strong&gt; de l&apos;application&lt;/li&gt;
	&lt;li&gt;&lt;strong&gt;confond contrôle sur les données et test sur le comportement&lt;/strong&gt; (trèèès souvent!)&lt;/li&gt;
	&lt;li&gt;ou, plus rarement, confond socle technique et fonctionnalité&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;De ce que je constate, &lt;span style=&quot;text-decoration: underline;&quot;&gt;le développeur&lt;/span&gt; (au choix) :&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;est obligé de se substituer au client dans la rédaction des tests, ce qui n&apos;est &lt;strong&gt;pas son métier&lt;/strong&gt; (pas simple donc)&lt;/li&gt;
	&lt;li&gt;&lt;strong&gt;n&apos;arrive pas à s&apos;abstraire du technique&lt;/strong&gt;&lt;/li&gt;
	&lt;li&gt;&lt;strong&gt;se focalise sur le cheminement&lt;/strong&gt; (comment arriver là?) et l&apos;emplacement dans l&apos;application&lt;/li&gt;
	&lt;li&gt;a tendance à &lt;strong&gt;écrire du code PHP&lt;/strong&gt; plutôt que de réutiliser des étapes existantes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bon, le constant est sévère, mais je généralise bien sûr. Cependant il est très difficile d&apos;échapper à ça.&lt;/p&gt;

&lt;p&gt;Je passe aux exemples, tirés d&apos;un code vu ce matin même.&lt;/p&gt;
&lt;h2&gt;Décrire une fonctionnalité : pas si simple&lt;/h2&gt;
&lt;p&gt;Prenons ce bout de fonctionnalité que j&apos;ai reçu comme spécification :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-gherkin&quot; data-lang=&quot;gherkin&quot;&gt;Feature: access to the task&amp;#39;s page from a list of tasks
In order to see a task
As a logged in user
I need to open a task

Background:
Given I am logged in user

Scenario Outline:
When I press &amp;quot;Find a task&amp;quot;
And I fill in &amp;quot;Task reference&amp;quot; with &amp;quot;&amp;lt;reference&amp;gt;&amp;quot;
And I press &amp;quot;Search&amp;quot;
Then I should be on &amp;quot;index/task/id/&amp;lt;id&amp;gt;&amp;quot;

Examples:
| reference   | id  |
| task1       | 1   |
| task2       | 2   |&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Bon, ça marche. Mais quand on y regarde plus près :&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;on se consacre plus aux étapes permettant d&apos;accéder aux conditions du scénario qu&apos;au scénario lui-même&lt;/li&gt;
	&lt;li&gt;si la structure de la page change, le test est obsolète&lt;/li&gt;
	&lt;li&gt;on ne teste pas le comportement, mais la donnée. Si la donnée change, le test est obsolète&lt;/li&gt;
	&lt;li&gt;si l&apos;url change (rewriting, etc), le test est obsolète&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bref, &lt;span style=&quot;text-decoration: underline;&quot;&gt;le test va rapidement devenir obsolète.&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;Il est difficile dans ce cas de voir comment s&apos;abstraire des données (liaison id et task). Après réflexion, on peut suggérer d&apos;évoluer vers ceci :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-gherkin&quot; data-lang=&quot;gherkin&quot;&gt;Feature: access to the task&amp;#39;s page from a list of tasks
In order to see a task
As a logged in user
I need to open an task&amp;#39;s page from a list

Background:
Given I am logged in user

Scenario Outline:
Given I see a list of tasks, including the task &amp;quot;&amp;lt;reference&amp;gt;&amp;quot;
When I follow &amp;quot;&amp;lt;reference&amp;gt;&amp;quot;
Then I should be on the Task&amp;#39;s page
And I should see the task &amp;quot;&amp;lt;reference&amp;gt;&amp;quot;

Examples:
| reference  |
| task1      |
| task2      |&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;On a donc opéré des modifications afin de rendre le test indépendant du jeu de données ou de l&apos;interface de l&apos;application.&lt;/p&gt;

&lt;p&gt;Ce que je dis souvent, c&apos;est que, en théorie, un test de comportement est valide quelque soit le support :&lt;strong&gt; que l&apos;on passe d&apos;un site web à une application mobile, le changement de support ne change pas la fonctionnalité ou les scénarios !&lt;/strong&gt; Ca ne change que leur implémentation.&lt;/p&gt;

&lt;p&gt;On pourra certainement trouver encore à redire, mais la fonctionnalité, telle qu&apos;elle est décrite, est désormais valable quelque soit son implémentation technique. Seul son comportement est ici spécifié. Elle a donc une forte probabilité d&apos;être viable et pertinente dans le temps.&lt;/p&gt;
&lt;h2&gt;Implémenter une définition de fonctionnalité : pas plus facile&lt;/h2&gt;
&lt;p&gt;On a vu un exemple de fonctionnalité à risque. Passons de l&apos;autre côté et mettons-nous du point de vue du développeur. De la même façon, voici une implémentation possible :&lt;/p&gt;

&lt;p&gt;Note : en l’occurrence, la liste des tâches n&apos;est possible dans l&apos;application qu&apos;après  avoir effectué une recherche.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;/**
 * @Given /^I see a list of tasks, including the task &amp;quot;([^&amp;quot;]*)&amp;quot;$/
 */
public function iSeeAListOfTasksIncludingTheTask($reference)
{
    $session = $this-&amp;gt;getMainContext()-&amp;gt;getSubcontext(&amp;#39;mink&amp;#39;)-&amp;gt;getSession();
    $page = $session-&amp;gt;getPage();

    $session-&amp;gt;visit(&amp;#39;/task&amp;#39;);

    // Find the task in the search engine
    $page-&amp;gt;find(&amp;#39;css&amp;#39;, &amp;#39;.ipt-task-search&amp;#39;)-&amp;gt;setValue($reference);
    $page-&amp;gt;find(&amp;#39;css&amp;#39;, &amp;#39;.button-search&amp;#39;)-&amp;gt;press();

    if ($session-&amp;gt;getCurrentUrl() != &amp;quot;/task/{$reference}&amp;quot;) {
        throw new AssertException(&amp;quot;We cannot find the task {$reference} with the search engine&amp;quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;On constate différente choses :&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;Que de code ! C&apos;est long à écrire&lt;/li&gt;
	&lt;li&gt;Que de code ! Et pas réutilisable en plus !&lt;/li&gt;
	&lt;li&gt;Que de code ! Et qu&apos;est-ce qui se passe si l&apos;interface HTML change ?&lt;/li&gt;
	&lt;li&gt;On ne comprend pas ce qui se passe au premier coup d&apos;oeil&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;L&apos;implémentation fonctionne, mais est peu viable dans le temps, et surtout on a perdu du temps pour l&apos;écrire (c&apos;est fastidieux de devoir manipuler le navigateur à la main).&lt;/p&gt;

&lt;p&gt;Là où Behat est fort, c&apos;est qu&apos;il nous permet, en PHP, de faire comme si on écrivait des étapes de scénario &quot;à la main&quot;. Ca fait gagner un temps monstre et permet de réutiliser les définitions existantes :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;/**
* @Given /^I see a list of tasks, including the task &amp;quot;([^&amp;quot;]*)&amp;quot;$/
*/
public function iSeeAListOfTasksIncludingTheTask($reference) {
    return array(
        new Given(&amp;#39;I am on &amp;quot;/&amp;quot;&amp;#39;)
        , new When(sprintf(&amp;#39;I fill &amp;quot;Task reference&amp;quot; with &amp;quot;%s&amp;quot;&amp;#39;, $reference))
        , new When(&amp;#39;I press &amp;quot;Search&amp;quot;&amp;#39;)
    );
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Pour s&apos;aider, le développeur peut s&apos;appuyer (si tout se passe bien) sur la personne qui a rédigé le scénario, qui l&apos;aidera à découper sa définition en différentes étapes.&lt;/p&gt;

&lt;p&gt;Attention, contrairement au scénario Gherkin, ce code peut être amené parfois à évoluer. Par  exemple, si on ajoute un scénario pour la recherche de tâche, avec cette étape :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-gherkin&quot; data-lang=&quot;gherkin&quot;&gt;When I search the task &amp;quot;&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;On pourra dès lors écrire :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;/**
* @Given /^I search the task &amp;quot;([^&amp;quot;]*)&amp;quot;$/
*/
public function iSearchTheTask($reference) {
    return array(
        new Given(&amp;#39;I am on &amp;quot;/&amp;quot;&amp;#39;)
        , new When(sprintf(&amp;#39;I fill &amp;quot;Task reference&amp;quot; with &amp;quot;%s&amp;quot;&amp;#39;, $reference))
        , new When(&amp;#39;I press &amp;quot;Search&amp;quot;&amp;#39;)
    );
}

/**
* @Given /^I see a list of tasks, including the task &amp;quot;([^&amp;quot;]*)&amp;quot;$/
*/
public function iSeeAListOfTasksIncludingTheTask($reference) {
    return array(
        new When(sprintf(&amp;#39;I search the task &amp;quot;%s&amp;quot;&amp;#39;, $reference))
    );
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Le développeur peut donc, sans architecture ou code complexe, mais simplement en utilisant les objets Etapes fournis par Behat, organiser ses définitions de façon à les rendre réutilisables.&lt;/p&gt;
&lt;h2&gt;Le mot de la fin&lt;/h2&gt;
&lt;p&gt;Bref, ça semble évident, mais quand on fait du BDD... et bien il faut se focaliser le le Comportement. Ce n&apos;est pas facile, et contre intuitif pour beaucoup de monde. Toutefois, le rédacteur du test peut s&apos;aider de la structure de la fonctionnalité (comment la décrire, qui y participe, quels en sont les bénéfices, puis quels cas d&apos;utilisation je peux en donner).&lt;/p&gt;

&lt;p&gt;En d&apos;autres mots, le &quot;rédacteur&quot; ne doit pas s&apos;appuyer sur ce qu&apos;il connaît de son application (emplacement, design...), mais sur la vision du produit (comment ça se passe ? Avec quel gain pour l&apos;utilisateur ?). En clair :&lt;/p&gt;
&lt;blockquote&gt;Le rédacteur ne doit pas s&apos;appuyer sur ce qu&apos;il connaît de son application mais sur la vision du produit&lt;/blockquote&gt;
&lt;p&gt;Le développeur, lui, doit prendre l&apos;habitude de ne pas se lancer tête baissée dans le code, au risque de consacrer trop de temps et d&apos;énergie à l&apos;utilisation de Behat. Certes il doit écrire ce code, mais il ne code plus pour interagir avec un autre code (comme lorsqu&apos;il le fait pour un test unitaire par exemple), mais pour interagir avec un produit. En clair :&lt;/p&gt;
&lt;blockquote&gt;Le développeur n&apos;interagit plus avec du code mais avec un produit&lt;/blockquote&gt;
&lt;p&gt;Ceci dit, félicitations d&apos;avoir lu ce billet jusqu&apos;au bout :-) .&lt;/p&gt;

&lt;p&gt;Je ne prétend pas avoir le recul suffisant, mais je crois que ces constats s&apos;appliquent généralement. C&apos;est le cas pour vous aussi ? Vous avez vu d&apos;autres écueils courants ? Ou au contraire, pour vous tout a roulé tout de suite ?&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Mémento Industrialisation PHP : Outils et bonnes pratiques</title>
   <link href="https://blog.lepine.pro/php/memento-industrialisation-php-outils-et-bonnes-pratiques"/>
   <updated>2012-05-22T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/php/memento-industrialisation-php-outils-et-bonnes-pratiques</id>
   <content type="html">&lt;p style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;http://www.amazon.fr/M%C3%A9mento-outils-PHP-Eyrolles/dp/2212134800&quot;&gt;&lt;img class=&quot;aligncenter&quot; title=&quot;Couverture du mémento PHP Industrialisation&quot; src=&quot;http://ecx.images-amazon.com/images/I/411vzWHHC5L._SL500_AA300_.jpg&quot; alt=&quot;Couverture du mémento PHP Industrialisation&quot; width=&quot;300&quot; height=&quot;300&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;
&lt;p&gt;Certains d&apos;entre-vous sont déjà au courant, voire l&apos;ont déjà entre les mains, mais je planche depuis déjà quelques temps sur un mémento dédié aux outils PHP et aux pratiques liées à l&apos;industrialisation.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Mais comme l&apos;a bien dit &lt;a href=&quot;http://raphaelhertzog.fr/2012/04/17/le-memento-git-est-disponible/&quot; target=&quot;_blank&quot;&gt;Raphaël Hertzog il y a peu quand il parlait de son mémento Git&lt;/a&gt;, écrire un mémento de quelques pages ce n&apos;est pas si facile !&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Au départ il y a une volonté : faire une petite synthèse des principaux outils utiles et pratiques quand on souhaite &quot;industrialiser&quot; certains aspects d&apos;un développement afin d&apos;en faciliter la qualité : tests unitaires, de comportement, outils d&apos;audit de code, de versionning...&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Ensuite... Ensuite, et bien je me suis rendu compte qu&apos;il était extrêmement difficile de faire un travail de synthèse suffisant pour faire tenir toutes ces informations en quelques pages (13 pages exactement). Et même très difficile ! Il a fallu condenser, relire, supprimer, re-condenser l&apos;information.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Au final, plus qu&apos;un simple mémento &quot;cheat-sheet&quot;, j&apos;espère avoir réussi à présenter un tour d&apos;horizon des outils et pratiques pour ceux et celles qui souhaiteraient aller plus loin dans leurs développements PHP, qu&apos;il s&apos;agisse de faciliter le travail en équipe, la maintenabilité ou la fiabilité.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;L&apos;idée n&apos;était surtout pas de faire la liste des outils disponibles et de les comparer. Au contraire; j&apos;ai essayé de créer une continuité entre les outils présentés, afin d&apos;en avoir une vision d&apos;ensemble, et surtout afin de tirer parti de chacune de leur spécificité. Et j&apos;espère avoir réussi :-) !&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Chaque outil est présenté avec sa description, sa procédure d&apos;installation, ses usages, et généralement des petites astuces bonnes à avoir sous les yeux.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Ceux qui me connaissent savent que la qualité logicielle me tient beaucoup à cœur. J&apos;espère que ce mémento saura vous aider à découvrir, ou redécouvrir, quelques uns des outils utiles à mettre en place et conserver cette qualité dans vos projets PHP.&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Je vais faire mon commercial, mais &lt;a href=&quot;http://www.amazon.fr/M%C3%A9mento-outils-PHP-Eyrolles/dp/2212134800&quot; target=&quot;_blank&quot;&gt;le mémento est en pré-vente sur Amazon au prix de 9,90 €&lt;/a&gt;. Comme tous les mémentos Eyrolles, il est plastifié et théoriquement &quot;indéchirable&quot;, vous pouvez l&apos;amener partout. Attention, le mémento a la même couleur que le très bon &lt;a href=&quot;http://www.eyrolles.com/Informatique/Livre/memento-php-et-sql-9782212117851&quot; target=&quot;_blank&quot;&gt;mémento PHP et SQL&lt;/a&gt;. A ne pas confondre donc...&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Pour finir, si vous avez l&apos;occasion de l&apos;avoir entre vos mains n&apos;hésitez surtout pas à me faire un retour : ce qui va, ce qui manque, ce qui est en trop... J&apos;attends avec impatience vos avis !&lt;/p&gt;
&lt;/p&gt;&lt;p&gt;
&lt;p&gt;Au passage, encore merci à &lt;a href=&quot;http://jolicode.com/equipe&quot; target=&quot;_blank&quot;&gt;Xavier Lacot&lt;/a&gt; et à &lt;a href=&quot;https://twitter.com/#!/fauveauarmel&quot; target=&quot;_blank&quot;&gt;Armel Fauveau&lt;/a&gt;, qui m&apos;ont laaaaargement aidé et relu ;-)&lt;/p&gt;
&lt;/p&gt;</content>
 </entry>
 
 <entry>
   <title>Streams - Flux personnalisés et filtres en PHP</title>
   <link href="https://blog.lepine.pro/php/flux-personnalises-et-filtres-en-php-streams"/>
   <updated>2012-04-13T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/php/flux-personnalises-et-filtres-en-php-streams</id>
   <content type="html">&lt;p&gt;Lorsque vous faites un fopen(), ou toute autre fonction équivalente, PHP vous retourne une ressource, sous forme d&apos;un flux. Il &lt;a href=&quot;http://www.php.net/manual/en/wrappers.php&quot; target=&quot;_blank&quot;&gt;existe différent types de flux en PHP&lt;/a&gt; :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;p&amp;gt;fopen(&amp;#39;file://...&amp;#39;) // fichier&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;fopen(&amp;#39;php://temp&amp;#39;) // fichier temporaire&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;fopen(&amp;#39;php://memory&amp;#39;) // en mémoire&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;fopen(&amp;#39;php://stdout&amp;#39;) // sortie de la console&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;etc.&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Bref, il y en a pas mal...&lt;/p&gt;

&lt;p&gt;Mais on oublie souvent qu&apos;on peut aussi ajouter ses propres types de flux. Par exemple je vais créer un type de flux &quot;twitter&quot; pour lire mes tweets :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;$fp = fopen(&amp;quot;twitter://@halleck45&amp;quot;, &amp;quot;r&amp;quot;);
if ($fp) {
    while (!feof($fp)) {
        var_dump(fgets($fp, 140));
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h2&gt;Création d&apos;un nouveau type de flux&lt;/h2&gt;
&lt;p&gt;C&apos;est relativement facile : il suffit d&apos;&lt;a href=&quot;http://fr.php.net/manual/fr/function.stream-wrapper-register.php&quot; target=&quot;_blank&quot;&gt;ajouter un nouveau gestionnaire de flux&lt;/a&gt;, c&apos;est à dire une classe qui respecte le prototype &lt;strong&gt;&lt;a href=&quot;http://fr.php.net/manual/fr/class.streamwrapper.php&quot; target=&quot;_blank&quot;&gt;StreamWrapper&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;stream_wrapper_register(&amp;quot;twitter&amp;quot;, &amp;quot;TwitterStream&amp;quot;);&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Plutôt que d&apos;implémenter toutes les méthodes de ce prototype, concentrons nous sur le principal, et créons 4 méthodes :&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;&lt;strong&gt;stream_open(&lt;/strong&gt;), qui va ouvrir notre flux&lt;/li&gt;
	&lt;li&gt;&lt;strong&gt;stream_close()&lt;/strong&gt;, pour le fermer&lt;/li&gt;
	&lt;li&gt;&lt;strong&gt;stream_read($size)&lt;/strong&gt; qui va être appelée à chaque lecture dans le flux&lt;/li&gt;
	&lt;li&gt;&lt;strong&gt;stream_eof()&lt;/strong&gt;, pour indiquer qu&apos;on arrive à la fin&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pour simplifier l&apos;exemple, le flux Twitter est en lecture seule. Pour la même raison, on va rapatrier tous les tweets d&apos;un coup et les stocker dans notre objet dans un tableau.&lt;/p&gt;

&lt;p&gt;Pour démarrer, créons une petite fonction qui va aller chercher les tweets d&apos;un utilisateur donné :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;ini_set(&amp;#39;allow_url_fopen&amp;#39;, 1);
define(&amp;#39;TWITTER_PWD&amp;#39;, &amp;#39;votre-mot-de-passe&amp;#39;);
define(&amp;#39;TWITTER_LOGIN&amp;#39;, &amp;#39;votre-login&amp;#39;);

function example_stream_twitter_fetch($username) {
    $ch = curl_init();
    $url = sprintf(&amp;#39;http://api.twitter.com/1/statuses/user_timeline.json?screen_name=%s&amp;amp;include_entities=true&amp;amp;include_rts=true&amp;amp;count=20&amp;#39;, $username);
    curl_setopt($ch, CURLOPT_URL,$url);
    curl_setopt($ch, CURLOPT_USERPWD, TWITTER_LOGIN . &amp;#39;:&amp;#39; . TWITTER_PWD);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    $result = curl_exec($ch);
    curl_close($ch);
    return $result ? json_decode($result) : null;
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Maintenant le gestionnaire de flux :&lt;/p&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;class TwitterStream {

    protected $_json;
    protected $_offset = 0;

    public function stream_open($path, $mode, $options, &amp;amp;$opened_path) {

        //
        // Read only !
        if ($mode != &amp;#39;r&amp;#39;) {
            trigger_error(&amp;quot;Unsupported mode&amp;quot;, E_USER_WARNING);
            return false;
        }

        //
        // Calling example_stream_twitter_fetch() in order to fetch data from twitter
        $result = false;
        if (preg_match(&amp;#39;!@(\w*)!&amp;#39;, $path, $match)) {
            $result = example_stream_twitter_fetch($match[1]);
        }

        if (!$result) {
            if (($options &amp;amp; STREAM_REPORT_ERRORS)) {
                trigger_error(&amp;quot;Username not found&amp;quot;, E_USER_WARNING);
            }
            return false;
        }

        $this-&amp;gt;_json = $result;
        return (bool) $result;
    }

    public function stream_close() {
        $this-&amp;gt;_json = null;
        return true;
    }

    public function stream_read($count) {
        return $this-&amp;gt;_json[$this-&amp;gt;_offset++]-&amp;gt;text;
    }

    public function stream_eof() {
        return $this-&amp;gt;_offset &amp;gt;= sizeof($this-&amp;gt;_json);
    }

}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Rien de bien compliqué. Il faut juste bien penser à lancer un warning en cas de problème...&lt;/p&gt;

&lt;p&gt;Et ça suffit :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;stream_wrapper_register(&amp;quot;twitter&amp;quot;, &amp;quot;TwitterStream&amp;quot;);

$fp = fopen(&amp;quot;twitter://@halleck45&amp;quot;, &amp;quot;r&amp;quot;, STREAM_REPORT_ERRORS);
if ($fp) {
    while (!feof($fp)) {
        var_dump(fgets($fp, 140));
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Pratique non ?&lt;/p&gt;

&lt;p&gt;Bon, bien sûr l&apos;exemple est trivial, ne serait-ce parce que les tweets peuvent dépasser les 140 caractères à cause des liens ; mais je pense que vous aurez compris l&apos;intérêt de la chose :-)&lt;/p&gt;

&lt;h2&gt;Appliquer des fitres sur des flux&lt;/h2&gt;

&lt;p&gt;Autre &quot;truc pratique&quot; assez peu utilisé mais vraiment utile : on peut appliquer des filtre sur des flux, même sur les flux &quot;natifs&quot;.&lt;/p&gt;

&lt;p&gt;Par exemple, je veux convertir le texte que j&apos;écris dans un fichier en l33t, en utilisant cette fonction :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;function l33t($string) {
    return str_replace(array(&amp;#39;l&amp;#39;, &amp;#39;e&amp;#39;, &amp;#39;t&amp;#39;), array(&amp;#39;1&amp;#39;, &amp;#39;3&amp;#39;, &amp;#39;7&amp;#39;), $string);
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Nous allons enregistrer le filtre &quot;l33t&quot; et l&apos;associer à la classe &quot;l33t_filter&apos; :&lt;/p&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;stream_filter_register(&amp;quot;l33t&amp;quot;, &amp;quot;l33t_filter&amp;quot;);&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Cette classe l33t_filter doit hériter de la classe native php_user_filter :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;class l33t_filter extends php_user_filter {

    function filter($in, $out, &amp;amp;$consumed, $closing) {
        
        while ($bucket = stream_bucket_make_writeable($in)) {
            //
            // leet -&amp;gt; l33t
            $bucket-&amp;gt;data = l33t($bucket-&amp;gt;data);
            $consumed += $bucket-&amp;gt;datalen;
            stream_bucket_append($out, $bucket);
        }
        // on retournerait PSFS_ERR_FATAL en cas d&amp;#39;erreur bloquante
        return PSFS_PASS_ON;
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;On peut par exemple imaginer appliquer des filtres de cryptage, de contrôle...&lt;/p&gt;

&lt;p&gt;Alors, convaincu ? :-) Avez-vous déjà utilisé votre propres flux ou filtre de flux en PHP ?&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Behat : liens et ressources utiles</title>
   <link href="https://blog.lepine.pro/php/behat-liens-et-ressources-utiles"/>
   <updated>2012-04-02T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/php/behat-liens-et-ressources-utiles</id>
   <content type="html">Ce billet fait suite à :

&lt;ul&gt;
	&lt;li&gt;&lt;a title=&quot;Behat – jour 1 : comment tester son produit SCRUM ?&quot; href=&quot;/php/behat-jour-1-comment-tester-son-produit-scrum&quot;&gt;Behat – jour 1 : comment tester son produit SCRUM ?&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a title=&quot;Behat – jour 2 : Installation et premiers tests&quot; href=&quot;/php/behat-jour-2-installation-et-premiers-tests&quot;&gt;Behat – jour 2 : Installation et premiers tests&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a title=&quot;Behat – jour 3 : Tester une application web avec Mink&quot; href=&quot;/php/behat-jour-3-tester-une-application-web-avec-mink&quot;&gt;Behat – jour 3 : Tester une application web avec Mink&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a title=&quot;Behat – jour 4 : API Mink, Sous-contextes et Hooks&quot; href=&quot;/php/behat-jour-4-api-mink-sous-contextes-et-hooks&quot;&gt;Behat – jour 4 : API Mink, Sous-contextes et Hooks&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pour ce dernier billet du tutoriel Behat, j&apos;avais prévu de parler la mise en place de Behat au sein d&apos;une plate-forme d&apos;intégration continue (PIC). Je me suis rendu compte qu&apos;il y avait déjà quelques bons tutos là dessus. Je vous propose donc plutôt une petite liste de liens que je vous invite à découvrir pour aller plus loin avec Behat.&lt;/p&gt;
&lt;h2&gt;Contextes prêts à l&apos;usage&lt;/h2&gt;
&lt;p&gt;Le &lt;a href=&quot;https://github.com/gabrielpillet/BehatCH&quot; target=&quot;_blank&quot;&gt;BehatCH&lt;/a&gt;, développé par &lt;a href=&quot;http://team-fusion.pmsipilot.com/&quot; target=&quot;_blank&quot;&gt;PMSIpilot&lt;/a&gt; est ce que j&apos;ai trouvé de plus complet pour apporter des bonnes bases à un projet. regardez surtout le &lt;a href=&quot;https://github.com/gabrielpillet/BehatCH/blob/master/features/bootstrap/contexts/BrowserContext.php&quot; target=&quot;_blank&quot;&gt;BrowserContext&lt;/a&gt; et le &lt;a href=&quot;https://github.com/gabrielpillet/BehatCH/blob/master/features/bootstrap/contexts/TableContext.php&quot; target=&quot;_blank&quot;&gt;TableContext&lt;/a&gt;, qui permettent d&apos;écrire des choses comme :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-gherkin&quot; data-lang=&quot;gherkin&quot;&gt;&amp;lt;p&amp;gt;I wait &amp;quot;5&amp;quot; seconds until I see &amp;quot;element&amp;quot;&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;I should see &amp;quot;5&amp;quot; elements&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;The 1st column of the 1st row in the &amp;quot;table&amp;quot; table should contain &amp;quot;Lorem&amp;quot;&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Vous pouvez également utiliser les &lt;a href=&quot;https://github.com/Behat/CommonContexts&quot; target=&quot;_blank&quot;&gt;Extra Contexts&lt;/a&gt; mis à disposition par l&apos;auteur de Behat, qui sont eux moins axés outils mais plus à mon sens comme des composants pour créer d&apos;autres outils.&lt;/p&gt;
&lt;h2&gt;Blogs intéressants sur Behat&lt;/h2&gt;
&lt;p&gt;Pour les blogs francophones, je vous recommande les blog de &lt;a href=&quot;http://team-fusion.pmsipilot.com/&quot; target=&quot;_blank&quot;&gt;PMSIpilot&lt;/a&gt; et de &lt;a href=&quot;http://knplabs.fr/blog&quot; target=&quot;_blank&quot;&gt;knpLabs&lt;/a&gt; (bon, ok c&apos;est plus souvent en anglais qu&apos;en français :-) ...).&lt;/p&gt;

&lt;p&gt;Pour les anglohpones, vous pouvez jeter un oeil sur le blog de &lt;a href=&quot;http://lestbddphp.wordpress.com&quot; target=&quot;_blank&quot;&gt;Shashikant Jagtap&lt;/a&gt;, de l&apos;&lt;a href=&quot;http://everzet.com/&quot; target=&quot;_blank&quot;&gt;auteur même de Behat&lt;/a&gt;, ou aussi sur &lt;a href=&quot;http://www.craftitonline.com/category/bdd/&quot; target=&quot;_blank&quot;&gt;craftItOnline&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Plate-forme d&apos;intégration continue et Behat&lt;/h2&gt;
&lt;p&gt;Avant toute chose sachez que Behat permet très facilement de générer des rapports en XML, au format JUnit :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;behat -f junit --out ./report&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Voici un tutoriel pour installer &lt;a href=&quot;http://lestbddphp.wordpress.com/2012/01/03/behat-with-sauce-labs-and-jenkins/&quot; target=&quot;_blank&quot;&gt;Behat sur Jenkins&lt;/a&gt;, et en voici &lt;a href=&quot;http://team-fusion.pmsipilot.com/679/creer-un-job-behat-dans-hudson/&quot; target=&quot;_blank&quot;&gt;un autre pour Hudson&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Bonne lecture ! :-) N&apos;hésitez pas si vous avez des ressources à suggérer à les mettre en commentaire&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Behat - jour 4 : API Mink, Sous-contextes et Hooks</title>
   <link href="https://blog.lepine.pro/non-classe/behat-jour-4-api-mink-sous-contextes-et-hooks"/>
   <updated>2012-03-29T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/non-classe/behat-jour-4-api-mink-sous-contextes-et-hooks</id>
   <content type="html">&lt;p&gt;Voici l&apos;avant dernier billet de la série sur la prise en main de Behat. Pour rappel, on a vu :&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;&lt;a title=&quot;Behat – jour 1 : comment tester son produit SCRUM ?&quot; href=&quot;/php/behat-jour-1-comment-tester-son-produit-scrum&quot;&gt;Behat – jour 1 : comment tester son produit SCRUM ?&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a title=&quot;Behat – jour 2 : Installation et premiers tests&quot; href=&quot;/php/behat-jour-2-installation-et-premiers-tests&quot;&gt;Behat – jour 2 : Installation et premiers tests&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a title=&quot;Behat – jour 3 : Tester une application web avec Mink&quot; href=&quot;/php/behat-jour-3-tester-une-application-web-avec-mink&quot;&gt;Behat – jour 3 : Tester une application web avec Mink&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Allons un peu plus loin :-)&lt;/p&gt;

&lt;h2&gt;Sortir des sentiers battus : l&apos;API de Mink&lt;/h2&gt;
&lt;p&gt;Assez rapidement on se retrouve à devoir gérer des cas particuliers, par exemple :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-gherkin&quot; data-lang=&quot;gherkin&quot;&gt;Quand je suis à découvert
&amp;lt;p&amp;gt;Alors le bouton &amp;quot;retirer de l&amp;#39;argent&amp;quot; doit être désactivé&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/p&gt;

&lt;p&gt;On sort des cas classiques de Mink. Comment faire ?&lt;/p&gt;

&lt;p&gt;La solution consiste à développer nous même ce comportement :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;/**
 * @Then /^le bouton &amp;quot;([^&amp;quot;]*)&amp;quot; doit être désactivé$/
 */
public function leBoutonDoitEtreDesactive($button)
{
    throw new PendingException();
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;On commence par récupérer notre page à partir de l&apos;objet de session:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;$page = $this-&amp;gt;getSession()-&amp;gt;getPage();&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Ensuite on va récupérer l&apos;élément html concerné. &lt;a href=&quot;http://mink.behat.org/#traverse-the-page-selectors&quot; target=&quot;_blank&quot;&gt;Il existe différente manière de faire cela&lt;/a&gt;. Le plus simple dans notre cas consiste à passer par un des raccourcis de sélection de Mink : &lt;strong&gt;findButton&lt;/strong&gt;(libellé | id | nom)...&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;$element = $page-&amp;gt;findButton($button);&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Si l&apos;élément html n&apos;est pas trouvé, on va lever une exception, sinon on va continuer en faisant une assertion simple : l&apos;élément doit avoir l&apos;attribut &quot;disabled&quot;. Ce qui donne au final :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;$page = $this-&amp;gt;getSession()-&amp;gt;getPage();
$element = $page-&amp;gt;findButton($button);

if (null === $element) {
    throw new Behat\Mink\Exception\ElementNotFoundException(
        $this-&amp;gt;getSession(), &amp;#39;element&amp;#39;, &amp;#39;css&amp;#39;, $button
    );
}

&amp;lt;p&amp;gt;assertEquals(true, $element-&amp;gt;hasAttribute(&amp;#39;disabled&amp;#39;));&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/p&gt;


&lt;p&gt;Au passage, remarquez qu&apos;il s&apos;agit d&apos;une assertion classique de PHPUnit, mais en mode fonction. Pour cela on aura bien entendu ajouté au début de notre fichier :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;require_once &amp;#39;PHPUnit/Autoload.php&amp;#39;;
&amp;lt;p&amp;gt;require_once &amp;#39;PHPUnit/Framework/Assert/Functions.php&amp;#39;;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/p&gt;

&lt;p&gt;Je vous laisse regarder la &lt;a href=&quot;http://mink.behat.org/http://&quot; target=&quot;_blank&quot;&gt;documentation&lt;/a&gt; ou la &lt;a title=&quot;Cheat Sheet Behat&quot; href=&quot;/php/ressources-tutos-php/cheat-sheet-behat&quot; target=&quot;_blank&quot;&gt;feuille d&apos;astuce pour Mink&lt;/a&gt; pour plus d&apos;informations. Sachez juste qu&apos;on peut faire pas mal de chose, comme exécuter du JavaScript &lt;a href=&quot;/php/ressources-tutos-php/communiquer-a-travers-internet&quot;&gt;par exemple&lt;/a&gt; (avec  $session-&gt;evaluateScript() ) ;-) ...&lt;/p&gt;

&lt;h2&gt;Organiser son code&lt;/h2&gt;
&lt;p&gt;Jusqu&apos;ici on a systématiquement mis notre code dans le fichier FeatureContext.php. C&apos;est pas l&apos;idéal : on va très vite se retrouver avec un fichier énorme et imbuvable. Il nous suffit de découper notre contexte en sous-contextes. Tout se fait dans le constructeur du contexte principal :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;class FeatureContext extends BehatContext
{

    public function __construct(array $parameters) {
        $this-&amp;gt;useContext(&amp;#39;mink&amp;#39;, new MinkContext($parameters));
        $this-&amp;gt;useContext(&amp;#39;example1&amp;#39;, new MyExample1Context($parameters));
    }&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;On a donc isolé le contexte de Mink pour en faire un sous-contexte, et on a plus ajouté le nôtre (&apos;example1&apos;).&lt;/p&gt;

&lt;p&gt;L&apos;utilisation des contextes est assez simple. Chaque sous-contexte a un nom (ici &apos;mink&apos; et &apos;behat&apos;), que l&apos;on peut utiliser pour les récupérer :&lt;/p&gt;

&lt;dl&gt;&lt;dt&gt;&lt;strong&gt;getMainContext()&lt;/strong&gt;&lt;/dt&gt;&lt;dd&gt;Récupérer le contexte principal&lt;/dd&gt;&lt;dt&gt;&lt;strong&gt;getSubContext(&apos;nom&apos;)&lt;/strong&gt;&lt;/dt&gt;&lt;dd&gt;Récupérer un sous contexte&lt;/dd&gt;&lt;dt&gt;&lt;strong&gt;getSubcontexts()&lt;/strong&gt;&lt;/dt&gt;&lt;dd&gt;Récupérer la liste des tous les sous-contextes&lt;/dd&gt;&lt;/dl&gt;

&lt;p&gt;Par exemple :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;$session = $this-&amp;gt;getMainContext()-&amp;gt;getSubContext(&amp;#39;mink&amp;#39;)-&amp;gt;getSession();&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;On va donc créer le fichier MyExample1Context.php :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;class MyExample1Context extends BehatContext
{

    /**
     * @Then /^le bouton &amp;quot;([^&amp;quot;]*)&amp;quot; doit être désactivé$/
     */
    public function leBoutonDoitEtreDesactive($button) {
        $session = $this-&amp;gt;getMainContext()-&amp;gt;getSubContext(&amp;#39;mink&amp;#39;)-&amp;gt;getSession();
        $page = $session-&amp;gt;getPage();
        $element = $page-&amp;gt;findButton($button);

        if (null === $element) {
            throw new Behat\Mink\Exception\ElementNotFoundException(
                    $this-&amp;gt;getSession(), &amp;#39;element&amp;#39;, &amp;#39;css&amp;#39;, $button
            );
        }

        assertEquals(true, $element-&amp;gt;hasAttribute(&amp;#39;disabled&amp;#39;));
    }

}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Et voilà, nous voici avec un code découpé et des fichiers plus spécialisés.&lt;/p&gt;

&lt;h2&gt;Les hooks de Behat&lt;/h2&gt;
&lt;p&gt;Comme pour les tests unitaires, il est possible d&apos;exécuter du code à certaines phases du déroulement du tests. Il suffit d&apos;utiliser des annotations :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;/**
 * @BeforeSuite
 */
&amp;lt;p&amp;gt;public static function prepare(SuiteEvent $event)&amp;lt;/p&amp;gt;
{
    // (...)
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;les déclencheurs disponibles sont :&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;BeforeSuite&lt;/li&gt;
	&lt;li&gt;AfterSuite&lt;/li&gt;
	&lt;li&gt;BeforeFeature&lt;/li&gt;
	&lt;li&gt;AfterFeature&lt;/li&gt;
	&lt;li&gt;BeforeScenario&lt;/li&gt;
	&lt;li&gt;AfterScenario&lt;/li&gt;
	&lt;li&gt;BeforeStep&lt;/li&gt;
	&lt;li&gt;AfterStep&lt;/li&gt;
	&lt;li&gt;AfterStep&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Un dessin valant mieux qu&apos;un long discours, le plus simple est de&lt;a href=&quot;http://docs.behat.org/guides/3.hooks.html#behat-event-system&quot; target=&quot;_blank&quot;&gt; regarder ici&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;On voit qu&apos;on a quand même peu de limites avec Mink et Behat. Bien plus, on peut même l&apos;intégrer à une PIC (Jenkins, Hudson...). C&apos;est d&apos;ailleurs ce qu&apos;on verra dans le prochain billet, qui sera le dernier de la série ^^&lt;/p&gt;

=&gt; Juste par curiosité : j&apos;utilise exclusivement Sahi pour mes tests. Beaucoup de monde utilise Selenium ? C&apos;est mieux ? Vous avez des avis ?
</content>
 </entry>
 
 <entry>
   <title>Behat – jour 3 : Tester une application web avec Mink</title>
   <link href="https://blog.lepine.pro/php/behat-jour-3-tester-une-application-web-avec-mink"/>
   <updated>2012-03-19T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/php/behat-jour-3-tester-une-application-web-avec-mink</id>
   <content type="html">&lt;p&gt;On a vu précédemment &lt;a href=&quot;/php/behat-jour-1-comment-tester-son-produit-scrum&quot; title=&quot;Behat – jour 1 : comment tester son produit SCRUM ?&quot;&gt;ce qu&apos;était Behat&lt;/a&gt; et &lt;a title=&quot;Behat – jour 2 : Installation et premiers tests&quot; href=&quot;/php/behat-jour-2-installation-et-premiers-tests&quot;&gt;comment tester une application simple en php avec Behat&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Maintenant allons plus loin et voyons comment tester une application web (à la quelle on accède par un navigateur). Et oui c&apos;est possible ! :-)&lt;/p&gt;
&lt;h2&gt;Behat et ... Mink&lt;/h2&gt;
&lt;p&gt;On l&apos;a dit : Behat permet de tester un produit. Ceci dit, il est rare qu&apos;un client vous demande une application en ligne de commande ; le plus souvent le produit en question va être constitué de pages web. Qu&apos;à cela ne tienne, Behat est très fortement lié à un autre outil : &lt;a href=&quot;http://mink.behat.org/&quot; target=&quot;_blank&quot;&gt;Mink&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Mink c&apos;est quoi ? Et bien &lt;strong&gt;Mink va vous permettre&lt;/strong&gt; :&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;ou bien de &lt;strong&gt;simuler un navigateur&lt;/strong&gt; pour interagir avec votre application (produit)&lt;/li&gt;
	&lt;li&gt;ou bien de &lt;strong&gt;piloter un vrai navigateur&lt;/strong&gt; (Firefox, Chrome...) pour interagir avec le produit&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Voyons comment le lancer en mode &quot;simulation&quot; dans un premier temps.&lt;/p&gt;
&lt;h3&gt;Application de test&lt;/h3&gt;
&lt;p&gt;Pour vous éviter de perdre du temps, je vous propose de télécharger une petite page web toute simple qui va correspondre à notre produit de test :&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;une seule page pour consulter notre compte et ajouter/retirer des sous&lt;/li&gt;
	&lt;li&gt;un formulaire de connexion (pas de mot de passe)&lt;/li&gt;
	&lt;li&gt;les informations sont stockées en session&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ce qui donne ça : &lt;a href=&quot;https://blog.lepine.pro/images/2012-03-screen-app-exemple-mink.jpg&quot;&gt;&lt;img class=&quot;alignnone size-medium wp-image-459&quot; title=&quot;screen-app-exemple-mink&quot; src=&quot;https://blog.lepine.pro/images/2012-03-screen-app-exemple-mink.jpg&quot; alt=&quot;&quot; width=&quot;208&quot; height=&quot;300&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pour télécharger l&apos;application exemple, ça se passe ici : &lt;a href=&quot;https://blog.lepine.pro/images/2012-03-behat-jour3-appli-web1.zip&quot;&gt;behat-jour3-appli-web.zip&lt;/a&gt; (15 Ko).&lt;/p&gt;
&lt;h3&gt;Notre produit web&lt;/h3&gt;
&lt;p&gt;N&apos;oublions pas que le client (product owner) reste le maître de son produit. Il nous fournit donc un fichier de fonctionnalités pour décrire son produit.&lt;/p&gt;

&lt;em&gt;Remarque : la fonctionnalité est décrite en anglais ; bien que très actif, &lt;strong&gt;utiliser behat et mink en français reste aujourd&apos;hui très aventureux à mon goût&lt;/strong&gt;, ne serait-ce qu&apos;à cause des nombreux apostrophes présents dans notre langue &lt;a href=&quot;https://github.com/Behat/Behat/issues/106&quot; target=&quot;_blank&quot;&gt;qui posent des difficultés&lt;/a&gt;.&lt;/em&gt;

&lt;p&gt;Fichier bank.feature :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-gherkin&quot; data-lang=&quot;gherkin&quot;&gt;&amp;lt;p&amp;gt;Feature: Manage a bank account&amp;lt;/p&amp;gt;
  In order to manage my account
  As a logged in user
  I need to be able to add or take mon ey on my account

  Background:
    And I am logged in as &amp;quot;jeanfrancois&amp;quot;
    And I have &amp;quot;50&amp;quot; euro
    And I am on &amp;quot;/&amp;quot;

  Scenario: Check my bank account
    Then I should see &amp;quot;You have 50 euro on your account&amp;quot;

  Scenario Outline: Add money
    Given I have &amp;quot;&amp;lt;initialAmount&amp;gt;&amp;quot; euro
    When I select &amp;quot;&amp;lt;operation&amp;gt;&amp;quot; from &amp;quot;Operation&amp;quot;
    And I fill in &amp;quot;Amount&amp;quot; with &amp;quot;&amp;lt;amount&amp;gt;&amp;quot;
    And I press &amp;quot;Go&amp;quot;
    Then I should see &amp;quot;You have &amp;lt;finalAmount&amp;gt; euro on your account&amp;quot;

    Examples:
    | operation   | initialAmount | amount    | finalAmount   |
    | Add money   | 50            | 10        | 60            |
    | Add money   | 50            | 20        | 70            |
    | Add money   | 50            | 5         | 55            |
    | Add money   | 50            | 0         | 50            |
    | Take money  | 50            | 10        | 40            |
    | Take money  | 50            | 20        | 30            |
    | Take money  | 50            | 30        | 20            |

  Scenario: Overdrafts are not allowed
    Given I have &amp;quot;50&amp;quot; euro
    When I select &amp;quot;Take money&amp;quot; from &amp;quot;Operation&amp;quot;
    And I fill in &amp;quot;Amount&amp;quot; with &amp;quot;60&amp;quot;
    And I press &amp;quot;Go&amp;quot;
    Then I should see &amp;quot;You have 50 euro on your account&amp;quot;
    And I should see &amp;quot;Overdrafts are not allowed&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Nous voilà prêts :-) Notez que les expressions utilisées (I fill in &quot;xxx&quot; with &quot;xx&quot;, etc) ne sont pas anodines, bien au contraire. On va le voir par la suite.&lt;/p&gt;
&lt;h2&gt;Installer Mink&lt;/h2&gt;
&lt;p&gt;Rien de plus simple :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;sudo pear install behat/mink-beta&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Maintenant on va &quot;dire&quot; à Behat que l&apos;on souhaite utiliser Mink pour nos tests. Ouvrez le fichier bootstrap/FeatureContext.php, et remplacez&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;class FeatureContext extends BehatContext {&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;par&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;require_once &amp;#39;mink/autoload.php&amp;#39;;
class FeatureContext extends Behat\Mink\Behat\Context\MinkContext&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;C&apos;est là que la magie s&apos;opère : Mink dispose déjà de nombreuses expressions disponibles pour tester notre produit web ! On va pouvoir le confirmer en faisant un :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;behat -dl --lang=en&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;a href=&quot;https://blog.lepine.pro/images/2012-03-behat-mink-syntaxe.jpg&quot;&gt;&lt;img class=&quot;aligncenter size-medium wp-image-464&quot; title=&quot;Résultat de la commande behat -dl après avoir installé Mink&quot; src=&quot;https://blog.lepine.pro/images/2012-03-behat-mink-syntaxe.jpg&quot; alt=&quot;Résultat de la commande behat -dl après avoir installé Mink&quot; width=&quot;300&quot; height=&quot;278&quot; /&gt;&lt;/a&gt;

&lt;p&gt;Il ne nous reste plus qu&apos;à préparer un petit fichier de configuration, nommé &lt;strong&gt;feature/behat.yml&lt;/strong&gt;, qui sera automatiquement lu par behat :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&amp;lt;p&amp;gt;default:&amp;lt;/p&amp;gt;
  context:
    parameters:
      base_url: http://localhost/mettez/ici/l/adresse/a/tester&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Ici nous indiquons simpelment à Mink la racine de base de notre application, pour éviter de la répéter dans chacun de nos tests.&lt;/p&gt;
&lt;h3&gt;Tester le produit : Mink crée du sens&lt;/h3&gt;
&lt;p&gt;Il est temps de se lancer et d&apos;exécuter en ligne de commande :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;behat&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Miracle ^^ : la majorité des expressions utilisées par notre product owner possède déjà un sens. En tant que développeur, il n&apos;y a que deux expressions pour lesquelles je dois donner du sens :&lt;/p&gt;

&lt;a href=&quot;https://blog.lepine.pro/images/2012-03-mink-resultat-commande1.jpg&quot;&gt;&lt;img class=&quot;aligncenter size-medium wp-image-466&quot; title=&quot;Mink : résultat de la commande 1 : il reste des étapes à définir&quot; src=&quot;https://blog.lepine.pro/images/2012-03-mink-resultat-commande1.jpg&quot; alt=&quot;Mink : résultat de la commande 1 : il reste des étapes à définir&quot; width=&quot;300&quot; height=&quot;187&quot; /&gt;&lt;/a&gt;.

&lt;p&gt;Quand le product owner spécifie &apos;I should see &quot;content&quot;&apos;, Mink va tout seul faire le lien avec l&apos;assertion &quot;le contenu de la page doit comporter le texte &quot;content&quot;.&lt;/p&gt;

&lt;p&gt;Mais bien mieux encore : les champs de mes formulaires ne sont pas reconnus par leur nom ou leur identifiant (name ou id). Non non, &lt;strong&gt;le product owner n&apos;en n&apos;a rien à faire de la structure interne d&apos;une page web ; ce qu&apos;il veut c&apos;est un produit qui à l&apos;écran correspond à ses attentes&lt;/strong&gt;. &lt;strong&gt;Les éléments de formulaires sont reconnus par leur libellé&lt;/strong&gt; (label).&lt;/p&gt;
&lt;blockquote&gt;le product owner n&apos;en a rien à faire de la structure interne d&apos;une page web ; ce qu&apos;il veut c&apos;est un produit qui, à l&apos;écran, correspond à ses attentes&lt;/blockquote&gt;
&lt;p&gt;Bien sûr, Mink est permissif et on peut tout même cibler un lien, bouton, élément de formulaire... par son identifiant ou son nom... peu importe :-)&lt;/p&gt;

&lt;p&gt;Vous trouverez la &lt;a href=&quot;/php/ressources-tutos-php/cheat-sheet-behat&quot; title=&quot;Cheat Sheet Behat&quot;&gt;liste des expressions de base de Mink ici&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;Donner son sens aux expressions non définies&lt;/h2&gt;
&lt;p&gt;Il nous reste donc deux expressions qui n&apos;ont actuellement pas de signification : &quot;Given I am logged in as ...&quot; et &quot;I have x euro&quot;. A nous, développeurs, de les définir dans features/bootstrap/FeatureContext.php.&lt;/p&gt;

&lt;p&gt;Attention, il y a là un &lt;strong&gt;énorme piège&lt;/strong&gt;. Je l&apos;ai vu à chaque fois, le premier réflexe d&apos;un développeur va être de créer un lien entre le code source de l&apos;application et le code disponible dans le fichier FeatureContext.php. Par exemple en créer une instance d&apos;une Zend_Application comme on le ferait pour des tests unitaires d&apos;une application Zend... C&apos;est la dernière chose à faire !&lt;/p&gt;

&lt;p&gt;Non, si le product owner veut un produit web, si Mink nous permet de tester un produit web, continuons de tester un produit web uniquement, et laissons le code de côté :-p .&lt;/p&gt;

&lt;p&gt;Le product owner nous dit qu&apos;il est un utilisateur connecté (&quot;I am logged in as...&quot;). Connectons l&apos;utilisateur au sein de l&apos;application :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;/**
 * @Given /^I am logged in as &amp;quot;([^&amp;quot;]*)&amp;quot;$/
 */
public function iAmLoggedInAs($username)
{
    return array(
        new Step\Given(&amp;#39;I go to &amp;quot;login.php&amp;quot;&amp;#39;)
        ,new Step\When(&amp;quot;I fill in \&amp;quot;My name\&amp;quot; with \&amp;quot;$username\&amp;quot;&amp;quot;)
        ,new Step\When(&amp;#39;I press &amp;quot;Login&amp;quot;&amp;#39;)
    );
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Pensez à ajouter au début du fichier :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;use Behat\Behat\Context\Step;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Comme vous le voyez, on réutilise les étapes utilisables directement dans nos scénarios. Dans 99% des cas cela suffit. &lt;strong&gt;Ce sont les mêmes étapes que le product owner pourrait utiliser dans un fichier de fonctionnalité classique.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ce qui reviendrait à écrire :&lt;/p&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-gherkin&quot; data-lang=&quot;gherkin&quot;&gt;Given I go to &amp;quot;login.php&amp;quot;
When I fill in &amp;quot;My name&amp;quot; with &amp;quot;jeanfrancois&amp;quot;
When I press &amp;quot;Login&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;On fait la même chose pour donner du sens à &apos;I have &quot;50&quot; euro&apos; :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;/**
 * @Given /^I have &amp;quot;([^&amp;quot;]*)&amp;quot; euro$/
 */
public function iHaveEuro($balance) {
    return array(
        new Step\Given(&amp;#39;I go to &amp;quot;/&amp;quot;&amp;#39;)
        , new Step\When(&amp;quot;I fill in \&amp;quot;New balance\&amp;quot; with \&amp;quot;$balance\&amp;quot;&amp;quot;)
        , new Step\When(&amp;#39;I press &amp;quot;Reset&amp;quot;&amp;#39;)
    );
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Ca y est, on est bons :&lt;/p&gt;
&lt;a href=&quot;https://blog.lepine.pro/images/2012-03-mink-resultat-commande2.jpg&quot;&gt;&lt;img class=&quot;aligncenter size-medium wp-image-470&quot; title=&quot;Mink : le produit est conforme&quot; src=&quot;https://blog.lepine.pro/images/2012-03-mink-resultat-commande2.jpg&quot; alt=&quot;Mink : le produit est conforme&quot; width=&quot;234&quot; height=&quot;300&quot; /&gt;&lt;/a&gt;

&lt;p&gt;Le système de test est fiable car il n&apos;est pas assujetti à des modifications de notre code, mais seulement aux modifications de notre produit. Si l&apos;application venait à ne plus correspondre aux souhaits du product owner, celui-ci en serait immédiatement averti.&lt;/p&gt;

&lt;h2&gt;Un vrai navigateur, avec du vrai javascript&lt;/h2&gt;

&lt;p&gt;C&apos;est beau, mais pas très réaliste : quid des applications riches ? Que faire si on a du Javascript et de l&apos;ajax de tous les côtés ? &lt;/p&gt;

&lt;p&gt;Et bien on va continuer d&apos;utiliser Mink, mais cette fois-ci on va piloter un vrai navigateur. Mink permet de piloter n&apos;importe quel navigateur en utilisant le driver de notre choix : &lt;a href=&quot;http://sourceforge.net/projects/sahi/files/&quot; target=&quot;_blank&quot;&gt;Sahi&lt;/a&gt; ou &lt;a href=&quot;http://seleniumhq.org/download/&quot; target=&quot;_blank&quot;&gt;Selenium&lt;/a&gt; (1 ou 2). &lt;/p&gt;

&lt;p&gt;Ayant eu de nombreuses déconvenues avec Selenium par le passé, et n&apos;ayant pas encore eu de souci majeur à ce jour avec Sahi, j&apos;ai une &lt;strong&gt;nette préférence pour Sahi&lt;/strong&gt;. Installons le ensemble :&lt;/p&gt;

&lt;p&gt;L&apos;instalaltion est simple. Téléchargez Sahi depuis http://sourceforge.net/projects/sahi/files/ et exécutez le :&lt;/p&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;wget http://sourceforge.net/projects/sahi/files/latest/download?source=files
&amp;lt;p&amp;gt;java -jar sahi_v35_20110719.jar&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt; Cliquez sur suivant, suivant... c&apos;est pas moi qui vais vous apprendre un installer quelque chose :-D.

&lt;p&gt;Lancez ensuite Sahi (placez vous bien dans le dossier spécifié pour éviter les ennuis) : &lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;cd dossier/installation/bin/&amp;lt;/p&amp;gt;
./sahi.sh &amp;amp;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3&gt;Lier des fonctionnalités à un vrai navigateur&lt;/h3&gt;

&lt;p&gt;Pour utiliser un vrai navigateur à la place de l&apos;émulateur, il suffit d&apos;utiliser le tag @javascript devant le scénario qui le nécessite:&lt;/p&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-gherkin&quot; data-lang=&quot;gherkin&quot;&gt;@javascript
  Scenario: ...&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;ou devant la fonctionnalité toute entière :&lt;/p&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-gherkin&quot; data-lang=&quot;gherkin&quot;&gt;@javascript
&amp;lt;p&amp;gt;Feature: ...&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Ensuite on lance behat comme d&apos;habitude :&lt;/p&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;behat&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Vous devriez à ce stade voir se lancer votre navigateur et voir les pages changer toutes seules : les champs se remplissent, ça clique... Bref, ça marche !&lt;/p&gt;

&lt;p&gt;Alors bien sûr vous allez me dire : c&apos;est lent ! Et alors ? Ca reste toujours infiniment plus rapide que de tester tout ça à la main, et surtout c&apos;est fiable : si une ligne est rouge, le produit n&apos;est pas livrable ; mais si tout est vert, alors c&apos;est que vous êtes dans les clous et que vous avez bien fait votre boulot. De plus le gain de temps de test manuel et d&apos;aller et retours avec le product owner est considérable. Là c&apos;est vraiment le client qui est le maître de son produit.&lt;/p&gt;

&lt;h3&gt;Configurer le navigateur&lt;/h3&gt;

&lt;p&gt;Il nous reste deux ou trois petites choses à voir. En effet, figurez vous que c&apos;est firefox qui est lancé pour les tests, mais que moi je préfererai que ce soit chrome. Pas de problème, modifions le fichier behat.yml :&lt;/p&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;default:
  context:
    parameters:
      base_url: base_url: http://localhost/mettez/ici/l/adresse/a/tester
      browser: chrome&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Il y a pas mal d&apos;options, je vous laisse consulter la &lt;a href=&quot;/php/ressources-tutos-php/cheat-sheet-behat&quot; title=&quot;Cheat Sheet Behat&quot; target=&quot;_blank&quot;&gt;petite cheat sheet&lt;/a&gt; si besoin ;-)&lt;/p&gt;

&lt;p&gt;La prochaine fois on verra comment allez plus loin dans le contrôle du navigateur, en exploitant l&apos;API de Mink, et comment s&apos;organiser pour travailler à plusieurs en créant des sous-contextes personnalisés plutôt que de ranger tout notre code dans un seul fichier. &lt;/p&gt;

&lt;p&gt;Mais en attendant, n&apos;hésitez pas à tester Behat et Mink et surtout à dire ce qu&apos;il en est pour vous :-)&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Behat – jour 2 : Installation et premiers tests</title>
   <link href="https://blog.lepine.pro/php/behat-jour-2-installation-et-premiers-tests"/>
   <updated>2012-03-11T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/php/behat-jour-2-installation-et-premiers-tests</id>
   <content type="html">&lt;p&gt;Maintenant qu&apos;on a vu &lt;a title=&quot;Behat – jour 1 : comment tester son produit SCRUM ?&quot; href=&quot;/php/behat-jour-1-comment-tester-son-produit-scrum&quot;&gt;à quoi sert Behat&lt;/a&gt;, il est temps de passer à la pratique...&lt;/p&gt;

&lt;em&gt;Vous trouverez une archive contenant toutes les sources de ce billet en bas de cette page.&lt;/em&gt;
&lt;h2&gt;Installer Behat&lt;/h2&gt;
&lt;p&gt;Le plus simple à mon goût : passer par pear :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;pear channel-discover pear.symfony.com
&amp;lt;p&amp;gt;pear channel-discover pear.behat.org&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;pear install behat/behat&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/p&gt;

&lt;p&gt;Un petit test pour vérifier que tout s&apos;est bien passé :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;behat --version&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h2&gt;Démarrer un projet&lt;/h2&gt;
&lt;p&gt;Voici la structure initiale de mon exemple :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;mkdir -p  application/library tests/product&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;a href=&quot;https://blog.lepine.pro/images/2012-03-day2-behat-arbo.jpg&quot;&gt;&lt;img class=&quot;alignnone size-full wp-image-417&quot; title=&quot;Arborescence du projet Behat&quot; src=&quot;https://blog.lepine.pro/images/2012-03-day2-behat-arbo.jpg&quot; alt=&quot;Arborescence du projet Behat&quot; width=&quot;152&quot; height=&quot;105&quot; /&gt;&lt;/a&gt;

&lt;p&gt;Placez-vous dans le dossier tests/product, puis tapez :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;behat --init&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Cela a pour effet de créer les dossiers nécessaires à votre projet :&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;&lt;strong&gt;features&lt;/strong&gt; : les fichiers de fonctionnalité *.feature&lt;/li&gt;
	&lt;li&gt;&lt;strong&gt;features/bootstrap&lt;/strong&gt; : les classes nécessaires au fonctionnement des fonctionnalités (contextes)&lt;/li&gt;
	&lt;li&gt;&lt;strong&gt;features/bootstrap/FeatureContext.php&lt;/strong&gt; : le contexte principal&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Première fonctionnalité&lt;/h2&gt;
&lt;p&gt;Il est temps de démarrer. Notre client (product owner) souhaite une application dans laquelle on puisse gérer un compte bancaire : on peut consulter son solde, ajouter des sous, en retirer... et on n&apos;a pas le droit d&apos;être découvert.&lt;/p&gt;

&lt;p&gt;Le fichier de fonctionnalité livré par notre client ressemble à ceci &gt;feature/banque.feature&lt; :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-gherkin&quot; data-lang=&quot;gherkin&quot;&gt;# language: fr
&amp;lt;p&amp;gt;Fonctionnalité: posséder un compte bancaire&amp;lt;/p&amp;gt;
  Afin de gérer les comptes bancaires des utilisateurs
  En tant que client
  Je dois être capable d&amp;#39;effectuer des opérations basique sur mon compte

  Scénario: Avoir un compte bancaire valide
    Etant donné que je suis un nouveau client
    Alors je dois avoir &amp;quot;0&amp;quot; euros sur mon compte

  Scénario: Retirer de l&amp;#39;argent sur mon compte
    Etant donné que je suis un client
    Et que je possède &amp;quot;50&amp;quot; euros sur mon compte
    Quand je retire &amp;quot;10&amp;quot; euros
    Alors je dois avoir &amp;quot;40&amp;quot; euros sur mon compte

  Plan du Scénario: Ajouter de l&amp;#39;argent sur mon compte
    Etant donné que je suis un client
    Et que je possède &amp;quot;&amp;lt;soldeInitial&amp;gt;&amp;quot; euros sur mon compte
    Quand je dépose &amp;quot;&amp;lt;montant&amp;gt;&amp;quot; euros
    Alors je dois avoir &amp;quot;&amp;lt;soldeFinal&amp;gt;&amp;quot; euros sur mon compte

    Exemples:
      | soldeInitial    | montant | soldeFinal |
      | 0               | 10      | 10         |
      | 15              | 5       | 20         |
      | 35              | 5       | 40         |

  Scénario: Interdire les découverts
    Etant donné que je suis un client
    Quand j&amp;#39;essaye de retirer plus d argent que je n en ai sur mon compte
    Alors j&amp;#39;ai un message d erreur &amp;quot;Vous ne pouvez pas être à découvert&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Le product owner sait ce qu&apos;il veut, il nous a même donné des exemples pour mieux nous orienter dans notre développement.&lt;/p&gt;

&lt;p&gt;Notez au passage le commentaire &quot;# language: fr&quot; au début du fichier. Il indique que notre fonctionnalité est décrite en français.&lt;/p&gt;
&lt;h2&gt;Lancer les tests&lt;/h2&gt;
&lt;p&gt;Il va être temps de lancer behat. Placez-vous dans le dossier tests/product/feature, puis tapez simplement :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;behat --lang=fr&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

(&lt;em&gt;N&apos;oubliez pas de préciser la langue, vu qu&apos;on travaille pour l&apos;instant en français&lt;/em&gt;). On va dérouler ensemble le résultat de cette commande :
&lt;h3&gt;Rappel de la fonctionnalité&lt;/h3&gt;
&lt;p&gt;On commence par un rappel de la fonctionnalité et des scénarios testés.&lt;/p&gt;

&lt;a href=&quot;https://blog.lepine.pro/images/2012-03-day2-behat-scenario.jpg&quot;&gt;&lt;img class=&quot;aligncenter size-medium wp-image-426&quot; title=&quot;Fonctionnalité Behat&quot; src=&quot;https://blog.lepine.pro/images/2012-03-day2-behat-scenario.jpg&quot; alt=&quot;Fonctionnalité Behat&quot; width=&quot;251&quot; height=&quot;300&quot; /&gt;&lt;/a&gt;

&lt;p&gt;S&apos;il y avait eu des erreurs, le texte aurait été écrit en &lt;span style=&quot;color: #ff0000;&quot;&gt;rouge&lt;/span&gt;, et si tout avait été ok,&lt;span style=&quot;color: #008000;&quot;&gt; il serait vert&lt;/span&gt;. Là le texte est&lt;span style=&quot;color: #ff9900;&quot;&gt; orange&lt;/span&gt; : il n&apos;a pas encore de signification par rapport à notre produit.&lt;/p&gt;
&lt;h3&gt;Bilan du test&lt;/h3&gt;
&lt;p&gt;On a ensuite des informations sur les tests :&lt;/p&gt;

&lt;a href=&quot;https://blog.lepine.pro/images/2012-03-day2-behat-result1.jpg&quot;&gt;&lt;img class=&quot;aligncenter size-full wp-image-431&quot; title=&quot;Behat - résultats : en attente&quot; src=&quot;https://blog.lepine.pro/images/2012-03-day2-behat-result1.jpg&quot; alt=&quot;Behat - résultats : en attente&quot; width=&quot;242&quot; height=&quot;58&quot; /&gt;&lt;/a&gt;
&lt;h2&gt;Donner du sens aux fonctionnalités&lt;/h2&gt;
&lt;p&gt;Toutes nos étapes sont en attente de définition. En effet, on n&apos;a rien qui fait le lien entre les scénarios (les souhaits du product owner) et notre produit (le code source). C&apos;est là que Behat va être à mon sens magique : il nous fournit le code PHP nécessaire pour créer ce lien, avec pour chaque phrase :&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;l&apos;&lt;strong&gt;annotation&lt;/strong&gt; qui permet de &lt;strong&gt;faire le lien&lt;/strong&gt; (&lt;em&gt;@Given /^que je suis un nouveau client$/&lt;/em&gt;)&lt;/li&gt;
	&lt;li&gt;la &lt;strong&gt;méthode&lt;/strong&gt; a insérer pour &lt;strong&gt;donner du sens&lt;/strong&gt; à cette phrase (&lt;em&gt;public function queJeSuisUnNouveauClient()&lt;/em&gt;)&lt;/li&gt;
	&lt;li&gt;les&lt;strong&gt; valeurs de cas&lt;/strong&gt; du scénario (encadrés par des guillemets), qui sont fournis en &lt;strong&gt;paramètres&lt;/strong&gt; de la méthode&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Voici donc la dernière étape du traitement du résultat de la commande :&lt;/p&gt;

&lt;a href=&quot;https://blog.lepine.pro/images/2012-03-day2-behat-result3.jpg&quot;&gt;&lt;img class=&quot;aligncenter size-medium wp-image-435&quot; title=&quot;Behat : donner du sens aux fonctionnalités&quot; src=&quot;https://blog.lepine.pro/images/2012-03-day2-behat-result3.jpg&quot; alt=&quot;Behat : donner du sens aux fonctionnalités&quot; width=&quot;300&quot; height=&quot;103&quot; /&gt;&lt;/a&gt;

&lt;p&gt;Behat nous fourni le code a copier-coller vers la classe qui gère notre contexte principal, à savoir la classe &lt;strong&gt;FeatureContext&lt;/strong&gt; contenue dans /features/bootstrap/FeatureContext.php. Allons -y : copiez le code dans le fichier &lt;FeatureContext.php&gt;.&lt;/p&gt;

&lt;p&gt;Vous pouvez constater que chaque méthode lance une exception de type &lt;strong&gt;PendingException&lt;/strong&gt;. Cela signifie qu&apos;il va falloir modifier ces méthodes pour les relier à notre application.&lt;/p&gt;

&lt;p&gt;Pour vous simplifier la vie, je vous propose de télécharger directement le code nécessaire au bon fonctionnement de ces tests :&lt;a href=&quot;https://blog.lepine.pro/images/2012-03-Account.php_.zip&quot;&gt; Account.php&lt;/a&gt;, à placer dans application/library. Sinon libre à vous d&apos;écrire le code qui correspondra à l&apos;application ; en soit, peu importe le code, ce qui nous intéresse c&apos;est le produit ;-)&lt;/p&gt;

&lt;p&gt;Nous allons maintenant convertir le texte (langue naturelle) en code (source) :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;!--?php use Behat\Behat\Context\ClosuredContextInterface,     Behat\Behat\Context\TranslatedContextInterface,     Behat\Behat\Context\BehatContext,     Behat\Behat\Exception\PendingException; use Behat\Gherkin\Node\PyStringNode,     Behat\Gherkin\Node\TableNode; require_once &amp;#39;PHPUnit/Autoload.php&amp;#39;; require_once &amp;#39;PHPUnit/Framework/Assert/Functions.php&amp;#39;; require_once __DIR__ . &amp;#39;/../../../../application/library/Account.php&amp;#39;; use \MyApp\Account as Account; /**  * Features context.  */ class FeatureContext extends BehatContext {     /**      * Testes account      *      * @var \MyApp\Account      */     private $_account;     /**      * Contains the last exception      *      * @var \Exception      */     private $_lastException;     /**      * @Given /^que je suis un nouveau client$/      */     public function queJeSuisUnNouveauClient() {         $this---&amp;gt;_account = new Account;
    }

    /**
     * @Then /^je dois avoir &amp;quot;([^&amp;quot;]*)&amp;quot; euros sur mon compte$/
     */
    public function jeDoisAvoirEurosSurMonCompte($balance) {
        assertEquals($balance, $this-&amp;gt;_account-&amp;gt;getBalance());
    }

    /**
     * @Given /^que je suis un client$/
     */
    public function queJeSuisUnClient() {
        if(is_null($this-&amp;gt;_account)) {
            $this-&amp;gt;_account = new Account;
        }
    }

    /**
     * @Given /^que je possède &amp;quot;([^&amp;quot;]*)&amp;quot; euros sur mon compte$/
     */
    public function queJePossedeEurosSurMonCompte($balance) {
        $this-&amp;gt;_account-&amp;gt;setBalance($balance);
    }

    /**
     * @Given /^je retire &amp;quot;([^&amp;quot;]*)&amp;quot; euros$/
     */
    public function jeRetireEuros($amount) {
        $this-&amp;gt;_account-&amp;gt;takeMoney($amount);
    }

    /**
     * @Given /^je dépose &amp;quot;([^&amp;quot;]*)&amp;quot; euros$/
     */
    public function jeDeposeEuros($amount) {
        $this-&amp;gt;_account-&amp;gt;addMoney($amount);
    }

    /**
     * @Given /^j\&amp;#39;essaye de retirer plus d argent que je n en ai sur mon compte$/
     */
    public function jEssayeDeRetirerPlusDArgentQueJeNEnAiSurMonCompte() {
        try {
            $this-&amp;gt;_account-&amp;gt;setBalance(50);
        $this-&amp;gt;_account-&amp;gt;takeMoney(100);
        } catch (\Exception $e) {
            $this-&amp;gt;_lastException = $e;
        }
    }

    /**
     * @Given /^j\&amp;#39;ai un message d erreur &amp;quot;([^&amp;quot;]*)&amp;quot;$/
     */
    public function jAiUnMessageDErreur($message) {
        assertEquals($message, $this-&amp;gt;_lastException-&amp;gt;getMessage());
    }

}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Vous constaterez que :&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;on utilise PHPUnit en mode &quot;fonction&quot; pour nos assertions&lt;/li&gt;
	&lt;li&gt;chaque méthode correspond à une phase d&apos;un scénario&lt;/li&gt;
	&lt;li&gt;on fait ce qu&apos;on veut à l&apos;intérieur de notre contexte ^^&lt;/li&gt;
	&lt;li&gt;c&apos;est simple : il ne faut même pas 3 minutes pour écrire ce code&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;L&apos;heure de vérité&lt;/h2&gt;
&lt;p&gt;Le suspense est à son comble : notre code correspond t--il au souhait du client quant à son produit ?&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;behat --lang=fr&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;a href=&quot;https://blog.lepine.pro/images/2012-03-behat-product-valid.jpg&quot;&gt;&lt;img class=&quot;aligncenter size-medium wp-image-444&quot; title=&quot;Behat : le produit est conforme&quot; src=&quot;https://blog.lepine.pro/images/2012-03-behat-product-valid.jpg&quot; alt=&quot;Behat : le produit est conforme&quot; width=&quot;300&quot; height=&quot;285&quot; /&gt;&lt;/a&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Si besoin voici une archive contenant l&apos;ensemble de ce projet : &lt;a href=&quot;https://blog.lepine.pro/images/2012-03-behat-decouverte-jour2.zip&quot;&gt;Découverte de behat - jour 2.zip&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Notre client (product owner) peut désormais s&apos;assurer à tout moment que son produit est valide.Bien plus, il va pouvoir ajuster son produit au fur et à mesure des sprints en vous décrivant clairement son besoin : fini les specs de 500 pages, c&apos;est le product owner qui est maître de son produit ! Et cela grâce à Behat.&lt;/p&gt;

&lt;p&gt;La prochaine fois on verra comment mieux organiser son code : sous-contextes, configuration... et surtout comment tester une application web, grâce à Mink. mais en attendant, toute remarque / commentaire est le bienvenu ;-)&lt;/p&gt;

&lt;p&gt;Je vous rappelle qu&apos;une &lt;a title=&quot;Cheat Sheet Behat&quot; href=&quot;/php/ressources-tutos-php/cheat-sheet-behat&quot;&gt;cheat sheet pour behat&lt;/a&gt; est disponible.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Cheat Sheet Behat</title>
   <link href="https://blog.lepine.pro/php/ressources-tutos-php/cheat-sheet-behat"/>
   <updated>2012-03-06T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/php/ressources-tutos-php/cheat-sheet-behat</id>
   <content type="html">&lt;p&gt;Je vous propose une petite feuille d&apos;astuces (cheat sheet) pour Behat et Mink. N&apos;hésitez pas pas à vous en servir et vous en resservir :-)&lt;/p&gt;

&lt;p&gt;PDF : &lt;strong&gt;&lt;a href=&quot;https://blog.lepine.pro/images/2012-04-behat-cheat-sheet1.pdf&quot;&gt;Cheat Sheet Behat and Mink&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;strong&gt;Edit&lt;/strong&gt; : The English version : PDF (en) : &lt;a href=&apos;https://blog.lepine.pro/images/2012-03-behat-cheat-sheet-en.pdf&apos;&gt;English Behat Cheat Sheet&lt;/a&gt;

&lt;p&gt;Image :&lt;/p&gt;
&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;&lt;a href=&quot;https://blog.lepine.pro/images/2012-04-behat-cheat-sheet.jpg&quot;&gt;&lt;img class=&quot;alignnone size-medium wp-image-408&quot; title=&quot;Cheat Sheet Behat Mink - page 1&quot; src=&quot;https://blog.lepine.pro/images/2012-04-behat-cheat-sheet.jpg&quot; alt=&quot;Cheat Sheet Behat Mink - page 1&quot; width=&quot;212&quot; height=&quot;300&quot; /&gt;&lt;/a&gt;
&lt;p&gt;Page 1&lt;/td&gt;&lt;/p&gt;
&lt;td align=&quot;center&quot;&gt;&lt;a href=&quot;https://blog.lepine.pro/images/2012-04-behat-cheat-sheet-mink.jpg&quot;&gt;&lt;img class=&quot;alignnone size-medium wp-image-409&quot; title=&quot;Cheat Sheet Behat Mink - page 2&quot; src=&quot;https://blog.lepine.pro/images/2012-04-behat-cheat-sheet-mink.jpg&quot; alt=&quot;Cheat Sheet Behat Mink - page 2&quot; width=&quot;212&quot; height=&quot;300&quot; /&gt;&lt;/a&gt;
&lt;p&gt;Page 2&lt;/td&gt;&lt;/p&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Au programme :&lt;/p&gt;

&lt;p&gt;Page 1 :&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;Utilisation de la ligne de commande&lt;/li&gt;
	&lt;li&gt;Behat en 2 mots&lt;/li&gt;
	&lt;li&gt;Les fichiers de fonctionnalités et les scénarios&lt;/li&gt;
	&lt;li&gt;Insérer des exemples dans les scénarios&lt;/li&gt;
	&lt;li&gt;Naviguer avec Mink&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Page 2 :&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;Infos sur la Session&lt;/li&gt;
	&lt;li&gt;Infos sur les éléments&lt;/li&gt;
	&lt;li&gt;Infos sur les noeuds HTML&lt;/li&gt;
	&lt;li&gt;Configuration par défaut (behat.yml)&lt;/li&gt;
	&lt;li&gt;Drivers disponibles pour Mink&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pour découvrir Behat, je vous invite à consulter le billet &lt;a title=&quot;Behat – jour 1 : comment tester son produit SCRUM ?&quot; href=&quot;/php/behat-jour-1-comment-tester-son-produit-scrum&quot;&gt;Jour 1 - Comprendre et utiliser Behat pour tester un produit Scrum ?&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tout commentaire est le bienvenu (ce qui manque, n&apos;est pas clair, etc...). Merci de vos retours !&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Behat - jour 1 : comment tester son produit SCRUM ?</title>
   <link href="https://blog.lepine.pro/php/behat-jour-1-comment-tester-son-produit-scrum"/>
   <updated>2012-03-02T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/php/behat-jour-1-comment-tester-son-produit-scrum</id>
   <content type="html">Ce billet démarre une série consacrée à cet outil que je trouve génial : Behat. Désolé pour la longueur, mais le sujet est suffisamment intéressant à mon goût pour être creusé comme il faut. :-)

Pour ce premier jour avec Behat, on va se placer du côté du client uniquement. la partie développeur viendra dans le prochain billet. Nous allons donc découvrir Behat pas à pas.

&lt;p&gt;Behat, c&apos;est quoi ? Pour le comprendre, il faut d&apos;abord faire un tour du côté des méthodes agiles, et particulièrement du côté de SCRUM&lt;/p&gt;
&lt;h2&gt;Scrum - la notion de produit et de test d&apos;acceptation&lt;/h2&gt;
&lt;em&gt;J&apos;abstrais volontairement une partie des aspects de Scrum pour me consacrer uniquement aux points déterminants pour comprendre les enjeux de Behat. Vous trouverez plus de détails sur Scrum &lt;a href=&quot;http://fr.wikipedia.org/wiki/Scrum_%28m%C3%A9thode%29&quot;&gt;ici&lt;/a&gt;).&lt;/em&gt;

&lt;p&gt;Scrum, c&apos;est avant tout un état d&apos;esprit, mais c&apos;est aussi un exemple d&apos;organisation. Imaginez un peintre à qui l&apos;on demande de peindre Mona Lisa. Si on fait l&apos;analogie avec le développement, la manière classique consiste à peindre le tableau de haut en bas, comme le ferait une imprimante. Le tableau ne pourra être livré qu&apos;une fois la peinture entièrement achevée, de bas en haut.&lt;/p&gt;

&lt;em&gt;(les images proviennent de &lt;a href=&quot;http://www.agileproductdesign.com/blog/dont_know_what_i_want.html&quot;&gt;Jeff Patton&lt;/a&gt;)&lt;/em&gt;

&lt;span style=&quot;color: #ff0000;&quot;&gt;&lt;strong&gt;&lt;a href=&quot;https://blog.lepine.pro/images/2012-03-incrementing.jpg&quot;&gt;&lt;img class=&quot;aligncenter size-medium wp-image-366&quot; title=&quot;Le développement incrémental&quot; src=&quot;https://blog.lepine.pro/images/2012-03-incrementing.jpg&quot; alt=&quot;Le développement incrémental - mona lisa&quot; width=&quot;300&quot; height=&quot;117&quot; /&gt;&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;

&lt;p&gt;Au contraire, en Scrum, on va commencer par esquisser les traits de Mona Lisa, puis on va demander au client si ça lui convient. Puis on va affiner le trait et le dessin. On redemande au client si ça lui convient. Enfin on va pouvoir ajouter des touches de couleurs, etc. C&apos;est ce qu&apos;on appelle un &lt;strong&gt;développement itératif&lt;/strong&gt;. A chaque itération (&quot;&lt;strong&gt;release&lt;/strong&gt;&quot;), le client valide le produit fourni et décide ou non de continuer. S&apos;il s&apos;arrête, le produit est simplement moins riche qu&apos;escompté, mais il est livrable et &lt;strong&gt;utilisable par le client&lt;/strong&gt;.&lt;/p&gt;

&lt;span style=&quot;color: #ff0000;&quot;&gt;&lt;strong&gt;&lt;a href=&quot;https://blog.lepine.pro/images/2012-03-iterating.jpg&quot;&gt;&lt;img class=&quot;aligncenter size-medium wp-image-367&quot; title=&quot;Le développement itératif&quot; src=&quot;https://blog.lepine.pro/images/2012-03-iterating.jpg&quot; alt=&quot;Le développement itératif - mona lisa&quot; width=&quot;300&quot; height=&quot;121&quot; /&gt;&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;

&lt;p&gt;En Scrum, le client est donc le maître du produit qu&apos;on lui fourni : il a la main dessus et doit participer activement a sa description fonctionnelle. On dit alors qu&apos;il est le propriétaire du produit (&lt;strong&gt;Product Owner&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;Pour décrire, justement, son produit, le client fourni généralement des spécifications fonctionnelles détaillées (SFD - qui, au passage, ne sont en général jamais lues car trop longues). Cette fois le client va au contraire décrire des &lt;strong&gt;cas d&apos;utilisation&lt;/strong&gt; de son produit. Pour savoir si le produit livré correspond à son besoin, il lui suffira de s&apos;assurer que les cas d&apos;utilisation sont respectés. Ces cas d&apos;utilisation, garants du produit, sont regroupés dans tests d&apos;acceptations du produit.&lt;/p&gt;

&lt;p&gt;A chaque itération (release), &lt;strong&gt;le produit livré est donc comparé au produit souhaité&lt;/strong&gt; au moyen de ces &lt;strong&gt;tests d&apos;acceptation&lt;/strong&gt;. C&apos;est la garantie :&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;de satisfaire la demande initiale du client&lt;/li&gt;
	&lt;li&gt;d&apos;éviter les quiproquos (&quot;mais moi je voulais pas ça&quot;)&lt;/li&gt;
	&lt;li&gt;d&apos;éviter les spécifications trop volumineuses&lt;/li&gt;
	&lt;li&gt;d&apos;éviter l&apos;ajout de nouvelles fonctionnalités par le client au fur et à mesure du développement (mais il pourra ajouter des fonctionnalités lors d&apos;une prochaine release)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Behat dans tout ça ?&lt;/h2&gt;
&lt;p&gt;Behat, c&apos;est l&apos;outil PHP qui va permettre de&lt;strong&gt; faire le lien entre ces tests d&apos;acceptation&lt;/strong&gt;, écrits par le Product Owner,&lt;strong&gt; et le produit&lt;/strong&gt; que moi, développeur, je lui livre. Bien plus,&lt;strong&gt; il permet de lancer ces tests d&apos;acceptation de manière automatisée&lt;/strong&gt; sur le produit et d&apos;en fournir un rapport lisible par n&apos;importe quel client. A chaque release, en un coup d&apos;oeil, le client voit si ce qu&apos;on lui a livré correspond à son besoin.&lt;/p&gt;

&lt;p&gt;Attention ! &lt;strong&gt;Behat ne se substitue pas aux tests unitaires&lt;/strong&gt;. Non, au contraire. Ce qu&apos;il faut se dire c&apos;est que&lt;span style=&quot;text-decoration: underline;&quot;&gt; le client n&apos;en a rien à faire de savoir  que le code est de bonne qualité&lt;/span&gt; ; ce qu&apos;il veut c&apos;est un produit fini, fonctionnel, conforme à ses souhaits initiaux. Le test unitaire, lui, va nous permettre de nous assurer de l&apos;intégrité du code pour des fonctionnalités sensibles ou complexes. Il ne teste pas le comportement du produit (&lt;span style=&quot;text-decoration: underline;&quot;&gt;mais le test unitaire reste cependant indispensable&lt;/span&gt;)&lt;/p&gt;

&lt;p&gt;Le développeur a en effet souvent tendance à détourner ses tests unitaires de leur fonction initiale (tester du code), pour tester des scénarios (par exemple qu&apos;un Controlleur a le comportement attendu). Ce n&apos;est pas aux outils de tests unitaires de faire cela. Heureusement, Behat est là pour ça ;-) !&lt;/p&gt;
&lt;h2&gt;Comment ça marche ?&lt;/h2&gt;
&lt;p&gt;Bon, c&apos;est bien, mais plus concrètement ça marche comment ?&lt;/p&gt;

&lt;p&gt;Et bien le Product Owner va rédiger ses tests d&apos;acceptation en langue naturelle. Chaque fonctionnalité va être écrite dans un &lt;strong&gt;&lt;fichier nom_de_la_fonctionnalite.feature&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Il commencer par une description de sa &lt;strong&gt;fonctionnalité&lt;/strong&gt; :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-gherkin&quot; data-lang=&quot;gherkin&quot;&gt;Fonctionnalité: Avoir un compte bancaire
  Afin d&amp;#39;offrir aux utilisateurs la possibilité d&amp;#39;avoir un compte bancaire
  Etant donné que je suis inscrit
  Je dois être capable d&amp;#39;ajouter ou de retirer de l&amp;#39;argent sur mon compte&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Cette description permet de comprendre la fonctionnalité de manière générale. Ensuite il va fournir des cas d&apos;utilisation qui vont décrire cette fonctionnalité. On parle alors de &quot;&lt;strong&gt;Scénario&lt;/strong&gt;&quot;. Chaque scénario est défini par :&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;un contexte (étant donné que)&lt;/li&gt;
	&lt;li&gt;des événements déclencheurs (quand)&lt;/li&gt;
	&lt;li&gt;un résultat attendu (alors)&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-gherkin&quot; data-lang=&quot;gherkin&quot;&gt;Scénario:
  Etant donné que je suis un utilisateur connecté
  Et que j&amp;#39;ai un compte bancaire
  Et que le solde de mon compte est de &amp;quot;10&amp;quot; euros
  Quand j&amp;#39;ajoute &amp;quot;5&amp;quot; euros sur mon compte
  Alors mon solde doit être de &amp;quot;15&amp;quot; euros&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Il va pouvoir combiner les scénarios :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-gherkin&quot; data-lang=&quot;gherkin&quot;&gt;Scénario:
  Etant donné que j&amp;#39;ai un compte bancaire
  Et que le solde de mon compte est de &amp;quot;10&amp;quot; euros
  Quand j&amp;#39;ajoute &amp;quot;5&amp;quot; euros sur mon compte
  Alors mon solde doit être de &amp;quot;15&amp;quot; euros

&amp;lt;p&amp;gt;Scénario:&amp;lt;/p&amp;gt;
  Etant donné que j&amp;#39;ai un compte bancaire
  Et que le solde de mon compte est de &amp;quot;10&amp;quot; euros
  Quand je retire &amp;quot;50&amp;quot; euros sur mon compte
  Alors je dois avoir le message d&amp;#39;erreur &amp;quot;vous n&amp;#39;avez pas le droit d&amp;#39;être à découvert&amp;quot;
  Et mon solde doit être de &amp;quot;10&amp;quot; euros&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Quand je lancerai Behat, si mon application fonctionne correctement, ce scénario va être valide et donc coloré en vert à l&apos;écran.Sinon il sera rouge.&lt;/p&gt;

&lt;p&gt;Mieux encore, le Product Owner va pouvoir insérer des exemples. Behat va automatiquement lancer ces tests pour chaque exemple fourni :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-gherkin&quot; data-lang=&quot;gherkin&quot;&gt;Scénario Outline:
  Etant donné que j&amp;#39;ai un compte bancaire
  Et que le solde de mon compte est de &amp;quot;&amp;quot; euros
  Quand j&amp;#39;ajoute &amp;quot;&amp;quot; euros sur mon compte
  Alors mon solde doit être de &amp;quot;&amp;quot; euros

  Examples:
    | soldeInitial | montant | soldeFinal |
    | 5            | 10      | 15         |
    | 20           | 20      | 40         |
    | 20           | 7       | 27         |
    | 0            | 10      | 10         |&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Pas mal non ? Je vais avoir un rapport visuel (une page web par exemple) de l&apos;état de mon produit par rapport à ces tests :&lt;/p&gt;

&lt;a href=&quot;https://blog.lepine.pro/images/2012-03-behat-report1.jpg&quot;&gt;&lt;img class=&quot;aligncenter size-medium wp-image-375&quot; title=&quot;Exemple de rapport de test d&apos;acceptation avec Behat&quot; src=&quot;https://blog.lepine.pro/images/2012-03-behat-report1.jpg&quot; alt=&quot;Exemple de rapport de test d&apos;acceptation avec Behat&quot; width=&quot;300&quot; height=&quot;263&quot; /&gt;&lt;/a&gt;

&lt;p&gt;Maintenant vous avez compris le principe du test d&apos;acceptation. Vous vous êtes mis dans la peau d&apos;un Product Owner qui rédige ses tests d&apos;acceptation. Si vous êtes Product Owner, vous n&apos;avez pas besoin d&apos;aller plus loin. Si vous êtes développeur ou Scrum Master, on verra ensemble le versant technique de Behat dans le prochain Billet ;-)&lt;/p&gt;

&lt;p&gt;Edit: le jour 2 est prêt : &lt;a href=&quot;/php/behat-jour-2-installation-et-premiers-tests&quot; title=&quot;Behat – jour 2 : Installation et premiers tests&quot;&gt;Behat – jour 2 : Installation et premiers tests&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Atelier PHP sur Orléans - Tester son code / produit</title>
   <link href="https://blog.lepine.pro/php/actus-php/atelier-php-sur-orleans-tester-son-code-produit"/>
   <updated>2012-02-29T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/php/actus-php/atelier-php-sur-orleans-tester-son-code-produit</id>
   <content type="html">&lt;p&gt;Comme chaque premier jeudi du mois, demain se tiendra un atelier afup à &lt;strong&gt;Orléans&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Au programme :&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;le &lt;strong&gt;développement piloté par les test&lt;/strong&gt;s (TDD)&lt;/li&gt;
	&lt;li&gt;tester son code avec &lt;a href=&quot;http://www.phpunit.de/&quot; target=&quot;_blank&quot;&gt;&lt;strong&gt;phpUnit&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;tester son code avec &lt;a href=&quot;https://github.com/mageekguy/atoum&quot; target=&quot;_blank&quot;&gt;&lt;strong&gt;Atoum&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;tester son produit avec &lt;a href=&quot;http://behat.org/&quot; target=&quot;_blank&quot;&gt;&lt;strong&gt;Behat&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;a href=&quot;http://afup.org/pages/site/?route=actualites/518/tour-dhorizon-des-pratiques-et-outils-de-tests-en-php-a-orleans&quot;&gt;Les infos nécessaires sont ici&lt;/a&gt;

&lt;p&gt;Je n&apos;ai pas prévu de slides : uniquement du code ;-) . Nous échangerons ensuite sur le thème du test en général et sur les outils présentés...&lt;/p&gt;

&lt;p&gt;Tout le monde est le bienvenu : membres et non-membres de l&apos;AFUP, développeur confirmé ou non.&lt;/p&gt;

&lt;p&gt;A demain !&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Utilisez un moteur javascript en PHP et faites exploser vos perfs</title>
   <link href="https://blog.lepine.pro/php/utilisez-un-moteur-javascript-en-php-et-faites-exploser-vos-perfs"/>
   <updated>2012-02-25T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/php/utilisez-un-moteur-javascript-en-php-et-faites-exploser-vos-perfs</id>
   <content type="html">&lt;p&gt;Bon, je l&apos;admets, ce titre est un brin accrocheur et trolleur :-) . Ceci dit, je viens de tomber sur Twitter sur un package PECL que je ne connaissais pas et qui à mon avis ouvre des perspectives très intéressantes, notamment pour améliorer les performances de certaines fonctionnalités chronophage de PHP.&lt;/p&gt;

&lt;p&gt;Sur certains de mes tests (&lt;strong&gt;voir plus bas&lt;/strong&gt;), le résultat est juste... 300 fois plus performant !&lt;/p&gt;

&lt;p&gt;Ce package, &lt;a title=&quot;V8JS&quot; href=&quot;http://pecl.php.net/package/v8js&quot; target=&quot;_blank&quot;&gt;V8JS&lt;/a&gt;, permet d&apos;utiliser dans PHP le célèbre &lt;a title=&quot;V8 javascript Engine&quot; href=&quot;http://code.google.com/intl/fr/apis/v8/intro.html&quot; target=&quot;_blank&quot;&gt;V8 JavaScript Engine&lt;/a&gt;. Ce moteur, développé par Google et très performant, est le moteur javaScript de Chrome.&lt;/p&gt;
&lt;h2&gt;Installation du V8 JavaScript Engine&lt;/h2&gt;
&lt;p&gt;On va commencer par installer le moteur en lui-même :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;sudo apt-get install libv8-dev
&amp;lt;p&amp;gt;sudo apt-get install libv8-3.1.8.22&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/p&gt;
&lt;h2&gt;Installation du package pear&lt;/h2&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;sudo pecl install v8js-beta php_ini=/chemin/vers/le/php.ini&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Puis modifier le php.ini pour inclure le module v8js.so&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ini&quot; data-lang=&quot;ini&quot;&gt;[v8js]
&amp;lt;p&amp;gt;extension=/chemin/du/module/v8js.so&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/p&gt;

&lt;p&gt;Vous pouvez déplacer le module v8js.so dans le dossier des modules de PHP, ce qui permet d&apos;avoir plus simplement :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&amp;lt;p&amp;gt;extension=v8js.so&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/p&gt;

&lt;p&gt;Un petit phpinfo() pour vérifier :&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blog.lepine.pro/images/2012-02-moteur-javascript-php.jpg&quot;&gt;&lt;img class=&quot;aligncenter  wp-image-317&quot; title=&quot;Moteur Javascript V8 en PHP&quot; src=&quot;https://blog.lepine.pro/images/2012-02-moteur-javascript-php.jpg&quot; alt=&quot;phpinfo du moteur Javascript V8 en PHP&quot; width=&quot;497&quot; height=&quot;192&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;Utiliser V8JS&lt;/h2&gt;
&lt;p&gt;les choses sérieuses commencent ! Examinons la &lt;a href=&quot;http://www.php.net/manual/en/book.v8js.php&quot; target=&quot;_blank&quot;&gt;documentatio&lt;/a&gt;n. Plutôt simple : on a une classe &lt;a href=&quot;http://www.php.net/manual/en/class.v8js.php&quot;&gt;V8Js&lt;/a&gt;, et une méthode &lt;a href=&quot;http://www.php.net/manual/en/v8js.executestring.php&quot;&gt;executeString()&lt;/a&gt; pour appeler le moteur :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;$v8 = new V8Js();
$JS = &amp;lt;&amp;lt;&amp;lt;EOT
function car(){
}
var myCar = new car();
&amp;lt;p&amp;gt;myCar.color = &amp;#39;green&amp;#39;;&amp;lt;/p&amp;gt;
print(myCar.color);
&amp;lt;p&amp;gt;EOT;&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;try {&amp;lt;/p&amp;gt;
  $v8-&amp;gt;executeString($JS);
} catch (V8JsException $e) {
  var_dump($e);
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;On a donc utilisé le moteur javascript pour :&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;déclarer une classe&lt;/li&gt;
	&lt;li&gt;instancer un objet&lt;/li&gt;
	&lt;li&gt;ajouter un attribut à cette instance&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Les performances&lt;/h2&gt;
&lt;p&gt;C&apos;est là qu&apos;on tombe dans des choses intéressantes.&lt;/p&gt;

&lt;p&gt;Parenthèse :&lt;/p&gt;
&lt;em&gt;Attention, comme tout bench, ces mesures sont à remettre dans leur cadre. ici je teste un type de comportement, dans un contexte donné, sur ma machine. De plus c&apos;est orienté, j&apos;ai délibérément choisi quelque chose pour lequel JavaScript excelle.&lt;/em&gt;

&lt;p&gt;Bref, en voyant ça, je me suis dit : tiens, et si je testais un peu les performances ? J&apos;ai alors cherché un comportement pour lequel JavaScript est particulièrement performant, et qui existe également en PHP. J&apos;ai tout de suite pensé aux fonctions array_reduce / reduce. Voici deux codes qui me semblent comparables.&lt;/p&gt;

&lt;p&gt;Voici le premier, en PHP pur :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;&amp;lt;?php
// bench
$start = microtime(true);

//
// Test
$myArray = array();
for ($i = 0; $i &amp;lt; 100000; $i++) {
    array_push($myArray, $i);
}

function rsum($v, $w) {
    $v += $w;
    return $v;
}

$r = array_reduce($myArray, &amp;quot;rsum&amp;quot;);

//
// Bench
$duration = microtime(true) - $start;
&amp;lt;p&amp;gt;var_dump($duration);&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/p&gt;

&lt;p&gt;Dans l&apos;autre, on reporte les calculs sur le moteur JavaScript :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;//
// bench
$start = microtime(true);

$js = &amp;lt;&amp;lt;&amp;lt;EOT
&amp;lt;p&amp;gt;var i;&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;var myArray =  [];&amp;lt;/p&amp;gt;
for (i = 0; i &amp;lt; 100000; i++) {
    myArray.push(i);
}

function rsum(v, w) {
    v += w;
    return v;
}

var r = myArray.reduce(rsum);
print(r);
&amp;lt;p&amp;gt;EOT;&amp;lt;/p&amp;gt;


//
// Test
$v8 = new V8Js();
&amp;lt;p&amp;gt;try {&amp;lt;/p&amp;gt;
  $v8-&amp;gt;executeString($js);
} catch (V8JsException $e) {
  var_dump($e);
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2&gt;les résultats&lt;/h2&gt;
&lt;p&gt;Voici ce que j&apos;obtiens :&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;&lt;strong&gt;en pur PHP : 2.5989&lt;/strong&gt;&lt;/li&gt;
	&lt;li&gt;&lt;strong&gt;en reportant sur le moteur javascript : 0.0085&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bref, comme je le disais plus haut : un sacré écart !&lt;/p&gt;
&lt;h2&gt;Conclusion sur le moteur V8 en PHP&lt;/h2&gt;
&lt;p&gt;je n&apos;ai aucun recul sur cette utilisation du moteur V8 au sein de PHP. Le package est d&apos;ailleurs en bétâ. Cependant, je pense que c&apos;est une piste sérieuse si on cherche à reporter certains traitements (particulièrement des calculs) vers JavaScript...&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Le point sur les limites du typage de PHP</title>
   <link href="https://blog.lepine.pro/php/le-point-sur-les-limites-du-typage-de-php"/>
   <updated>2012-02-20T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/php/le-point-sur-les-limites-du-typage-de-php</id>
   <content type="html">&lt;h2&gt;Les limites&lt;/h2&gt;
&lt;p&gt;PHP a ceci de particulier qu&apos;il est est un &lt;strong&gt;langage de typage faible&lt;/strong&gt; (le type des variables peut changer en cours de route), mais qu&apos;il &lt;strong&gt;autorise un typage fort partiel des paramètres de fonctions&lt;/strong&gt; pour ce qui concerne les objets et les tableaux. On pourra ainsi écrire :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;function test(array $argument) {
    (...)
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&amp;nbsp;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;function test(monObjet $argument) {
    (...)
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;mais par contre il n&apos;est &lt;strong&gt;pas possible de typer les paramètres de méthodes pour les types scalaires&lt;/strong&gt; (entier, chaînes de caractères...)&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;function test(string $argument) {
    // qui a dit que PHP ne savait pas être drôle ?
    // Argument 1 passed to test() must be an instance of string, string given
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Pourtant, ça semble bien souvent manquer : combien de fois vois t-on des tests de type dans un code ?&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;function test($argument) {
    if(!is_string($argument)) {
        throw new InvalidArgumentException(&amp;amp;amp;quot;eh ! on voulait une chaîne !&amp;amp;amp;quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Bref, on se retrouve avec du code pas vraiment utile (qui n&apos;est ni métier ni applicatif), qui, bien souvent, nuit à la lecture et à la bonne compréhension des sources.&lt;/p&gt;
&lt;h2&gt;Les &lt;strike&gt;solutions&lt;/strike&gt; pistes&lt;/h2&gt;
&lt;p&gt;il existe cependant des solutions, ou au moins des tentatives de solutions, pour pallier à ces inconvénients.&lt;/p&gt;
&lt;h3&gt;Patch&lt;/h3&gt;
&lt;p&gt;La première provient d&apos;un modification du langage lui-même, sous forme d&apos;un patch ou d&apos;une extension. &lt;a href=&quot;http://ilia.ws/archives/207-Type-Hinting-Conclusion.html&quot;&gt;Ilia Alshanetsky&lt;/a&gt; propose depuis longtemps un patch pour PHP 5.3. Ce patch, complet, modifie le comportement du parseur, mais complète également Reflection.&lt;/p&gt;
&lt;p&gt;Malheureusement, l&apos;utilisation de ce patch nécessite de devoir patcher systématiquement PHP, avec d&apos;autant plus de risques que le patch n&apos;est pas officiel. Mais surtout ce patch n&apos;est pas mis à jour, et n&apos;est plus compatible avec les versions récentes de PHP 5.3 (ne parlons même pas de &lt;a href=&quot;/php/ca-vous-dit-dinstaller-lalpha-de-php-5-4&quot; title=&quot;Ca vous dit d’installer l’alpha de PHP 5.4 ?&quot;&gt;PHP 5.4&lt;/a&gt; :-) )&lt;/p&gt;
&lt;h3&gt;La Standard Php Libary&lt;/h3&gt;
&lt;p&gt;Bon, c&apos;est un idée, mais il y en a eu d&apos;autres. Marcus Börger et David Coallier ont développé l&apos;&lt;a href=&quot;http://pecl.php.net/package/SPL_Types&quot;&gt;extension PECL SPL_Types&lt;/a&gt; pour insérer ce comportement dans la &lt;a href=&quot;http://php.net/manual/en/book.spl-types.php&quot;&gt;SPL&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Ici, pas vraiment de typage fort scalaire, mais une surcouche à utiliser :&lt;/p&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;function test(SplInt $integer){
    // (...)
}

$value = new SplInt(5);
&amp;lt;p&amp;gt;test($value);&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/p&gt;

Ça semble marcher, mais personnellement je trouve ca peu élégant et pas vraiment optimal : quid de la compatibilité avec des librairies externes qui n&apos;utiliseront pas les typages Spl ? Et bon, on ne type pas les scalaires ; dans notre exemple ce qui suit ne fonctionnera pas, ce qui montre en soi que cette solution n&apos;est qu&apos;une rustine :

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;assert(5 instanceof SplInt); // faux
&amp;lt;p&amp;gt;assert(is_int( new SplInt(5) )); // faux&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/p&gt;
&lt;h3&gt;La rustine de la mort qui tue&lt;/h3&gt;
&lt;p&gt;Très vite, de nombreux développeurs ont eu l&apos;idée de passer par un gestionnaire d&apos;erreur personnalisé pour résoudre ce problème. Personnellement, je l&apos;avais mis en place sur un framework dans une ancienne boite. À l&apos;époque j&apos;avais trouvé ça tout seul et ça m&apos;avait bien amusé. Après j&apos;avais fait des benchs et je l&apos;avais vite retiré ;-)&lt;/p&gt;

&lt;p&gt;L&apos;idée est d&apos;intercepter les erreurs, d&apos;analyser le message de l&apos;erreur et de voir s&apos;il correspond pas à un problème de type. Si c&apos;est le cas on fait nous même le contrôle &quot;a la main&quot;, sinon on renvoie vers le gestionnaire d&apos;erreur par défaut.&lt;/p&gt;

&lt;p&gt;Voici une version basique :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;set_error_handler(function($errno, $errstr, $errfile, $errline) {
    if(preg_match(&amp;#39;!must be an instance of (\w*), (\w*) given!&amp;#39;, $errstr, $matches)) {
        $matches[2] = str_replace(&amp;#39;double&amp;#39;,&amp;#39;float&amp;#39;, $matches[2]);
        return strtolower($matches[1]) == strtolower($matches[2]);
    }
});&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;En 6 petites lignes de code, magique : on peut désormais écrire ceci :
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;function example1(integer $v) {
    echo &amp;#39;Cool, integer was given !&amp;#39;.PHP_EOL;
}
function example2(string $v) {
    echo &amp;#39;Cool, string was given !&amp;#39;. PHP_EOL;
}
function example3(float $v) {
    echo &amp;#39;Cool, float was given !&amp;#39;;
}
function example4(boolean $v) {
    echo &amp;#39;Cool, boolean was given !&amp;#39;;
}
function example5(object $v) {
    echo &amp;#39;Cool, object was given !&amp;#39;;
}
function example6(resource $v) {
    echo &amp;#39;Cool, resource was given !&amp;#39;;
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;N&apos;oubliez pas que &lt;strong&gt;c&apos;est catastrophique côté perf &lt;/strong&gt;:-)&lt;/p&gt;

&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Bref, on se retrouve avec des rustines et pas vraiment des solutions exploitables en production. Pourtant, si je comprend que le typage fort puisse gêner, &lt;strong&gt;je trouve que le typage des paramètres et des retours de de fonction (comme en Java par exemple) manque beaucoup&lt;/strong&gt;, et pourrait apporter du grain dans la professionnalisation du langage. &lt;/p&gt;

&lt;p&gt;D&apos;ailleurs, si on observe bien, on retrouve tout de même de &lt;a href=&quot;https://wiki.php.net/rfc/typechecking&quot;&gt;nombreuses RFC à ce sujet&lt;/a&gt;, et le sujet à été débattu à de nombreuses reprises, et par de nombreuses &quot;figures&quot; de PHP (&lt;a href=&quot;https://wiki.php.net/rfc/typecheckingparseronly&quot;&gt;Derick Rethans&lt;/a&gt;, &lt;a href=&quot;http://sebastian-bergmann.de/archives/900-Scalar-Type-Hints-in-PHP-5.3.99.html&quot;&gt;Sebastian Bergmann&lt;/a&gt;...), on a même un &lt;a href=&quot;http://schlueters.de/blog/archives/139-Scalar-type-hints-in-PHP-trunk.html&quot;&gt;moment que c&apos;était bon&lt;/a&gt;. Aux dernières nouvelles (mais je dois avouer que je ne sais pas trop ce qu&apos;il en est ) il me semble que l&apos;introduction du typage fort des arguments des fonctions reste très largement discutée, voire contestée, et qu&apos;alors même on l&apos;espérait pour php 5.4, elle n&apos;est prévue dans aucune des futures versions sur les rails.&lt;/p&gt;

&lt;strong&gt;Quel est votre avis sur la question ?&lt;/strong&gt; Le typage fort des paramètres et des retours de fonctions vous manque t-il au quotidien ? Ou bien au contraire trouvez-vous que c&apos;est justement la force de PHP que d&apos;offrir une grande souplesse aux développeurs et de s&apos;en passer ? &lt;strong&gt;Etes-vous déçus de ne pas avoir cette nouveauté dans PHP 5.4 ?&lt;/strong&gt;
</content>
 </entry>
 
 <entry>
   <title>Slides de l'atelier php sur les Traits pour l'AFUP</title>
   <link href="https://blog.lepine.pro/php/slides-de-latelier-php-sur-les-traits-pour-lafup"/>
   <updated>2012-02-03T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/php/slides-de-latelier-php-sur-les-traits-pour-lafup</id>
   <content type="html">Pour ceux qui étaient là hier pour notre atelier php à Orléans sur la programmation orientée objet, php 5.4 et les traits, voici les slides que j&apos;ai utilisés :

&lt;div id=&quot;__ss_11402129&quot; style=&quot;width: 100%;&quot;&gt;&lt;strong style=&quot;display: block; margin: 12px 0 4px;&quot;&gt;&lt;a title=&quot;Programmation Orientée Objet et les Traits en PHP 5.4&quot; href=&quot;http://www.slideshare.net/halleck45/programmation-oriente-objet-et-les-traits-en-php-54&quot; target=&quot;_blank&quot;&gt;Programmation Orientée Objet et les Traits en PHP 5.4&lt;/a&gt;&lt;/strong&gt; &lt;iframe src=&quot;http://www.slideshare.net/slideshow/embed_code/11402129&quot; frameborder=&quot;0&quot; marginwidth=&quot;0&quot; marginheight=&quot;0&quot; scrolling=&quot;no&quot; width=&quot;100%&quot; style=&quot;min-height: 400px&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;
&lt;div style=&quot;padding: 5px 0 12px;&quot;&gt;View more &lt;a href=&quot;http://www.slideshare.net/&quot; target=&quot;_blank&quot;&gt;presentations&lt;/a&gt; from &lt;a href=&quot;http://www.slideshare.net/halleck45&quot; target=&quot;_blank&quot;&gt;halleck45&lt;/a&gt;&lt;/div&gt;
&lt;div style=&quot;padding: 5px 0 12px;&quot;&gt;Au passage, merci à tous d&apos;êtres venus ! Prochain rendez-vous le 1er mars (premier jeudi du mois) :-)&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>PHP à Orléans : ça bouge !</title>
   <link href="https://blog.lepine.pro/php/php-a-orleans-ca-bouge"/>
   <updated>2012-01-20T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/php/php-a-orleans-ca-bouge</id>
   <content type="html">&lt;p&gt;Et bien pour un premier apéro php sur Orléans, on peut dire que ça s&apos;est bien passé ! Une bonne dizaine de personnes, motivées, sympas.... finalement à Orléans on semble bien décidés à montrer qu&apos;on existe :-)&lt;/p&gt;

&lt;p&gt;Pour synthétiser la soirée, qui s&apos;est tenu sur la place du Martroi, au coeur d&apos;Orléans, et bien des bonnes discussions de geeks, des projets... Ainsi a t-il été décidé de se retrouver une fois par mois pour des ateliers / formations sur PHP et sur des technologies tierces.&lt;/p&gt;

&lt;p&gt;Parmi les présents, on retrouve en force SUPINFO, mais aussi la CCI, et différentes web agency orléanaises. On espère être plus nombreux pour le prochain rendez-vous ; aussi si vous êtes de la région, n&apos;hésitez pas à surveiller le site d&apos;apéro php ou à m&apos;envoyer un petit message si vous êtes intéressés. Y&apos;aurait un développeur photographe dans l&apos;équipe ? :-p&lt;/p&gt;

&lt;p&gt;En attendant le prochain rendez-vous, voici quelques photos de la soirée. Bon, désolé pour la qualité de l&apos;image, on ne peut pas dire que mon téléphone soit le meilleur pour ce qui est de prendre des photos...&lt;/p&gt;

&lt;a href=&quot;https://blog.lepine.pro/images/2012-01-IMG_20120119_194548.jpg&quot;&gt;&lt;img class=&quot;size-medium wp-image-267 aligncenter&quot; title=&quot;Apéro php Orléans 19 janvier 2012&quot; src=&quot;https://blog.lepine.pro/images/2012-01-IMG_20120119_194548.jpg&quot; alt=&quot;Apéro php Orléans 19 janvier 2012 - photo 1&quot; width=&quot;300&quot; height=&quot;225&quot; /&gt;&lt;/a&gt;

&lt;a href=&quot;https://blog.lepine.pro/images/2012-01-IMG_20120119_194555.jpg&quot;&gt;&lt;img class=&quot;size-medium wp-image-268 aligncenter&quot; title=&quot;Apéro php Orléans 19 janvier 2012&quot; src=&quot;https://blog.lepine.pro/images/2012-01-IMG_20120119_194555.jpg&quot; alt=&quot;Apéro php Orléans 19 janvier 2012 - photo 2&quot; width=&quot;300&quot; height=&quot;225&quot; /&gt;&lt;/a&gt;
</content>
 </entry>
 
 <entry>
   <title>Comment tester un Trait avec phpUnit ?</title>
   <link href="https://blog.lepine.pro/php/comment-tester-un-trait-avec-phpunit"/>
   <updated>2011-09-13T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/php/comment-tester-un-trait-avec-phpunit</id>
   <content type="html">&lt;p&gt;Il peut être intéressant de tester un code avec des &lt;code&gt;Traits&lt;/code&gt;, PHP et phpUnit. Les &lt;code&gt;Traits&lt;/code&gt; sont apparus en PHP 5.4, et permettent de définir des comportements qui peuvent être réutilisés dans plusieurs classes.&lt;/p&gt;

&lt;p&gt;Lors de mes premiers tests, je me suis vite rendu compte d’un problème : pour pouvoir tester un trait, c’est à dire un comportement, il faut une classe qui implémente ce comportement :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;trait MyBehavior {
    public function getAny()
    {
        return &apos;ok&apos;;
    }
}

class Example {
    use MyBehavior;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Et le test :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;class BehaviorTest extends PHPUnit\Framework\TestCase {

    public function testMyBehavior()
    {
        $behavior = new Example;
        $this-&amp;gt;assertEquals(&apos;ok&apos;, $behavior-&amp;gt;getAny());
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Or ici on voit bien l’erreur : &lt;strong&gt;on ne test pas unitairement le comportement du Trait, mais son implémentation&lt;/strong&gt; dans une classe qui est sujette à modifications.&lt;/p&gt;

&lt;p&gt;C’est ici qu’intervient PHPUnit, qui a introduit la méthode &lt;strong&gt;&lt;code&gt;getObjectForTrait()&lt;/code&gt;&lt;/strong&gt; dans sa version 3.6. 
Grâce à cette méthode, il est possible de tester directement notre comportement.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;trait MyBehavior {
    public function getAny()
    {
        return &apos;ok&apos;;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;et le test :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;class BehaviorTest extends PHPUnit\Framework\TestCase {
    
    public function testMyBehavior() 
    {
        $behavior = $this-&amp;gt;getObjectForTrait(&apos;MyBehavior&apos;);
        $this-&amp;gt;assertEquals(&apos;ok&apos;, $behavior-&amp;gt;getAny());
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Pratique non ?&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;💡 &lt;strong&gt;Pour aller plus loin&lt;/strong&gt; :&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&quot;https://phpunit.de/&quot;&gt;PHPUnit&lt;/a&gt;, le framework de test unitaire pour PHP.&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;http://php.net/manual/fr/language.oop5.traits.php&quot;&gt;Les traits en PHP&lt;/a&gt;, la documentation officielle.&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;./2012-02-03-slides-de-latelier-php-sur-les-traits-pour-lafup.html&quot;&gt;Les traits en PHP&lt;/a&gt;, conférence que j’ai donnée à l’AFUP.&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;
</content>
 </entry>
 
 <entry>
   <title>Couverture de code de PHP : un avis sur la polémique de PHP 5.3.7 ?</title>
   <link href="https://blog.lepine.pro/php/couverture-de-code-de-php-un-avis-sur-la-polemique-de-php-5-3-7"/>
   <updated>2011-09-09T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/php/couverture-de-code-de-php-un-avis-sur-la-polemique-de-php-5-3-7</id>
   <content type="html">&lt;p&gt;Si vous êtes intéressé par la couverture de code de PHP* (la manière dont les fonctions natives du langage sont testées par des Tests unitaires), il est très intéressant de consulter &lt;a href=&quot;http://gcov.php.net/&quot; target=&quot;_blank&quot;&gt;http://gcov.php.net/&lt;/a&gt; .&lt;/p&gt;

&lt;p&gt;Ce site contient en temps réel les compte-rendus de la couverture de la version en cours de PHP (PHP 5.3) et la la prochaine version (PHP 5.4)&lt;/p&gt;

&lt;p&gt;Vous y trouverez :&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;la couverture de code&lt;/li&gt;
	&lt;li&gt;les compte-rendus de compilation&lt;/li&gt;
	&lt;li&gt;la liste des fonctions en erreur / warning&lt;/li&gt;
	&lt;li&gt;la liste des fonctions non testées&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ce site va peut être mettre fin aux polémiques sur le workflow des versions de PHP. En effet, on se souvient tous de l&apos;&lt;a href=&quot;http://www.php.net/archive/2011.php#id2011-08-22-1&quot; target=&quot;_blank&quot;&gt;affaire récente de PHP 5.3.7&lt;/a&gt; qui a révélé de grosses lacunes dans le langage (à savoir que les versions stables de PHP contiennent en réalité de nombreuses fonctions dont les tests unitaires échouent).&lt;/p&gt;

&lt;p&gt;J&apos;ai bien envie de tirer alors un petit bilan, qui n&apos;engage que moi, mais sur lequel j&apos;aimerai avoir votre avis. Je pense que depuis PHP 5.3, PHP s&apos;est largement professionnalisé, grâce à d&apos;une part un nouveau modèle Orienté Objet plus riche, d&apos;autre part l&apos;appropriation par les développeurs des outils de PIC (Plate-forme d&apos;intégration continue) et de phpUnit en particulier.&lt;/p&gt;

&lt;p&gt;Cette professionnalisation a amené de nouveaux types de développeurs PHP, avec de nouvelles exigences, auxquelles PHP tente de répondre aujourd&apos;hui.&lt;/p&gt;

&lt;p&gt;Je pense alors que, oui on peut critiquer le processus actuel de mise en production de PHP, car avec PHP 5.3.7 PHP a été la &quot;risée&quot; du web (j&apos;éxagère exprès, mais disons que ça ne l&apos;aide pas à effacer l&apos;image très négative des développeurs PHP, qui sont souvent considérés comme des &quot;sous-développeurs&quot;, même encore aujourd&apos;hui).&lt;/p&gt;

&lt;p&gt;Mais c&apos;est sur la bonne voie. PHP se professionnalise, se réorganise (processus de votes, passage de subversion à git...), se réoriente. Le langage répond de mieux en mieux à nos exigences de qualité (il suffit de voir le nombre de type d&apos;&lt;a href=&quot;http://www.php.net/~helly/php/ext/spl/classException.html&quot;&gt;exceptions dans la SPL&lt;/a&gt; par exemple, ou encore l&apos;apparition des Traits dans PHP 5.4). Bref, je trouve dommage de jeter la pierre, comme certains l&apos;ont fait, à un langage qui est dans une phase de transition, et, je l&apos;espère, de transition positive.&lt;/p&gt;

&lt;p&gt;Et vous ? Pensez-vous également que cette phase de transition soit positive ? Il serait aussi intéressant de savoir ce qu&apos;en pensent des développeurs d&apos;autres langages, pour connaître des avis extérieurs...&lt;/p&gt;

&lt;p&gt;____________&lt;/p&gt;

* note pour  ceux qui ne sont pas familiarisés avec les tests unitaires, il est peut-être temps de vous y mettre :-) . L&apos;outil le plus utilisé est &lt;a href=&quot;http://www.phpunit.de/manual/3.5/en/automating-tests.html&quot; target=&quot;_blank&quot;&gt;PhpUnit&lt;/a&gt;, mais un &quot;nouveau&quot; venu qui a l&apos;air bien conçu mais que j&apos;ai pas encore eu l&apos;occasion de tester est &lt;a href=&quot;http://www.slideshare.net/impossiblium/atoum-le-framework-de-tests-unitaires-pour-php-53-simple-moderne-et-intuitif&quot; target=&quot;_blank&quot;&gt;Atoum&lt;/a&gt;, de &lt;a href=&quot;http://blog.mageekbox.net/&quot; target=&quot;_blank&quot;&gt;Frédéric Hardy&lt;/a&gt;.

&lt;p&gt;Pour générer un compte rendu de la couverture de code avec phpUnit, cette simple commande suffit :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;phpunit  --coverage-html /chemin/du/rapport/ /chemin/des/tests/unitaires&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
</content>
 </entry>
 
 <entry>
   <title>PHP Solutions d'Aout est disponible</title>
   <link href="https://blog.lepine.pro/php/php-solutions-daout-est-disponible"/>
   <updated>2011-08-02T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/php/php-solutions-daout-est-disponible</id>
   <content type="html">&lt;p&gt;Bon, et bien comme un peu de pub n&apos;a jamais fait de mal, et surtout que j&apos;ai écrit comme je vous le disais un petit article dans ce numéro, je vous invite à découvrir le dernier numéro de PHP Solution :&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;POO en PHP et nouveautés apportées par PHP 5.4.&lt;/li&gt;
	&lt;li&gt;Drupal et Magento deux géants pour une plateforme e-commerce / m-commerce complète.&lt;/li&gt;
	&lt;li&gt;Facebook : Créer vos applications au bout des doigts.&lt;/li&gt;
	&lt;li&gt;Générer des classeurs pour tableur avec PHPExcel.&lt;/li&gt;
	&lt;li&gt;Les solutions pour booster WordPress.&lt;/li&gt;
	&lt;li&gt;PC ASUS G73S.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bref, si le coeur vous en dit ça se passe ici : &lt;a href=&quot;http://phpsolmag.org/drupal-et-magento-creez-votre-plateforme-e-commerce/&quot; target=&quot;_blank&quot;&gt;PHP Solutions Aout 2011&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;L&apos;article &quot;Repenser l&apos;approche orientée objet&quot; est une &lt;strong&gt;introduction&lt;/strong&gt; à une suite d&apos;article sur la professionnalisation de PHP et sur l&apos;arrivée prochaine de PHP 5.4. En quelques pages je n&apos;ai pas la prétention de &quot;repenser&quot; l&apos;objet, mais simplement démarrer en présentant mon point de vue sur : d&apos;un côté qu&apos;utiliser une classe ne veut pas dire qu&apos;on pense Objet ; d&apos;autre part que pour moi une application se définit comme un maillage de &quot;Comportements&quot;, d&apos;où l&apos;intérêt de PHP 5.4 et des &lt;a title=&quot;PHP 5.4 : les Traits (Horizontal Reuses)&quot; href=&quot;/php/php-5-4-les-traits-horizontal-reuses&quot;&gt;Traits&lt;/a&gt; selon moi.&lt;/p&gt;

&lt;p&gt;Après, si vous avez des suggestions, des retours et des critiques, n&apos;hésitez pas à laisser un petit commentaire, et surtout faites-nous partager votre vision de la Programmation Orientée Objet, et de la manière dont le modèle Objet de PHP oriente vos développements :-)&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Redonner son sens à l'héritage grâce aux Traits</title>
   <link href="https://blog.lepine.pro/php/redonner-son-sens-a-lheritage-grace-aux-traits"/>
   <updated>2011-08-01T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/php/redonner-son-sens-a-lheritage-grace-aux-traits</id>
   <content type="html">&lt;p&gt;Suite à la rédaction d&apos;un petit article pour PHP Solutions sur l&apos;approche Orientée Objet, je me suis mis à faire quelques tests pour voir jusqu&apos;à quel point les Traits de PHP 5.4 pourront changer notre manière de programmer.&lt;/p&gt;

&lt;p&gt;Et bien ça fait plus que changer, les Traits sont à mon avis une &lt;strong&gt;véritable révolution dans l&apos;approche et la conceptualisation d&apos;un code&lt;/strong&gt;, quel qu&apos;il soit.&lt;/p&gt;

&lt;p&gt;En effet, &lt;strong&gt;un Trait permet de réutiliser un Comportement&lt;/strong&gt;. Or sur un gros projet, même en organisant bien notre code, on est souvent amené à recoder plusieurs fois le même comportement. Par exemple, pour créer un Singleton on a deux choix :&lt;/p&gt;
&lt;ol&gt;
	&lt;li&gt;ou bien on écrit tout le code nécessaire dans notre classe&lt;/li&gt;
	&lt;li&gt;ou bien on fait hériter notre classe d&apos;une classe mère Singleton&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Dans le premier cas, on a inévitablement du &lt;strong&gt;code redondant&lt;/strong&gt; dans notre application.&lt;/p&gt;

&lt;p&gt;Dans le second, on perd le principe de l&apos;héritage : l&apos;héritage ne sert pas ici à spécialiser un comportement métier, mais à disposer d&apos;outils pour un fonctionnement interne. On perd alors totalement ce principe de l&apos;Orienté Objet qui consiste&lt;strong&gt; réduire le couplage entre les modules métiers et les modules internes&lt;/strong&gt; ; L&apos;héritage en PHP n&apos;étant pas multiple, &lt;strong&gt;on perd le seul héritage métier/fonctionnel que l&apos;on aurait pu avoir pour notre classe&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Un solution consisterait à augmenter le niveau d&apos;héritage et de créer une hiérarchie de classes (c&apos;est ce qui est souvent fait, vu que l&apos;héritage multiple n&apos;existe pas en PHP). Mais dans ce cas :&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;on a perdu la logique objet : l&apos;héritage n&apos;a pas servi à spécialiser&lt;/li&gt;
	&lt;li&gt;le code n&apos;est plus maintenable&lt;/li&gt;
	&lt;li&gt;l&apos;architecture n&apos;est plus maintenable (on est pris dans un cercle vicieux, celui de rajouter toujours un niveau au dessus jusqu&apos;à créer une classe géante à la racine qui fait tout)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bref, à mon avis sur ce genre de code on est souvent dans une impasse. C&apos;est là qu&apos;interviennent les Traits. Plutôt que de consacrer l&apos;héritage à un comportement interne, laissons l&apos;héritage pour le comportement métier et utilisons les traits pour ce qui est interne.&lt;/p&gt;

&lt;p&gt;Attention, je ne dis pas que les Traits ne peuvent pas être utilisés pour du comportement métier (bien au contraire), ne me faites pas dire ce que je n&apos;ai pas dit :-)&lt;/p&gt;

&lt;p&gt;En voici un exemple simple. Cet exemple n&apos;est pas idéal, dans la mesure où il s&apos;agit plus d&apos;un détournement des Traits que d&apos;une utilisation plus intelligente, mais je pense qu&apos;il est clair :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;trait Singleton {
    /**
     * Constructor
     *
     */
    protected function __construct() {}
     /**
     * Get singleton instance
      *
     * @return static
     */
    public static function getInstance() {

        static $instance = null;
        if (is_null($instance)) {
            $instance = new static;
        }

        return $instance;
    }

    /**
     * Prevents cloning
     *
     * @throws Exception
     */
    public function __clone() {
        throw new \Exception(&amp;#39;Cloning of this object isn\&amp;#39;t authorized&amp;#39;);
    }

    /**
     * Prevents deserialization
     *
     * @throws Exception
     */
    public function __wakeup() {
        throw new \Exception(&amp;quot;Cannot deserialize instance of Singleton pattern in&amp;quot; . get_called_class());
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Il ne reste plus qu&apos;à l&apos;utiliser :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;class Example extends MaClasseMetier {
    use \Singleton;
}
$oExample = Example::getInstance();
var_dump($oExample === Example::getInstance());
// true
$oExample = new Example;
// Fatal error: Call to protected Example::__construct() from invalid context&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Ce détournement nous permet d&apos;utiliser le Comportement &quot;Singleton&quot; pour toutes les classes qui en ont besoin, avec un simple &quot;use Singleton&quot;. Bon, il ne faut pas forcément le faire, et je sens les remarques qui vont arriver sur le Singleton et la testabilité, mais je pense que c&apos;est un exemple clair des horizons que nous ouvrent les Traits :-p&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Compiler et installer PHP</title>
   <link href="https://blog.lepine.pro/php/compiler-et-installer-php"/>
   <updated>2011-07-04T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/php/compiler-et-installer-php</id>
   <content type="html">&lt;p&gt;Comme promis, voici le pas-à-pas d&apos;une installation personnalisée de PHP (en l&apos;occurrence PHP 5.4 alpha1).&lt;/p&gt;
&lt;!--more--&gt;

&lt;p&gt;Avant toute chose, notez qu&apos;en production il ne faut PAS utiliser une version alpha. Pour installer une version stable, ce code suffira :&lt;/p&gt;

&lt;em&gt;&lt;strong&gt;Note&lt;/strong&gt; : toutes les commandes ci-dessous sont à utiliser pour Ubuntu&lt;/em&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;sudo apt-get install php5&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Commencez par télécharger la release : &lt;a title=&quot;Lien vers PHP 5.4 alpha&quot; href=&quot;http://qa.php.net/&quot; target=&quot;_blank&quot;&gt;http://qa.php.net/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Ensuite, il suffit d&apos;extraire le contenu de l&apos;archive dans un dossier temporaire à part :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;wget http://downloads.php.net/stas/php-5.4.0alpha1.tar.gz
tar xvzf php-5.4.0alpha1.tar.gz&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;h2&gt;Uniquement en mode console ( cli )&lt;/h2&gt;
&lt;p&gt;Ouvrez un terminal, puis entrez :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;./configure --enable-pcntl --enable-shmop --enable-cli --without-apache --disable-cgi --enable-posix&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

(c&apos;est en l&apos;occurrence ce dont moi j&apos;avais besoin : ligne de commande uniquement + pcntl ; à vous de voir ce qu&apos;il vous faut).

&lt;p&gt;Si tout s&apos;est bien passé, il ne reste plus qu&apos;à préparer l&apos;installation :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;make&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;C&apos;est sans doute le moment de lancer les tests de PHP. Ces tests unitaires vous permettront de vous assurer que tout s&apos;est bien passé :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;make test&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;par contre, ne vous inquiétez pas, c&apos;est plutôt long.&lt;/p&gt;

&lt;p&gt;Si tout est ok, il est temps de lancer l&apos;installation de version de PHP :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;sudo make install&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Pour vérifier votre version de PHP (cli), c&apos;est simple :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;php -v
//
PHP 5.4.0alpha1 (cli) (built: Jun 29 2011 13:24:55)
Copyright (c) 1997-2011 The PHP Group
Zend Engine v2.4.0, Copyright (c) 1998-2011 Zend Technologies&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Je ne connais pas la procédure sous Windows, et je ne suis même pas sûr que ce soit possible (peut-être avec Visual Studio ?), mais si quelqu&apos;un veut bien l&apos;expliquer ...&lt;/p&gt;

&lt;p&gt;Edit: apparemment c&apos;est &lt;a href=&quot;http://www.artfulsoftware.com/php_mysql_win.html&quot;&gt;possible sous Windows&lt;/a&gt;. Des retours ?&lt;/p&gt;

&lt;h2&gt;Mode web (Apache)&lt;/h2&gt;
&lt;p&gt;Bon, lancer un script php c&apos;est souvent pas suffisant, c&apos;est mieux quand un site web tourne avec :-p&lt;/p&gt;

&lt;p&gt;Dans ce cas, on va légèrement changer la configuration, pour créer en même temps le module php pour apache (le with-apxs2):&lt;/p&gt;

Petite parenthèse:

&lt;p&gt;Si vous n&apos;avez pas apxs2, utilisez commande suivante :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;sudo apt-get install apache2-threaded-dev
un which apxs2 devrait vous retourner le chemin complet&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;


&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;./configure --with-mysql=shared --with-pdo-mysql  --with-apxs2=/usr/bin/apxs2 --enable-xml --with-zlib
copier le libphp5.xo
make&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Il faut copier le module qui a été généré par le make (dans libs/libphp5.so) dans le répertoire des modules apache :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;sudo cp libs/libphp5.so /usr/lib/apache2/modules/libphp5.so&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;et bien sûr, redémarrer Apache&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;sudo /etc/init.d/apache2 restart&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;le phpinfo() vous affiche désormais :&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blog.lepine.pro/images/2011-07-apercu-phpinfo-apres-compilation2.png&quot;&gt;&lt;img class=&quot;aligncenter size-full wp-image-172&quot; title=&quot;Aperçu phpinfo() après compilation&quot; src=&quot;https://blog.lepine.pro/images/2011-07-apercu-phpinfo-apres-compilation2.png&quot; alt=&quot;Aperçu phpinfo() après compilation&quot; width=&quot;503&quot; height=&quot;413&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blog.lepine.pro/images/2011-07-apercu-phpinfo-apres-compilation.png&quot;&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;PS : ce n&apos;est pas forcément la meilleure configuration, l&apos;intérêt de compiler est justement de prendre ce dont vous avez besoin; A vous de voir quels extensions vous sont utile ; n&apos;hésitez pas à faire un tour dans votre phpinfo() avant pour le savoir :-)&lt;/p&gt;

&lt;p&gt;PS : en même temps que j&apos;ai publié ce billet, notre cher Rasmus Lerdorf a publié une &lt;a href=&quot;http://codepad.org/SXfRlJ0w&quot; target=&quot;_blank&quot;&gt;config pour tester PHP 5.4&lt;/a&gt;.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Ca vous dit d'installer l'alpha de PHP 5.4 ?</title>
   <link href="https://blog.lepine.pro/php/ca-vous-dit-dinstaller-lalpha-de-php-5-4"/>
   <updated>2011-07-01T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/php/ca-vous-dit-dinstaller-lalpha-de-php-5-4</id>
   <content type="html">&lt;p&gt;Ca y est, depuis le 28 juin une alpha de PHP 5.4 est officiellement disponible. Ca vous dit de la tester ?&lt;/p&gt;
&lt;!--more--&gt;

&lt;p&gt;Personnellement (et il n&apos;est jamais trop tard pour changer d&apos;idée :-) ), je pense qu&apos;un développeur doit varier ses environnements de travail, ne serait-ce que pour être prêt pour n&apos;importe quelle migration. &lt;/p&gt;

&lt;p&gt;Bien sûr, il ne me viendrait pas à l&apos;idée de travailler sur une alpha au boulot ou de l&apos;installer sur mon serveur de production : je ne suis pas suicidaire. Par contre, avoir, par exemple, une machine avec PHP 5.4 dès aujourd&apos;hui, me permettra d&apos;être prêt le jour où le besoin s&apos;en fera sentir professionnellement ; cela me permettra d&apos;apprendre à utiliser les apports de cette version, mais aussi d&apos;en connaître les pièges et les subtilités.&lt;/p&gt;

&lt;p&gt;De même, varier les environnements peut être avantageux : si pour une raison x ou y vous devez changer de type serveur (Linux à Windows par exemple) pour une application, vous saurez anticiper un minimum les contraintes et limiter les dégâts (même si là j&apos;ai pris un exemple extrême :-p )&lt;/p&gt;

&lt;p&gt;Mais vous, comment fonctionnez-vous généralement quand une version majeure est annoncée ? Vous migrez rapidement (pour vos développements persos) pour pouvoir prendre en main les nouveautés rapidement ? Ou bien préférez-vous attendre que ce soit bien stable avant d&apos;y toucher, de peur que certains comportements changent en cours de route ?&lt;/p&gt;

&lt;p&gt;Je pense que ça peut être intéressant de voir les habitudes des développeurs PHP sur ce point, alors n&apos;hésitez pas à décrire les vôtres ici. A vos votes et vos commentaires :-p !&lt;/p&gt;

[poll id=&quot;2&quot;]
</content>
 </entry>
 
 <entry>
   <title>Apéro PHP à Blois : Joyeux anniversaire PHP !</title>
   <link href="https://blog.lepine.pro/php/apero-php-a-blois-joyeux-anniversaire-php"/>
   <updated>2011-07-01T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/php/apero-php-a-blois-joyeux-anniversaire-php</id>
   <content type="html">&lt;p&gt;Pour l&apos;&lt;a href=&quot;.org/pages/site/?route=actualites/459/trinquons-partout-en-france-pour-lanniversaire-de-php53&quot; title=&quot;Anniversaire de PHP 5.3&quot; target=&quot;_blank&quot;&gt;anniversaire de PHP 5.3&lt;/a&gt;, &lt;a href=&quot;http://www.viadeo-static.com/fr/profile/sophie.beaupuis&quot; title=&quot;Sophie Beaupuis&quot; target=&quot;_blank&quot;&gt;Sophie&lt;/a&gt; a fort gentiment organisé une rencontre sur Blois.&lt;/p&gt;

&lt;p&gt;Etaient principalement présents l&apos;équipe de la société &lt;a href=&quot;http://www.flavea.fr&quot; title=&quot;Flavea et l&apos;Apéro PHP 2011&quot; target=&quot;_blank&quot;&gt;Flavea&lt;/a&gt; et celle de la Maison de Valérie.&lt;/p&gt;

&lt;p&gt;Le petit éléphant bleu était bien là pour son anniversaire :-)&lt;/p&gt;

&lt;p&gt;Très bonne soirée ! On remet ça pour la sortie de PHP 5.4 ?&lt;/p&gt;

&lt;p&gt;Vous étiez-où vous ? Apparemment à Lille ça a été pas mal non plus non ?&lt;/p&gt;

[gallery]
</content>
 </entry>
 
 <entry>
   <title>Tour d'horizon des Callbacks en PHP</title>
   <link href="https://blog.lepine.pro/php/tour-dhorizon-des-callbacks-en-php"/>
   <updated>2011-06-17T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/php/tour-dhorizon-des-callbacks-en-php</id>
   <content type="html">&lt;p&gt;Je suis toujours étonné de voir de nombreux développeurs PHP, pourtant habitués à travailler avec des callbacks en JavaScript, connaître si peu voire pas du tout les callbacks PHP.&lt;/p&gt;

&lt;p&gt;C&apos;est pourquoi je vous propose aujourd&apos;hui un &lt;strong&gt;petit rappel des callbacks en PHP&lt;/strong&gt; :&lt;!--more--&gt;&lt;/p&gt;

&lt;p&gt;Un callback, c&apos;est quoi ? En un mot, &lt;strong&gt;Un callback est une référence vers un code exécutable&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;L&apos;utilisation de callbacks est très fréquente dans certains langages. Un exemple simple en JavaScript :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-javascript&quot; data-lang=&quot;javascript&quot;&gt;&amp;lt;a onclick=&amp;quot;doAnything()&amp;quot;&amp;gt;example&amp;lt;/a&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;La fonction doAnything() est un callback, qui sera exécuté à chaque clic sur le lien.&lt;/p&gt;
&lt;h2&gt;Création d&apos;un callback en PHP&lt;/h2&gt;
&lt;p&gt;En PHP, les callbacks peuvent être définis de nombreuses manières :&lt;/p&gt;
&lt;h3&gt;Fonction anonyme&lt;/h3&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;$callback = function() {
    echo &amp;quot;un callback dans une fonction anonyme&amp;quot;;
};&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3&gt;Fonction existante&lt;/h3&gt;
&lt;p&gt;Les callbacks peuvent référer une fonction existante. Dans ce cas, on utilise le nom de la fonction comme callback :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;$callback = &amp;#39;maFonction&amp;#39;;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3&gt;Méthode de classe existante&lt;/h3&gt;
&lt;p&gt;De la même façon, on peut référer une méthode de classe, avec cette fois quelques subtilités :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;$callback = array(&amp;#39;NomDeLaClass&amp;#39;,&amp;#39;maMethode&amp;#39;);&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Equivaut à :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;$obj = new NomDeLaClass;
$callback = array($obj,&amp;#39;maMethode&amp;#39;);&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Ou encore (méthode statique) :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;$callback = array(&amp;#39;NomDeLaClass::maMethodeStatique&amp;#39;);&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3&gt;Cas particuliers&lt;/h3&gt;
&lt;p&gt;Pour accéder au parent de la classe actuelle, utilisez le mot clef &quot;parent&quot; de cette façon :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;$callback = array(&amp;#39;parent::maMethode&amp;#39;);&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;et cela de la même façon pour self et static :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;$callback = array(&amp;#39;self::maMethode&amp;#39;);
$callback = array(&amp;#39;static::maMethode&amp;#39;);&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2&gt;Appeler/Exécuter un callback&lt;/h2&gt;
&lt;p&gt;le plus simple est sans doute d&apos;utiliser la fonction &lt;a title=&quot;Documentaiton de call_user_func()&quot; href=&quot;http://php.net/manual/fr/function.call-user-func.php&quot; target=&quot;_blank&quot;&gt;&lt;strong&gt;call_user_func&lt;/strong&gt;&lt;/a&gt; :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;call_user_func($callback);&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;le moins élégant (c&apos;est le moins qu&apos;on puisse dire :-)) est d&apos;utiliser la syntaxe suivante :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;$callback();&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2&gt;Passage de paramètres et références&lt;/h2&gt;
&lt;p&gt;Nous allons nous attarder maintenant sur les paramètres à passer au callback. C&apos;est simple :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;$callback($param1, $param2);
// ou
call_user_func($callback, $param1, $param2);&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;la fonction call_user_func a une soeur, &lt;strong&gt;call_user_func_array&lt;/strong&gt;, qui permet de passer les paramètres sous forme de tableau :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;call_user_func_array($callback, array($param1, $param2));&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Jusque là, c&apos;est simple. &lt;strong&gt;Il faut juste faire attention au passages de paramètres par référence&lt;/strong&gt;. En effet, la fonction call_user_func() ne passe pas les paramètres par référence :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;function test(&amp;amp;$val) {
}
call_user_func(&amp;#39;test&amp;#39;, $value);
// Warning: Parameter 1 to test()
// expected to be a reference, value given&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Il faut ruser et passer par call_user_func_array() en forçant la référence :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;function test(&amp;amp;$a) {
}
call_user_func_array(&amp;#39;test&amp;#39;,array(&amp;amp;$value));
// Cette fois c&amp;#39;est bon&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2&gt;Performances&lt;/h2&gt;
&lt;p&gt;Les appels de fonctions de type callback sont plus lents que des appels de fonction directs. Voici un bench sur 1 million d&apos;itérations :&lt;/p&gt;

&lt;a href=&quot;https://blog.lepine.pro/images/2011-06-bench-callbacks-php-5-3.jpg&quot;&gt;&lt;img title=&quot;Bench Callbacks en PHP 5.3&quot; src=&quot;https://blog.lepine.pro/images/2011-06-bench-callbacks-php-5-3.jpg&quot; alt=&quot;Bench Callbacks en PHP 5.3&quot; width=&quot;400&quot; height=&quot;231&quot; /&gt;&lt;/a&gt;

&lt;p&gt;Certes il y a une différence, mais dans application standard &lt;strong&gt;cette différence est généralement négligeable&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;Le mot de la fin&lt;/h2&gt;
&lt;p&gt;Vous me direz que PHP n&apos;est pas à la base un langage où l&apos;utilisation massive des callbacks est pertinente. Mais bien utilisé, les callbacks peuvent significativement alléger le code, et en voici un exemple trivial :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;$array=array(&amp;#39;pomme&amp;#39;,&amp;#39;peche&amp;#39;,&amp;#39;poire&amp;#39;,&amp;#39;abricot&amp;#39;);
array_walk($array, function(&amp;amp;$item) {
    $item = strtoupper($item);
});
print_r($array);
//Array
//(
//    [0] =&amp;gt; POMME
//    [1] =&amp;gt; PECHE
//    [2] =&amp;gt; POIRE
//    [3] =&amp;gt; ABRICOT
//)&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Et vous, vous avez l&apos;habitude d&apos;utiliser des callbacks, ou bien vous réservez ça pour d&apos;autres langages ?&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Le Design Pattern Flyweight (Poids mouche)</title>
   <link href="https://blog.lepine.pro/architecture/le-design-pattern-flyweight-poids-mouche"/>
   <updated>2011-06-08T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/architecture/le-design-pattern-flyweight-poids-mouche</id>
   <content type="html">&lt;p&gt;
&lt;p&gt;Aujourd&apos;hui je vous propose de parler &lt;strong&gt;Design Pattern&lt;/strong&gt;, et plus particulièrement d&apos;un pattern intéressant à mettre en place quand on cherche à alléger (en mémoire) une application : le &lt;strong&gt;pattern Flyweight (poid mouche)&lt;/strong&gt;.&lt;/p&gt;
&lt;/p&gt;

&lt;h2&gt;Problème à résoudre&lt;/h2&gt;
&lt;p&gt;
&lt;p&gt;l&apos;application doit manipuler de très nombreuses Entités (des produits pour une boutique par exemple), et chaque Entité est représentée par un Objet. L&apos;instanciation de tous ces objets est très gourmande en mémoire :&lt;/p&gt;
&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;$oDataMapper = new DataMapper_Product();
$tRowset = $oDataMapper-&amp;gt;fetchAll($where);

foreach($tRowset as $oProduct) {
    // one instance of Product $oProduct is created in each loop
    echo $oProduct-&amp;gt;getName();
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;!--more--&gt;
&lt;p&gt;
&lt;p&gt;On voit le problème : pour chaque ligne récupérée, un objet est instancié, donc est créé, chargé en mémoire... c&apos;est lent et coûteux.&lt;/p&gt;
&lt;/p&gt;
&lt;h2&gt;La solution : Flyweight&lt;/h2&gt;
&lt;p&gt;
&lt;p&gt;Il suffit de n&apos;instancier qu&apos;une seule fois l&apos;objet en question, et de l&apos;hydrater au fur et à mesure du besoin :&lt;/p&gt;
&lt;/p&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;$oDataMapper = new DataMapper_Product();
$tRowset = $oDataMapper-&amp;gt;fetchAll($where);

// Only one instance of Product is used
$oProduct = new Product;

foreach($tRowset as $tInfosAboutProduct) {
    $oProduct-&amp;gt;hydrate($tInfosAboutProduct);
    echo $oProduct-&amp;gt;getName();
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;
&lt;p&gt;Ce qui dans notre cas pourrait donner par exemple cette classe :&lt;/p&gt;
&lt;/p&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;class Product extends Dao {

    protected $_name;
    protected $_price;

    (... getter and setters ...)
    
    public function hydrate(array $tData) {
        $this
                -&amp;gt;setName($tData[&amp;#39;name&amp;#39;])
                -&amp;gt;setPrice($tData[&amp;#39;price&amp;#39;]);
        return $this;
    }

}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;
&lt;p&gt;Les performances sont dans ce cas meilleures, et l&apos;&lt;strong&gt;utilisation mémoire est plus réduite&lt;/strong&gt;.&lt;/p&gt;
&lt;/p&gt;
&lt;h2&gt;Quand utiliser le pattern Flyweight ?&lt;/h2&gt;

&lt;p&gt;
&lt;p&gt;Ce type de pattern n&apos;est pas forcément adapté dans toutes les situations, on l&apos;utilise :&lt;/p&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;quand on doit manipuler de très nombreux petits objets&lt;/li&gt;
&lt;li&gt;quand le coût (mémoire/vitesse) de cette manipulation est élevé&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
&lt;strong&gt;Et vous, avez-vous déjà utilisé ce pattern ? Quel est votre retour d&apos;expérience ?&lt;/strong&gt;
&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Objet : accéder au grand parent en PHP</title>
   <link href="https://blog.lepine.pro/php/objet-acceder-au-grand-parent-en-php"/>
   <updated>2011-06-06T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/php/objet-acceder-au-grand-parent-en-php</id>
   <content type="html">&lt;p&gt;S&apos;il est courant de surcharger une méthode parente en PHP, il est plus rare d&apos;accéder directement à la classe &quot;grand -mère&quot; sans passer par la mère. Pourtant... c&apos;est possible, et voici comment :-)&lt;!--more--&gt;&lt;/p&gt;

&lt;p&gt;En effet, un héritage de PHP 4 permet préserver le contexte d&apos;exécution de la classe fille quand on appelle une classe parente en utilisant son nom plutôt que l&apos;opérateur &quot;parent&quot; (qui n&apos;existait pas avant). Un exemple tout simple :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;class GrandMother {
    protected $_myVar = &amp;#39;5&amp;#39;;

    public function doSomething() {
        echo &amp;quot;GrandMother, value is &amp;quot; . $this-&amp;gt;_myVar;
    }
}

class Mother extends GrandMother {

    public function doSomething() {
        echo &amp;#39;Mother, value is &amp;#39;; // Never called
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;La classe fille n&apos;utilise pas &quot;parent&quot;, mais directement le nom de la classe grand-mère :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;class Child extends Mother {

    public function doSomething() {
        $this-&amp;gt;_myVar = &amp;#39;20&amp;#39;;
        return GrandMother::doSomething();
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Il ne reste plus qu&apos;à tester :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;$oObject = new Child;
$oObject-&amp;gt;doSomething();
// affiche &amp;quot;GrandMother, value is 20&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;A utiliser avec précaution et modération ;-)&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>PHP 5.4 : les Traits (Horizontal Reuses)</title>
   <link href="https://blog.lepine.pro/php/php-5-4-les-traits-horizontal-reuses"/>
   <updated>2011-06-04T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/php/php-5-4-les-traits-horizontal-reuses</id>
   <content type="html">&lt;p&gt;PHP 5.4 offre son lot de nouveautés, dont les &lt;strong&gt;Traits&lt;/strong&gt;. Un trait permet d&apos;injecter dans une classe des méthodes d&apos;une ou plusieurs autres &quot;classes&quot; (des traits):&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;trait Color {
	public function getColor() {
		return &amp;#39;blue&amp;#39;;
	}
}

class Vehicle {}

class Car extends Vehicle {
	use Color;
}

$myCar = new Car;
echo $myCar-&amp;gt;getColor(); // blue&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;On voit ici l&apos;intérêt du trait : &lt;strong&gt;reporter sur une entité un aspect &quot;fonctionnel&quot; d&apos;une classe&lt;/strong&gt;. On peut imaginer une classe Car presque vide mais fonctionnelle :&lt;/p&gt;


&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;trait Color {
	public function getColor() {
		(...)
	}

	public function changeColor() {
		(...)
	}
}

trait Selling {
	use Price; // un trait peut utiliser un autre trait
	public function buy() {
		(...)
	}
}

class Car {
	use Color, Selling;
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;strong&gt;Attention toutefois&lt;/strong&gt; : les Traits rompent avec les aspects classique de la programmation par Interface (de l&apos;orienté objet donc) ou de la programmation par Contrat :
&lt;p&gt;la classe Car &lt;strong&gt;n&apos;implémente PAS une Interface&lt;/strong&gt; Color ou Selling, elles ne fait que &lt;strong&gt;reporter, utiliser, &lt;span style=&quot;text-decoration: underline;&quot;&gt;horizontalement&lt;/span&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Ce point est très important et dangereux, car en PHP un principe de base est la verticalité des classes et le respect du principe de substitution de Liskov : un objet qui travaille avec un autre objet Y doit pouvoir continuer à fonctionner de la même façon lorsqu&apos;on substitue à cet objet Y un autre objet Z de même Interface.&lt;/p&gt;
&lt;h2&gt;Un Trait peut s&apos;assurer de la présence d&apos;une méthode dans la classe&lt;/h2&gt;
&lt;p&gt;Il est possible de s&apos;assurer dans un Trait de la présence de certaines méthodes dans la classe qui l&apos;utilise :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;trait Selling {
	(...)
	abstract public function getCarBrand();
}
class Car {
	use Color, Selling;
	protected $_brand;

	public function getCarBrand() {
		return &amp;#39;Ma marque est &amp;#39;.$this-&amp;gt;_brand;
	}
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2&gt;Portée des méthodes&lt;/h2&gt;

&lt;p&gt;Il est possible de préciser certaines informations lors de la déclaration du trait dans une classe :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;trait Selling {
	(...)
	public function getConstructorPrice() {};
}

class Car {
	use Selling {
		getConstructorPrice as private // la méthode getCarBrand est rendue privée
	}
}

$myCar = new Car;
$myCar-&amp;gt;getConstructorPrice(); // Error&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2&gt;Priorité des Traits&lt;/h2&gt;

&lt;p&gt;Que faire si une classe utilisent des traits qui contiennent les mêmes méthodes ? Tout est prévu :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;trait Selling {
	public function exampleDuplicateMethod() {}
}
trait Color {
	public function exampleDuplicateMethod() {}
}
class Car {
	use Selling, Color {
		Color::exampleDuplicateMethod insteadof Selling;
	}
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2&gt;Cas pratique : le singleton&lt;/h2&gt;

&lt;p&gt;Il est temps de voir un cas pratique :-) Il est pratique en PHP de créer une classe abstraite Singleton, que l&apos;on serait tentée d&apos;hériter à chaque fois que l&apos;on a besoin d&apos;un singleton. Malheureusement, PHP ne permet pas l&apos;héritage multiple, et on se prive alors peut-être d&apos;un héritage fonctionnel plus intéressant pour notre classe. L&apos;utilisation des Traits résouts partiellement ce problème :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;trait Singleton {
	public static function getInstance() {
		(...)
	}
}

class Example extends MotherExample {
	use Singleton;
}
$obj = Example::getInstance();&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Pratique non ? (n&apos;oubliez pas qu&apos;un singleton c&apos;est plus que ça, là il ne s&apos;agit que d&apos;un exemple)&lt;/p&gt;
&lt;h2&gt;Et PHP 5.3 dans tout ça ?&lt;/h2&gt;
&lt;p&gt;Les Traits n&apos;existent pas en PHP 5.3, pourtant cette version du language risque d&apos;être longtemps utilisée. Il existe une astuce simple pour simuler ces traits en PHP 5.x : la relation de Composition et l&apos;utilisation massive des méthodes magiques :&lt;/p&gt;

&lt;p&gt;Nous allons commencer par créer notre classe Child1, en lui donnant un tableau (nommé $_tTraits) qui contiendra la liste des classes dont on souhaite qu&apos;elle puisse les utiliser comme des Traits.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;class Trait1{
    public function method1() {
        echo &amp;#39;Cette fonction est dans le Trait 1&amp;#39;;
    }
}
class Trait2 {
    public $attribute2 = &amp;#39;demo&amp;#39;;
    public function method2() {
        echo &amp;#39;Cette fonction est dans le Trait 2&amp;#39;;
    }
}

class Child extends Parent1 {
    private $_tTraits  = array(&amp;#39;Trait1&amp;#39;, &amp;#39;Trait2&amp;#39;);
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Maintenant, nous allons automatiquement instancier ces classes lors de la construction de l&apos;objet. Ces instances seront stockées, nous nous en serviront pour reporter les actions effectuées sur la classe vers les classes Traits.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;class Child extends Parent1 {
    private $_tTraits = array(&amp;#39;Trait1&amp;#39;, &amp;#39;Trait2&amp;#39;);
    private $_tTraitsInstances   = array();  // ce tableau contient toutes les instances créées par le constructeur

    /**
     * Constructeur
     * création des instances de chaque classe Trait
     */
    public function __construct() {
        // ::::: build instance for each Trait class :::::
        foreach($this-&amp;gt;_tTraits as $className) {
		$this-&amp;gt;_tTraitsInstances[] = new $className;
	}
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Ensuite, nous allons reporter chaque appel de méthode, si elle n&apos;existe pas dans Child1, vers l&apos;un de ses Traits, si cette méthode existe.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;/**
 * Méthode magique __call()
 * On va reporter chaque appel sur une des instances des classes mères
 * @param string $funcName
 * @param array $tArgs
 * @return mixed
 */
public function __call($funcName, $tArgs) {
    foreach($this-&amp;gt;_tTraitsInstances as &amp;amp;$object) {
        if(method_exists($object, $funcName)) {
		return call_user_func_array(array($object, $funcName), $tArgs);
	}
    }
    throw new Exception(&amp;quot;The $funcName method doesn&amp;#39;t exist&amp;quot;);
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Désormais, tout appel de méthode de Child1 est reporté, si elle n&apos;existe pas dans Child1 même, vers une de ses classes Traits. Il est donc possible de surcharger une méthode de ces classes Traits simplement en la déclarant dans Child1.&lt;/p&gt;

&lt;p&gt;Enfin, nous allons reporter toutes les lectures d&apos;attributes (accesseurs) vers les attributs des instances des classes Traits:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;/**
 * Méthode magique __get()
 * On va reporter chaque lecture d&amp;#39;attribut (accesseur) sur une des instances des classes mères
 * @param string $varName
 * @return mixed
 */
public function __get($varName) {
    foreach($this-&amp;gt;_tTraitsInstances as &amp;amp;$object) {
        $tDefinedVars   = get_defined_vars($object);
        if(property_exists($object, $funcName)) return $object-&amp;gt;{$varName};
    }
    throw new Exception(&amp;quot;The $varName attribute doesn&amp;#39;t exist&amp;quot;);
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Pour au final avoir:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;class Child extends Parent1 {
	private $_tTraits          = array(&amp;#39;Trait1&amp;#39;, &amp;#39;Trait2&amp;#39;);
	private $_tTraitsInstances   = array();

	/**
	* Constructeur
	* création des instances de chaque classe Trait
	*/
	public function __construct() {
	// ::::: build instance for each Trait class :::::
	foreach($this-&amp;gt;_tTraits as $className) {
		$this-&amp;gt;_tTraitsInstances[] = new $className;
	}
	}

	/**
	 * Méthode magique __call()
	 * On va reporter chaque appel sur une des instances des classes mères
	 * @param string $funcName
	 * @param array $tArgs
	 * @return mixed
	 */
	public function __call($funcName, $tArgs) {
	    foreach($this-&amp;gt;_tTraitsInstances as &amp;amp;$object) {
		if(method_exists($object, $funcName)) {
			return call_user_func_array(array($object, $funcName), $tArgs);
		}
	    }
	    throw new Exception(&amp;quot;The $funcName method doesn&amp;#39;t exist&amp;quot;);
	}

	/**
	 * Méthode magique __get()
	 * On va reporter chaque lecture d&amp;#39;attribut (accesseur) sur une des instances des classes mères
	 * @param string $varName
	 * @return mixed
	 */
	public function __get($varName) {
	    foreach($this-&amp;gt;_tTraitsInstances as &amp;amp;$object) {
		$tDefinedVars   = get_defined_vars($object);
		if(property_exists($object, $funcName)) return $object-&amp;gt;{$varName};
	    }
	    throw new Exception(&amp;quot;The $varName attribute doesn&amp;#39;t exist&amp;quot;);
	}
}&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Nous avons tout ce qu&apos;il nous faut pour pouvoir utiliser notre classe :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;$oObject = new Child;

// appel d&amp;#39;une méthode d&amp;#39;un Trait
$oObject-&amp;gt;method2(); // affiche &amp;quot;Cette fonction est dans le Trait 1&amp;quot;

// lecture d&amp;#39;une variable
echo $oObject-&amp;gt;attribute2; // affiche &amp;quot;demo&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Vous voilà prêt à utiliser les Traits en PHP :-) Pour plus d&apos;info, vous pouvez consulter la &lt;a href=&quot;https://wiki.php.net/rfc/horizontalreuse&quot;&gt;RFC de PHP sur les Traits&lt;/a&gt;.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>plus rapide et facile qu'un array_merge : l'opérateur +</title>
   <link href="https://blog.lepine.pro/php/astuces-php-union-de-deux-tableaux-plus-pratique-et-rapide-quun-array_merge"/>
   <updated>2011-03-04T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/php/astuces-php-union-de-deux-tableaux-plus-pratique-et-rapide-quun-array_merge</id>
   <content type="html">&lt;p&gt;Aujourd&apos;hui, j&apos;ai envie de partager avec vous une astuce PHP peu connue, mais qui est bien pratique : l&apos;union (+) de deux tableaux&lt;/p&gt;

&lt;p&gt;Utiliser la fonction array_merge est un vrai casse-tête quand il s&apos;agit  de fusionner des tableaux en préservant les clefs.&lt;/p&gt;

&lt;p&gt;Or il existe un opérateur bien pratique : &lt;strong&gt;l&apos;opérateur +&lt;/strong&gt;, qui fusionne deux tableaux :&lt;!--more--&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;$t1 = array(&amp;#39;a&amp;#39;,&amp;#39;b&amp;#39;,&amp;#39;d&amp;#39;);
$t2 = array(3 =&amp;gt; &amp;#39;f&amp;#39;,4 =&amp;gt; &amp;#39;g&amp;#39;);
$t3 = $t1 + $t2;
print_r($t3);
&amp;lt;p&amp;gt;Array&amp;lt;/p&amp;gt;
(
    [0] =&amp;gt; a
    [1] =&amp;gt; b
    [2] =&amp;gt; d
    [3] =&amp;gt; f
    [4] =&amp;gt; g
)&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Pratique non ?&lt;/p&gt;

&lt;p&gt;Et en plus, utiliser l&apos;opérateur + est légèrement (à peine) plus rapide qu&apos;un array_merge classique (sur 10 000 occurrences de fusion de deux petits tableaux en PHP 5.3, sous Ubuntu, j&apos;obtiens un très léger écart de 0.05 secondes ^^)&lt;/p&gt;

&lt;p&gt;Attention, le comportement en cas de clefs communes est différent entre array_merge et + :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;$array1 = array(
    &amp;#39;brand&amp;#39; =&amp;gt; &amp;#39;peugeot&amp;#39;,
    &amp;#39;car&amp;#39; =&amp;gt; &amp;#39;206&amp;#39;,
    &amp;#39;color&amp;#39; =&amp;gt; &amp;#39;red&amp;#39;
);
$array2 = array(
    &amp;#39;brand&amp;#39; =&amp;gt; &amp;#39;renault&amp;#39;,
    &amp;#39;car&amp;#39; =&amp;gt; &amp;#39;scenic&amp;#39;,
    &amp;#39;color&amp;#39; =&amp;gt; &amp;#39;blue&amp;#39;
);&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Avec array_merge : la dernière valeur rencontrée écrase la précédente :&lt;/p&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;$array4 = array_merge($array1, $array2);
print_r($array4);
Array
(
    [brand] =&amp;gt; renault
    [car] =&amp;gt; scenic
    [color] =&amp;gt; blue
)&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Avec l&apos;opérateur +, la première valeur est conservée :&lt;/p&gt;
&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-php&quot; data-lang=&quot;php&quot;&gt;$array3 = $array1 + $array2;
print_r($array3);
Array
(
    [brand] =&amp;gt; peugeot
    [car] =&amp;gt; 206
    [color] =&amp;gt; red
)&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;em&gt;Merci à gege2061 de m&apos;avoir fait remarquer une absence dans ce billet, désormais corrigée ;-)&lt;/em&gt;
</content>
 </entry>
 
 <entry>
   <title>Performance PHP : l'héritage</title>
   <link href="https://blog.lepine.pro/php/performance-php-lheritage"/>
   <updated>2011-02-25T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/php/performance-php-lheritage</id>
   <content type="html">&lt;p&gt;Quand on parle d&apos;orienté objet, une crainte récurrente est celle de la performance.Or le moteur PHP Objet est très bien optimisé :&lt;/p&gt;

(les benchs suivants portent sur des moyennes de tests executés 10 000 fois minimum, sous Linux en PHP 5.3.3 standard, et sont exprimés en secondes)
&lt;h2&gt;Héritage&lt;/h2&gt;
&lt;p&gt;Contrairement aux idées reçus, instancier une classe qui en hérite de plusieurs autres n&apos;est pas plus lent qu&apos;instancier une classe mère :&lt;/p&gt;
&lt;h3&gt;Appel de classes statiques (secondes):&lt;/h3&gt;
&lt;!-- body, div, table, thead, tbody, tfoot, tr, th, td, p { font-family: &quot;Arial&quot;; font-size: x-small; } --&gt;
&lt;table style=&quot;height: 56px;&quot; border=&quot;0&quot; cellspacing=&quot;0&quot; width=&quot;312&quot; frame=&quot;VOID&quot; rules=&quot;NONE&quot;&gt;&lt;colgroup&gt;&lt;col width=&quot;95&quot;&gt;&lt;/col&gt;&lt;col width=&quot;95&quot;&gt;&lt;/col&gt;&lt;col width=&quot;108&quot;&gt;&lt;/col&gt;&lt;/colgroup&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td width=&quot;95&quot; height=&quot;17&quot; align=&quot;CENTER&quot; bgcolor=&quot;#333333&quot;&gt;&lt;strong&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Occurrences&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td width=&quot;95&quot; align=&quot;CENTER&quot; bgcolor=&quot;#333333&quot;&gt;&lt;strong&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;4 héritages&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td width=&quot;108&quot; align=&quot;CENTER&quot; bgcolor=&quot;#333333&quot;&gt;&lt;strong&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;aucun héritage&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td height=&quot;17&quot; align=&quot;RIGHT&quot; bgcolor=&quot;#333333&quot;&gt;&lt;strong&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;10000&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;RIGHT&quot;&gt;0,7106118202&lt;/td&gt;
&lt;td align=&quot;RIGHT&quot;&gt;0,7099120617&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td height=&quot;17&quot; align=&quot;RIGHT&quot; bgcolor=&quot;#333333&quot;&gt;&lt;strong&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;1000&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;RIGHT&quot;&gt;0,0733890533&lt;/td&gt;
&lt;td align=&quot;RIGHT&quot;&gt;0,0721051693&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;

&lt;a href=&quot;https://blog.lepine.pro/images/2011-02-php-5-3-performance-heritage1.gif&quot;&gt;&lt;img class=&quot;size-medium wp-image-23&quot; title=&quot;php-5-3-performance-heritage1&quot; src=&quot;https://blog.lepine.pro/images/2011-02-php-5-3-performance-heritage1gif&quot; alt=&quot;test de Performance PHP 5.3 : héritage et appels statiques&quot; width=&quot;300&quot; height=&quot;232&quot; /&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;h3&gt;Appel de classes instanciées (secondes):&lt;/h3&gt;
&lt;!-- body, div, table, thead, tbody, tfoot, tr, th, td, p { font-family: &quot;Arial&quot;; font-size: x-small; } --&gt;
&lt;table border=&quot;0&quot; cellspacing=&quot;0&quot; frame=&quot;VOID&quot; rules=&quot;NONE&quot;&gt;&lt;colgroup&gt;&lt;col width=&quot;95&quot;&gt;&lt;/col&gt;&lt;col width=&quot;95&quot;&gt;&lt;/col&gt;&lt;col width=&quot;108&quot;&gt;&lt;/col&gt;&lt;/colgroup&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td width=&quot;95&quot; height=&quot;17&quot; align=&quot;CENTER&quot; bgcolor=&quot;#333333&quot;&gt;&lt;strong&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;Occurrences&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td width=&quot;95&quot; align=&quot;LEFT&quot; bgcolor=&quot;#333333&quot;&gt;&lt;strong&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;4 héritages&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td width=&quot;108&quot; align=&quot;LEFT&quot; bgcolor=&quot;#333333&quot;&gt;&lt;strong&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;aucun héritage&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td height=&quot;17&quot; align=&quot;RIGHT&quot; bgcolor=&quot;#333333&quot;&gt;&lt;strong&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;10000&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;RIGHT&quot;&gt;0,7323720455&lt;/td&gt;
&lt;td align=&quot;RIGHT&quot;&gt;0,7246758938&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td height=&quot;17&quot; align=&quot;RIGHT&quot; bgcolor=&quot;#333333&quot;&gt;&lt;strong&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;1000&lt;/span&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;RIGHT&quot;&gt;0,0770690441&lt;/td&gt;
&lt;td align=&quot;RIGHT&quot;&gt;0,0738449097&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;a href=&quot;https://blog.lepine.pro/images/2011-02-php-5-3-performance-heritage-instances.gif&quot;&gt;&lt;img class=&quot;size-full wp-image-24&quot; title=&quot;php-5-3-performance-heritage-instances&quot; src=&quot;https://blog.lepine.pro/images/2011-02-php-5-3-performance-heritage-instances.gif&quot; alt=&quot;test de Performance PHP 5.3 : héritage et appels sur des instances&quot; width=&quot;296&quot; height=&quot;292&quot; /&gt;&lt;/a&gt;

&lt;p&gt;La différence est négligeable, même sur une grosse application.&lt;/p&gt;

&lt;p&gt;En conclusion, l&apos;utilisation massive de l&apos;héritage ne nuit pas à la performance d&apos;une application...&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Forum AFUP : Slides disponibles</title>
   <link href="https://blog.lepine.pro/php/forum-afup-slides-disponibles"/>
   <updated>2010-11-17T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/php/forum-afup-slides-disponibles</id>
   <content type="html">&lt;p&gt;Tous les slides des conférences AFUP 2010 sont disponibles. Ca se passe ici : &lt;a href=&quot;http://www.afup.org/pages/forumphp2010/resumes.php&quot;&gt;Slides Forum AFUP 2010&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;img class=&quot;aligncenter&quot; title=&quot;Forum AFUP&quot; src=&quot;http://www.afup.org/templates/forumphp2010/images/logo_afup.png&quot; alt=&quot;&quot; width=&quot;150&quot; height=&quot;71&quot; /&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Forum AFUP 2010</title>
   <link href="https://blog.lepine.pro/php/forum-afup-2010"/>
   <updated>2010-11-14T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/php/forum-afup-2010</id>
   <content type="html">&lt;p&gt;Le forum AFUP c&apos;est quoi ? C&apos;est LE lieu de rencontre des professionnels PHP à ne pas manquer. Et cette année, on fêtait les 15 ans de PHP !&lt;/p&gt;

&lt;p&gt;On a eu le droit à des grands noms, comme Rasmus Lerdof, Zeev Suraski (respectivement créateur de PHP et de Zend quand même ! Si si, pour la petite anecdote si beaucoup de fonctions PHP en C sont préfixées par zend_ c&apos;est pour &lt;strong&gt;ZE&lt;/strong&gt;ev Surasky et a&lt;strong&gt;ND&lt;/strong&gt;y Gutmans, l&apos;autre fondateur de Zend).&lt;/p&gt;

&lt;p&gt;Et aussi d&apos;autres noms moins connus mais qu&apos;il est bon de connaître et suivre en France : Julien Pauli, Pascal Martin, Eric Daspet...&lt;/p&gt;

&lt;p&gt;Bref, un forum intéressant, je pense essayer d&apos;y retourner l&apos;année prochaine ! En attendant je vous prépare une petite synthèse des conférences que j&apos;ai préferées...&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Forum AFUP 2010 : Plein PHAR</title>
   <link href="https://blog.lepine.pro/php/forum-afup-2010-plein-phar"/>
   <updated>2010-11-14T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/php/forum-afup-2010-plein-phar</id>
   <content type="html">&lt;p&gt;Première conférence intéressante : &lt;strong&gt;Plein Phar, de Fréderic Hardy&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Je vous livre mes notes en vrac, en espérant que ce soit lisible :&lt;!--more--&gt;&lt;/p&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Phar = JAR en Java (archive qui regroupe et éventuellement compresse plusieurs fichiers en un)&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;Concaténer plusieurs fichiers en 1&lt;/li&gt;
	&lt;li&gt;Compression (zLib, Tar…)&lt;/li&gt;
	&lt;li&gt;Sécurité : openSSL possible, signatures&lt;/li&gt;
	&lt;li&gt;Executable ou non&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Peut être inclus très facilement en PHP :&lt;/p&gt;

&lt;p&gt;require &quot;phar://...&quot;;&lt;/p&gt;
&lt;h2&gt;Structure&lt;/h2&gt;
&lt;p&gt;Un fichier Pahr contient:&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;Un fichier de démarrage (&lt;strong&gt;Stub&lt;/strong&gt;) en PHP
&lt;ul&gt;
	&lt;li&gt;Environnement&lt;/li&gt;
	&lt;li&gt;Autoload&lt;/li&gt;
	&lt;li&gt;Configuration&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
	&lt;li&gt;Un &lt;strong&gt;manifeste &lt;/strong&gt;(structure)&lt;/li&gt;
	&lt;li&gt;les &lt;strong&gt;fichiers&lt;/strong&gt;&lt;/li&gt;
	&lt;li&gt;[Éventuellement une &lt;strong&gt;Signature&lt;/strong&gt;]&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Métadonnées&lt;/h2&gt;
&lt;p&gt;Utilisation possible de metadonnées (un peu comme les annotations). On peut y mettre n&apos;importe quoi : Auteur, release, version…&lt;/p&gt;

&lt;p&gt;Je pense que ça peut être très pertinent de les utiliser pour gérer les versions : si avec un SVN c&apos;est assez simple, le SVN ne fonctionne que dans un seul sens (on connaît la version en amont). Pour un serveur qui déploie souvent, sur des serveurs clients dont il n&apos;est pas maître, prévoir une surcouche des versions par le Phar permet de mettre en place des systèmes de mise à jour automatique... Bref, une piste à creuser.&lt;/p&gt;
&lt;h2&gt;Compression&lt;/h2&gt;
&lt;p&gt;3 extensions :&lt;/p&gt;
-    Phar
-    PharTar
-    PharZip
&lt;p&gt;Il est possible de créer ses propres extensions, mais il vaut mieux utiliser ces 3 principales&lt;/p&gt;
&lt;h2&gt;Point de montage&lt;/h2&gt;
&lt;p&gt;On peut monter dans le phar des fichiers externes (mount)&lt;/p&gt;
&lt;h2&gt;Test unitaires&lt;/h2&gt;
&lt;p&gt;A la création, le stub n’est pas testé. Une fois le phar créé, les erreurs sont difficiles à gérer et opaques.&lt;/p&gt;

&lt;p&gt;On peut utiliser des tests unitaires pour :&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt; S’assurer que les données sont complètes (utilisation de la signature)&lt;/li&gt;
	&lt;li&gt;S’assurer des injections de dépendance&lt;/li&gt;
	&lt;li&gt;En utilisant des mocks (objets répliquant virtuels d’objets, qui permettent de modifier son comportement. Ex : créer un mock d’une connexion mysql)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Performance&lt;/h2&gt;
&lt;p&gt;Varie selon l’environnement.&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;Compressé : chute des perfs de 10/15%. C’est résolu par l’utilisation de phar.cache_list. (mais on a évidemment de l&apos;autre côté des gains de Ram)&lt;/li&gt;
	&lt;li&gt;Standard : baisse de 2/3%&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Phar est compatible avec APC (et ça c&apos;est une bonne chose ^^)&lt;/p&gt;
&lt;h2&gt;Obfuscation&lt;/h2&gt;
&lt;p&gt;Un phar n’est pas obfuscé ! Il ne faut pas compter dessus pour protéger le code (même s&apos;il n&apos;existe aucune solution ultime, Phar n&apos;en est pas du tout une)&lt;/p&gt;
&lt;h2&gt;Déploiement et SVN&lt;/h2&gt;
&lt;p&gt;Une bonne idée : il suffit d’utiliser Hudson pour regénerer le Phar à chaque commit&lt;/p&gt;
&lt;h2&gt;Avec le Zend Framework&lt;/h2&gt;
&lt;p&gt;On peut imaginer mettre tous les fichiers du ZF dans un Phar, puis mettre le controller frontal dans le Stub.&lt;/p&gt;
&lt;h2&gt;Ressources&lt;/h2&gt;
&lt;p&gt;Un bon tutoriel (et au passage un bon blog) : &lt;a href=&quot;http://blog.pascal-martin.fr/post/php-5.3-phar-php-archive&quot;&gt;http://blog.pascal-martin.fr/post/php-5.3-phar-php-archive&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Voici mes notes en vrac. Cette conférence m&apos;a donnée pas mal d&apos;idée, je pense que je vais m&apos;en servir dès lundi. Prochaine étape la prod&apos; ?&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Un blog est né (encore!)</title>
   <link href="https://blog.lepine.pro/non-classe/bonjour-tout-le-monde"/>
   <updated>2010-11-14T00:00:00+00:00</updated>
   <id>https://blog.lepine.pro/non-classe/bonjour-tout-le-monde</id>
   <content type="html">&lt;p&gt;Aujourd&apos;hui, ouverture de mon blog. Développeur PHP à plein temps et fan de SEO à mes heures, je compte vous proposer une synthèse de ce que je pense pertinent de connaître, aussi bien en veille qu&apos;en contenu de fond.&lt;/p&gt;

&lt;p&gt;J&apos;espère que cela vous plaira ! ^^&lt;/p&gt;
</content>
 </entry>
 
 
</feed>
