<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="https://ktherage.github.io/fr/xsl/atom.xsl" media="all"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="fr">
  <id>https://ktherage.github.io/fr/tags/phpunit/</id>
  <title>Kévin THÉRAGE | Expert Symfony Developer - PHPUnit</title>
  <subtitle><![CDATA[Kévin THÉRAGE – Expert Symfony Developer. Technical blog on Symfony, PHP, web development with tutorials, best practices and expert advice for developers.]]></subtitle>
  <link href="https://ktherage.github.io/fr/tags/phpunit/atom.xml" rel="self" type="application/atom+xml" />
  <link href="https://ktherage.github.io/fr/tags/phpunit/" rel="alternate" type="text/html" />
  <updated>2026-06-18T15:17:31+00:00</updated>
  <author>
    <name>Kévin THÉRAGE</name>
    <uri>https://ktherage.github.io/</uri>
  </author>
  <entry xml:lang="fr">
    <id>https://ktherage.github.io/fr/blog/dealing_with_isolated_phpstan_1_and_the_phpunit_13_blindspot/</id>
    <title>Gérer l&#039;isolation de PHPStan 1 et l&#039;angle mort de PHPUnit 13</title>
    <published>2026-05-29T00:00:00+00:00</published>
    <link href="https://ktherage.github.io/fr/blog/dealing_with_isolated_phpstan_1_and_the_phpunit_13_blindspot/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<p>Nous adorons les outils de développement isolés. Placer PHPStan, Rector ou PHP CS Fixer dans des sous-répertoires distincts comme <code translate="no">.tools/phpstan/</code> avec leur propre <code translate="no">composer.json</code> est un excellent moyen d'éviter l'enfer des dépendances dans votre projet racine.</p>
<p>Jusqu'à ce que cela rende votre pipeline d'analyse complètement aveugle.</p>
<p>Si vous êtes récemment passé à <strong>PHPUnit 13</strong> et que votre analyse statique a soudainement déraillé avec des erreurs fantômes du type <code translate="no">unknown class PHPUnit\Framework\TestCase</code>, vous vous êtes heurté à un mur d'isolation classique. Voyons pourquoi cela ne fonctionne plus et comment corriger le tir proprement.</p>
<hr>
<h2 id="le-symptome">Le Symptôme</h2>
<p>Votre suite de tests s'exécute dans Docker. Tout passe haut la main. Chaque assertion est au vert.
Pourtant, dès que vous lancez PHPStan, votre terminal explose :</p>
<pre><code class="language-text" translate="no"> ------ ---------------------------------------------------------------------------- 
  Line   tests/Client/FakeClientTest.php                                             
 ------ ---------------------------------------------------------------------------- 
  12     Class App\Tests\Client\FakeClientTest extends unknown class                 
         PHPUnit\Framework\TestCase.                                                 
         💡 Learn more at https://phpstan.org/user-guide/discovering-symbols         
  30     Call to an undefined static method                                          
         App\Tests\Client\FakeClientTest::assertInstanceOf().                        
 ------ ---------------------------------------------------------------------------- </code></pre>
<p>Vous jetez un œil à votre fichier <code translate="no">phpstan.neon.dist</code>. Vous avez pourtant déjà fait le pont en indiquant à PHPStan où trouver l'autoloader du projet :</p>
<pre><code class="language-yaml hljs yaml" translate="no"><span class="hljs-attr">parameters:</span>
    <span class="hljs-attr">level:</span> <span class="hljs-string">max</span>
    <span class="hljs-attr">paths:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">src/</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">tests/</span>
    <span class="hljs-attr">bootstrapFiles:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">vendor/autoload.php</span></code></pre>
<p>Vous double-vérifiez même l'autoloader manuellement via PHP :</p>
<pre><code class="language-bash hljs bash" translate="no">php -r <span class="hljs-string">"require 'vendor/autoload.php'; echo class_exists('PHPUnit\Framework\TestCase') ? '🟢 OUI' : '🔴 NON';"</span></code></pre>
<p>La console renvoie <code translate="no">🟢 OUI</code>. La classe est bien là. Alors, pourquoi PHPStan est-il aveugle ?</p>
<hr>
<h2 id="pourquoi-cela-fonctionnait-il-tres-bien-avec-phpunit-10">Pourquoi cela fonctionnait-il très bien avec PHPUnit 10 ?</h2>
<p>Si vous avez exactement cette même configuration sur un projet plus ancien tournant sous PHPUnit 9.5, tout fonctionne sans accroc. Qu'est-ce qui a changé ?</p>
<h3 id="1-le-virage-architectural-de-phpunit-10">1. Le virage architectural de PHPUnit 10+</h3>
<p>Dans PHPUnit 9, <code translate="no">TestCase</code> était une classe plutôt monolithique. Le moteur de réflexion statique de PHPStan (<code translate="no">BetterReflection</code>) n'avait aucun mal à la cartographier depuis un répertoire externe.</p>
<p>Avec PHPUnit 10 (et jusqu'à la v13), le framework a été entièrement refactorisé. <code translate="no">TestCase</code> s'appuie désormais sur un réseau complexe d'interfaces et de traits internes. Lorsque PHPStan tente de l'inspecter à distance au-delà des frontières du répertoire via un autoloader bootstrappé, le moteur de réflexion se perd dans l'arbre d'héritage et considère prudemment que la classe n'existe pas.</p>
<h3 id="2-le-piege-de-l-extension-obsolete">2. Le piège de l'extension obsolète</h3>
<p>Si vous regardez le fichier <code translate="no">.tools/phpstan/composer.json</code> de votre outil isolé, vous y trouverez probablement une contrainte héritée d'un ancien boilerplate de projet :</p>
<pre><code class="language-json hljs json" translate="no"><span class="hljs-string">"require"</span>: {
    <span class="hljs-attr">"phpstan/phpstan"</span>: <span class="hljs-string">"*"</span>,
    <span class="hljs-attr">"phpstan/phpstan-phpunit"</span>: <span class="hljs-string">"^1.1"</span>
}</code></pre>
<p>Cette contrainte <code translate="no">^1.1</code> verrouille l'extension PHPUnit sur sa <strong>branche 1.x</strong>, historiquement conçue pour PHPUnit 9. L'extension étant bloquée en v1.x, Composer fige silencieusement le cœur de <code translate="no">phpstan/phpstan</code> dans une version obsolète elle aussi (comme la <code translate="no">1.12.x</code>), ignorant complètement votre wildcard <code translate="no">*</code>. Vous vous retrouvez concrètement à analyser du code moderne sous PHPUnit 13 avec un moteur daté.</p>
<hr>
<h2 id="la-solution-propre-abandonner-les-contraintes-obsoletes">La solution propre : abandonner les contraintes obsolètes</h2>
<p>Plutôt que de vous battre avec les chemins via <code translate="no">scanDirectories</code> ou d'installer une copie factice de PHPUnit dans le répertoire de vos outils, mettez simplement à jour votre chaîne d'outils. <strong>PHPStan 2.0</strong> et son extension <strong>phpstan-phpunit 2.0</strong> gèrent nativement l'architecture complexe du PHPUnit moderne.</p>
<h3 id="1-passer-a-la-v2">1. Passer à la v2</h3>
<p>Ouvrez <code translate="no">.tools/phpstan/composer.json</code> et forcez la mise à jour :</p>
<pre><code class="language-json hljs json" translate="no">{
    <span class="hljs-attr">"require"</span>: {
        <span class="hljs-attr">"php"</span>: <span class="hljs-string">"&gt;=8.4"</span>,
        <span class="hljs-attr">"phpstan/phpstan"</span>: <span class="hljs-string">"^2.0"</span>,
        <span class="hljs-attr">"phpstan/phpstan-phpunit"</span>: <span class="hljs-string">"^2.0"</span>
    },
    <span class="hljs-attr">"config"</span>: {
        <span class="hljs-attr">"bin-dir"</span>: <span class="hljs-string">"./"</span>,
        <span class="hljs-attr">"sort-packages"</span>: <span class="hljs-literal">true</span>
    }
}</code></pre>
<h3 id="2-rafraichir-l-environnement">2. Rafraîchir l'environnement</h3>
<p>Lancez une mise à jour dans le répertoire de votre outil pour reconstruire le fichier lock :</p>
<pre><code class="language-bash hljs bash" translate="no"><span class="hljs-built_in">cd</span> .tools/phpstan &amp;&amp; composer update</code></pre>
<h3 id="3-vider-le-cache-et-analyser">3. Vider le cache et analyser</h3>
<p>Assurez-vous que votre fichier <code translate="no">phpstan.neon.dist</code> utilise bien la variable de chemin absolu pour cibler le répertoire vendor racine de manière sécurisée :</p>
<pre><code class="language-yaml hljs yaml" translate="no"><span class="hljs-attr">parameters:</span>
    <span class="hljs-attr">level:</span> <span class="hljs-string">max</span>
    <span class="hljs-attr">paths:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">src/</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">tests/</span>
    <span class="hljs-attr">bootstrapFiles:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">%currentWorkingDirectory%/vendor/autoload.php</span>

<span class="hljs-attr">includes:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">.tools/phpstan/vendor/phpstan/phpstan-phpunit/extension.neon</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">.tools/phpstan/vendor/phpstan/phpstan-phpunit/rules.neon</span></code></pre>
<p>Supprimez l'ancien cache d'analyse pour éviter les résultats obsolètes :</p>
<pre><code class="language-bash hljs bash" translate="no">.tools/phpstan/phpstan clear-result-cache</code></pre>
<p>Relancez votre analyseur. Les erreurs fantômes vont disparaître et vous retrouverez vos indicateurs au vert, sans pour autant dégrader l'architecture isolée de vos outils.</p>]]>
    </content>
  </entry>
</feed>
