<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://staabm.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://staabm.github.io/" rel="alternate" type="text/html" /><updated>2026-04-10T00:30:30+00:00</updated><id>https://staabm.github.io/feed.xml</id><title type="html">My developer experience</title><subtitle>Personal blog, about my open source activities.</subtitle><author><name>Markus Staab</name></author><entry><title type="html">Automate the process to find a regression commit</title><link href="https://staabm.github.io/2026/02/07/git-bisect-run.html" rel="alternate" type="text/html" title="Automate the process to find a regression commit" /><published>2026-02-07T00:00:00+00:00</published><updated>2026-02-07T00:00:00+00:00</updated><id>https://staabm.github.io/2026/02/07/git-bisect-run</id><content type="html" xml:base="https://staabm.github.io/2026/02/07/git-bisect-run.html"><![CDATA[<p>The first thing after someone reported a regression bug is to find out when it broke.
After finding the regression commit it’s easier to reason about the problem context and explore a new fix.</p>

<p>In the past I used <a href="https://git-scm.com/docs/git-bisect"><code>git bisect</code></a> to run the process and get to the source of the problem one step at a time.</p>

<p>Recently I learned this process can be automated using the <code>git bisect run</code> subcommand.
While the following procedure works in any Git based project, I will walk you through a real world PHPStan example.</p>

<h2 id="find-a-regression-commit---the-manual-way">Find a regression commit - the manual way</h2>

<p>Preparation: Put your reproducer code into <code>bug-xyz.php</code> in the phpstan-src root dir and run <code>bin/phpstan bug-xyz.php</code> on it to verify/observe the faulty behavior.</p>

<p>Usually running a bisect process works like</p>

<ol>
  <li>Initialize the process: <code>git bisect start</code>.</li>
  <li>Tell the tooling about known working and non-working commits using <code>git bisect bad &lt;your commit&gt;</code> and <code>git bisect good &lt;your commit&gt;</code>.</li>
  <li>Run <code>composer install</code> to build PHPStan with its dependencies for the current commit.</li>
  <li>Run <code>bin/phpstan bug-xyz.php</code> and observe whether PHPStan works like expected. If the faulty behavior can be observed execute <code>git bisect bad</code>. If it works like expected execute <code>git bisect good</code>.</li>
  <li>Git will automatically checkout a new commit. You start with step 3 again and do these manual steps until Git tells you that it found the “first bad commit”.</li>
</ol>

<p>While this process works good, it requires a lot of manual input and might take a while until you finally find the regression commit.
Some people might even not use this process because it requires a lot of boring manual commands.</p>

<h2 id="find-a-regression-commit---automated-git-bisect-run">Find a regression commit - Automated <code>git bisect run</code></h2>

<p>Automating the process requires a bit more preparation work, but usually it will save you a lot of time in the bisection process.
It also takes away possible human errors in the process, which can be frustrating when e.g. mistakenly mixing up good and bad commits.</p>

<p>After having it setup once you can easily adapt the involved files and apply the idea to different code examples.</p>

<h3 id="preparation">Preparation</h3>

<p>Put your reproducer code into <code>bug-xyz.php</code> in the phpstan-src root dir and run <code>bin/phpstan bug-xyz.php</code> on it to verify/observe the faulty behavior.</p>

<h4 id="variant-a-find-a-phpstan-false-positive-error">Variant A: Find a PHPStan false-positive error</h4>

<p>In this case we want to find the regression commit in which PHPStan started to emit a wrong error message which it did not emit in previous versions.</p>

<p>Create a file <code>test.sh</code> in the phpstan-src root dir:</p>
<pre><code>#!/bin/bash

composer install
PHPSTAN_FNSR=1 php bin/phpstan analyze bug-xyz.php --debug |grep "Expected type object, actual: mixed"
</code></pre>

<p>run <code>./test.sh</code> and check whether the script returns as expected using <code>echo $?</code>.
exit-code should be <code>0</code> when everything went well and non-0 otherwise.</p>

<h4 id="variant-b-find-a-phpstan-false-negative-error">Variant B: Find a PHPStan false-negative error</h4>

<p>In this case we want to find the regression commit in which PHPStan started to no longer report an expected error message.</p>

<p>Create a file <code>test.sh</code> in the phpstan-src root dir:</p>
<pre><code>#!/bin/bash

composer install
PHPSTAN_FNSR=1 php bin/phpstan analyze bug-xyz.php --debug |grep "Expected type object, actual: mixed"
test $? -eq 1 # error when grep did not match
</code></pre>

<p>run <code>./test.sh</code> and check whether the script returns as expected using <code>echo $?</code>.
exit-code should be <code>0</code> when everything went well and non-0 otherwise.</p>

<h3 id="run-git-bisect">Run <code>git bisect</code></h3>

<p>After we prepared a <code>bug-xyz.php</code> and a <code>test.sh</code> in the previous steps,
we can now instruct <code>git bisect</code> to start the bisection process utilizing <code>test.sh</code> to inform Git whether the bug can be observed or not.
The process might output a big wall of text while working through the Git history.</p>

<p>As usual run <code>git bisect start</code> and let it now first good and bad commits with <code>git bisect good</code> and <code>git bisect bad</code>.
Now run <code>git bisect run ./test.sh</code> and it will run on its own until it finds the first bad commit:</p>

<pre><code>git bisect run ./test.sh
.
.
.
[8612d23273cae722be5d8cfd4e559357edf775a7] Split MutatingScope-&gt;resolveType() into smaller methods - part 2 (#4760)
running './test.sh'
Composer could not detect the root package (phpstan/phpstan-src) version, defaulting to '1.0.0'. See https://getcomposer.org/root-version
Gathering patches for root package.
Installing dependencies from lock file (including require-dev)
Verifying lock file contents can be installed on current platform.
Nothing to install, update or remove
Package hoa/compiler is abandoned, you should avoid using it. No replacement was suggested.
Package hoa/consistency is abandoned, you should avoid using it. No replacement was suggested.
Package hoa/event is abandoned, you should avoid using it. No replacement was suggested.
Package hoa/exception is abandoned, you should avoid using it. No replacement was suggested.
Package hoa/file is abandoned, you should avoid using it. No replacement was suggested.
Package hoa/iterator is abandoned, you should avoid using it. No replacement was suggested.
Package hoa/math is abandoned, you should avoid using it. No replacement was suggested.
Package hoa/protocol is abandoned, you should avoid using it. No replacement was suggested.
Package hoa/regex is abandoned, you should avoid using it. No replacement was suggested.
Package hoa/stream is abandoned, you should avoid using it. No replacement was suggested.
Package hoa/ustring is abandoned, you should avoid using it. No replacement was suggested.
Package hoa/visitor is abandoned, you should avoid using it. No replacement was suggested.
Package hoa/zformat is abandoned, you should avoid using it. No replacement was suggested.
Generating autoload files
Generating attributes file
Generated attributes file in 377.431 ms
55 packages you are using are looking for funding.
Use the `composer fund` command to find out more!

 ! [NOTE] The Xdebug PHP extension is active, but "--xdebug" is not used.
 !        The process was restarted and it will not halt at breakpoints.
 !        Use "--xdebug" if you want to halt at breakpoints.

Note: Using configuration file /Users/staabm/workspace/phpstan-src/phpstan.neon.dist.
4a143bdda18c535d66ea5afbcea885c7cce63abd is the first bad commit
commit 4a143bdda18c535d66ea5afbcea885c7cce63abd
Author: Markus Staab &lt;maggus.staab@googlemail.com&gt;
Date:   Thu Jan 15 08:52:27 2026 +0100

    Cache ClassReflections

 src/Type/ObjectType.php | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
bisect found first bad commit
</code></pre>

<p>In the above output we can see Git identified a commit:</p>

<blockquote>
  <p>4a143bdda18c535d66ea5afbcea885c7cce63abd is the first bad commit</p>
</blockquote>

<p>To get more information about this commit you can for example open it in your browser:
<a href="https://github.com/phpstan/phpstan-src/commit/4a143bdda18c535d66ea5afbcea885c7cce63abd">https://github.com/phpstan/phpstan-src/commit/4a143bdda18c535d66ea5afbcea885c7cce63abd</a></p>]]></content><author><name>Markus Staab</name></author><category term="dx" /><category term="PHPStan" /><summary type="html"><![CDATA[The first thing after someone reported a regression bug is to find out when it broke. After finding the regression commit it’s easier to reason about the problem context and explore a new fix.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://staabm.github.io/images/og-images/git-bisect-run.jpg" /><media:content medium="image" url="https://staabm.github.io/images/og-images/git-bisect-run.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">PHPStan on steroids</title><link href="https://staabm.github.io/2026/01/25/phpstan-on-steroids.html" rel="alternate" type="text/html" title="PHPStan on steroids" /><published>2026-01-25T00:00:00+00:00</published><updated>2026-01-25T00:00:00+00:00</updated><id>https://staabm.github.io/2026/01/25/phpstan-on-steroids</id><content type="html" xml:base="https://staabm.github.io/2026/01/25/phpstan-on-steroids.html"><![CDATA[<p><a href="https://staabm.github.io/2022/12/23/phpstan-speedzember.html">Similar to last year</a>, while on vacation of my primary job I focused my open source efforts on improving PHPStan performance.</p>

<p><em>You might also be interested in other articles of this <a href="https://staabm.github.io/archive.html#performance">PHP performance series</a>.</em></p>

<p>I teamed up with <a href="https://github.com/ondrejmirtes">Ondřej Mirtes</a> - the creator of PHPStan - in deep analyzing several PHPStan use-cases.
We identified and discussed several potential performance opportunities which resulted in lots of performance oriented changes.</p>

<p>We turned around every stone in the codebase and looked through all PHPStan components to make it as efficient as it can get.
It took us ~6 weeks of collaboration which lead us to <a href="https://github.com/phpstan/phpstan/releases/tag/2.1.34">PHPStan 2.1.34</a> - a release loaded with patches aimed for speeding it up.
I was able to contribute ~100 pull requests alone - not counting the changes Ondřej worked on at the same time.</p>

<p>Our internal “reference benchmark” improved by ~50% in runtime. Running this version pre-release on a few codebases suggested real world projects should see 25 % to 40 % faster analysis times.
In the related <a href="https://github.com/phpstan/phpstan/discussions/13976">release discussion</a> we asked our users how they experience the new version.
We are happy to see our end-users can reproduce the improvements on real world projects. People report improvements of 8% - 40%.</p>

<p>We would love to see your raw performance numbers - <a href="https://github.com/phpstan/phpstan/discussions/13976#new_comment_form">please share them with us</a>.</p>

<h2 id="phpstan-loves-infectionphp">PHPStan loves InfectionPHP</h2>

<p>Another focus of this work was reducing bootstrap overhead so running the PHPStan integration while <a href="https://staabm.github.io/2025/08/01/infection-php-mutation-testing.html">mutating testing with InfectionPHP</a> gets faster.
This area was mostly IO oriented, because when analyzing only few files of a mutation the overall time was dominated by reading and ast-parsing files, and loading the configuration.</p>

<p>In our reference mutation testing example, we measured 70% less files being read/analyzed and the PHPStan invocation got ~40 % faster.</p>

<h2 id="known-problems">Known problems</h2>

<p>We are aware that after the 2.1.34 release analyzing files require more memory.
After a few experiments we already got ideas on how we can improve on it.
This will be explored in upcoming releases. Stay tuned.</p>

<h2 id="saving-resources-all-over-the-world">Saving resources all over the world</h2>

<p>We expect this changes to considerably reduce the amount of energy used in CI pipelines.</p>

<p>If you are using PHPStan in projects this will also <a href="https://twitter.com/OndrejMirtes/status/1601514447159578624">considerably reduce the wait time</a> for the engineers.
A shorter feedback loop helps developers to stay in focus and work more efficiently.</p>

<p>Nowadays, this can be especially important when paired with AI tooling workflows,
which can be particular slow and/or produce big amounts of source code changes containing bugs which are hard to catch by the human eye.</p>

<p>Chances are high, that you or your company is saving a lot of money with recent releases.
<a href="https://github.com/sponsors/staabm">Please consider supporting my work</a>, so I can make sure PHPStan keeps as fast as possible and evolves to the next level.</p>

<p>All this performance work have been developed while holiday of my primary job.</p>]]></content><author><name>Markus Staab</name></author><category term="PHPStan" /><category term="Performance" /><summary type="html"><![CDATA[Similar to last year, while on vacation of my primary job I focused my open source efforts on improving PHPStan performance.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://staabm.github.io/images/og-images/phpstan-on-steroids.jpg" /><media:content medium="image" url="https://staabm.github.io/images/og-images/phpstan-on-steroids.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Speedup PHPUnit code coverage generation</title><link href="https://staabm.github.io/2025/11/26/speedup-phpunit-code-coverage.html" rel="alternate" type="text/html" title="Speedup PHPUnit code coverage generation" /><published>2025-11-26T00:00:00+00:00</published><updated>2025-11-26T00:00:00+00:00</updated><id>https://staabm.github.io/2025/11/26/speedup-phpunit-code-coverage</id><content type="html" xml:base="https://staabm.github.io/2025/11/26/speedup-phpunit-code-coverage.html"><![CDATA[<p>While working on the PHPStan codebase I recently realized we spent a considerable amount of time to generate code-coverage data,
which we need later on to feed the Infection based <a href="https://staabm.github.io/2025/08/01/infection-php-mutation-testing.html">mutation testing process</a>.</p>

<p>Running mutation testing in our continuous integration pipeline based on GitHub Actions took around ~15m 30s in total per PHP version we support.
In this article I will describe how I approached this problem and what we came up with.</p>

<p>Most of the following ideas and optimizations will also fit for other PHPUnit code coverage use cases.</p>

<h2 id="getting-a-better-idea-of-what-is-slow">Getting a better idea of what is slow</h2>

<p>As a very first step I tried to divide the big block of work into smaller parts, to get a better understanding which part actually is slow.
Therefore, separating Infections’ preparational initial-tests step from the actual mutation testing was my first take.
This can be achieved by running infection with <a href="https://infection.github.io/guide/command-line-options.html"><code>--skip-initial-tests</code></a> and record the coverage data beforehand in a separate step.
The resulting GitHub Actions steps for this look like:</p>

<pre><code># see https://infection.github.io/guide/command-line-options.html#coverage
- name: "Create coverage in parallel"
run: |
  php -d pcov.enabled=1 tests/vendor/bin/paratest \
    --passthru-php="'-d' 'pcov.enabled=1'" \
    --coverage-xml=tmp/coverage/coverage-xml --log-junit=tmp/coverage/junit.xml \
    --exclude-source-from-xml-coverage

- name: "Run infection"
run: |
  git fetch --depth=1 origin $
  infection \
    --git-diff-base=origin/$ \
    --git-diff-lines \
    --coverage=tmp/coverage \
    --skip-initial-tests \
    --ignore-msi-with-no-mutations \
    --min-msi=100 \
    --min-covered-msi=100 \
    --log-verbosity=all \
    --debug \
    --logger-text=php://stdout
</code></pre>

<p>note, that we are using <code>pcov</code> over <code>xdebug</code> to record coverage information, as in our case this was the <a href="https://github.com/phpstan/phpstan-src/pull/4565#issuecomment-3545713472">considerably faster option</a>.</p>

<p>also note, that we are using <code>paratest</code> - which we use for running tests in phpstan-src already - to create coverage information with parallel running workers.
before this change, when infection itself triggered the initial test step, this work was done on a single process only.</p>

<p>This leads us to the following results:</p>
<ul>
  <li>the total amount of time required to run this dropped to ~12m 30s</li>
  <li>coverage generation takes ~6m 10s</li>
  <li>from looking at the <code>paratest</code> output, we see <code>Generating code coverage report in PHPUnit XML format ... done [01:00.714]</code></li>
  <li>running infection takes ~6m 20s</li>
</ul>

<h2 id="speedup-code-coverage-xml-report-generation">Speedup code coverage xml report generation</h2>

<p>I was pretty surprised that the xml report generation takes 1 minute alone.</p>

<p>Looking into blackfire profiles of this xml generation process yielded some interesting insight.
While working on a few micro-optimizations in the underlying libraries I slowly started to better understand how all this works.</p>

<ul>
  <li>Faster coverage-xml report <a href="https://github.com/sebastianbergmann/php-code-coverage/pull/1102">sebastianbergmann/php-code-coverage#1102</a></li>
  <li>Simplify XMLSerializer <a href="https://github.com/theseer/tokenizer/pull/24">theseer/tokenizer#24</a></li>
  <li>Simplify TokenCollection <a href="https://github.com/theseer/tokenizer/pull/25">theseer/tokenizer#25</a></li>
  <li>Streamline XMLSerializer <a href="https://github.com/theseer/tokenizer/pull/29">theseer/tokenizer#29</a></li>
  <li>Streamline Tokenizer-&gt;fillBlanks() <a href="https://github.com/theseer/tokenizer/pull/32">theseer/tokenizer#32</a></li>
  <li>Utilize PhpToken::tokenize() - requires PHP8+ <a href="https://github.com/theseer/tokenizer/pull/35">theseer/tokenizer#35</a></li>
</ul>

<p>After a chat with php-src contributor <a href="https://github.com/ndossche">Niels Dossche</a> the idea came up,
that XML report generation could see a big speed boost after untangling the DOM and XMLWriter implementation.
A new <a href="https://github.com/sebastianbergmann/php-code-coverage/pull/1125">pull request which drops the DOM dependency</a> shows we could reach a ~50% faster report generation.
While the implementation before this PR was more flexible, I think this flexibility is not worth such a performance penalty.
By removing the DOM interactions I feel we made the implementation more direct and explicit.</p>

<h2 id="faster-code-coverage-data-processing">Faster code coverage data processing</h2>

<p>Another idea which came up was looking into the involved data-structures of PHPUnits’ <a href="https://github.com/sebastianbergmann/php-code-coverage">sebastianbergmann/php-code-coverage</a> component.</p>

<p>Reworking the implementation which heavily relied on PHP arrays lead us to <a href="https://github.com/sebastianbergmann/php-code-coverage/pull/1105">~33% faster data processing</a> for PHPUnits’ <code>--path-coverage</code> option.
Inspiration for this change came from a <a href="https://gist.github.com/nikic/5015323">GIST by Nikita Popov</a>, which I found on github.com.
It explains in full detail why/when objects use less memory than arrays.</p>

<p>While refactoring the implementation by introducing more immutable objects and reducing unnecessary duplicate work I squeezed out a bit more performance:</p>
<ul>
  <li>Prevent sorting coverage-data over and over in <a href="https://github.com/sebastianbergmann/php-code-coverage/pull/1107">sebastianbergmann/php-code-coverage#1107</a> and <a href="https://github.com/sebastianbergmann/php-code-coverage/pull/1108">sebastianbergmann/php-code-coverage#1108</a></li>
  <li>Node properties are immutable <a href="https://github.com/sebastianbergmann/php-code-coverage/pull/1117">sebastianbergmann/php-code-coverage#1117</a></li>
</ul>

<h2 id="prevent-unnecessary-work">Prevent unnecessary work</h2>

<p>Sebastian came up with the <a href="https://github.com/sebastianbergmann/php-code-coverage/pull/1125#issuecomment-3582440397">suggestion of removing the <code>&lt;source&gt;</code>-element</a> from the xml coverage report via opt-in flag.</p>

<p>After playing with the idea it seems this information is not required by Infection, so he added a new <a href="https://github.com/sebastianbergmann/phpunit/issues/6422"><code>--exclude-source-from-xml-coverage</code> CLI option</a>
which will be <a href="https://github.com/infection/infection/pull/2604">automatically enabled by Infection to speedup the coverage generation</a> when PHPUnit 12.5+ is used.</p>

<p>A test on the PHPStan codebase shows, this can <a href="https://github.com/sebastianbergmann/php-code-coverage/pull/1125#issuecomment-3584453120">speedup the xml coverage report generation by ~15%</a>.</p>

<p>As a followup support for this new option was added into <a href="https://github.com/infection/infection/pull/2604">Infection</a> and <a href="https://github.com/paratestphp/paratest/pull/1056">ParaTest</a>.</p>

<h2 id="taking-shortcuts">Taking shortcuts</h2>

<p>Working on slow processes like code-coverage recording which takes multiple minutes to execute, its vital to take shortcuts which shorten the feedback loop.
To assist myself I hacked into the process a few lines of code which <code>serialize</code>d the generated <code>CodeCoverage</code> object and stored it as a 998MB file.</p>

<p>Using the pre-recorded data and the following short script made it possible to profile the xml report generation alone, without long waiting for the data recording:</p>
<pre><code class="language-php">&lt;?php

require_once 'vendor/autoload.php';

use PHPUnit\Runner\Version;
use SebastianBergmann\CodeCoverage\Report\Xml\Facade as XmlReport;

$coverage = unserialize(file_get_contents(__DIR__ . '/coverage-data.ser'));
$config = file_get_contents(__DIR__ . '/coverage.xml');

$writer = new XmlReport(Version::id());
$writer-&gt;process($coverage, $config);
</code></pre>

<p>I put all this into a <a href="https://github.com/staabm/code-coverage-benchmarks/tree/main/slow-coverage-xml1">separate git repository</a> to allow re-using it in the future.</p>

<h2 id="results">Results</h2>

<p>Applying all those <a href="https://github.com/phpstan/phpstan-src/pull/4624">fixes on the phpstan-src codebase</a> yielded a impressive improvement in xml coverage report generation:</p>

<p>Before (PHPUnit 11.5.x)</p>

<blockquote>
  <p>Time: 04:37.104, Memory: 688.50 MB</p>

  <p>Generating code coverage report in PHPUnit XML format … done [00:51.395]</p>
</blockquote>

<p>After upgrading to PHPUnit 12.5.x</p>

<blockquote>
  <p>Time: 04:23.595, Memory: 678.50 MB</p>

  <p>Generating code coverage report in PHPUnit XML format … done [00:21.631]</p>
</blockquote>

<p>After adding <code>--exclude-source-from-xml-coverage</code></p>
<blockquote>
  <p>Time: 04:16.807, Memory: 634.50 MB</p>

  <p>Generating code coverage report in PHPUnit XML format … done [00:17.431]</p>
</blockquote>

<h2 id="summary">Summary</h2>

<p>Working thru all this details and codebases made a lot of fun while also taking a lot of my freetime.</p>

<p>At this point I want to emphasize how important it is to separate the public API of a library/tool/component from the inner workings.
Sebastian Bergmann and Arne Blankerts did a great job in the repositories I worked on in this context by declaring classes <code>@internal</code>,
so we could easily even do backwards incompatible changes, as long as the top level public API is untouched.</p>

<p>In the future a lot of projects will benefit from these changes by updating PHPUnit and related libraries.
Faster tooling processes will also save costly CI-minute resources and people waiting time.</p>

<p>Make sure your boss considers <a href="https://github.com/sponsors/staabm">sponsoring my open source work</a>, so I can spend more time on your beloved code quality tooling.</p>]]></content><author><name>Markus Staab</name></author><category term="PHPStan" /><category term="PHPUnit" /><category term="Mutation Testing" /><category term="Performance" /><summary type="html"><![CDATA[While working on the PHPStan codebase I recently realized we spent a considerable amount of time to generate code-coverage data, which we need later on to feed the Infection based mutation testing process.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://staabm.github.io/images/og-images/speedup-phpunit-code-coverage.jpg" /><media:content medium="image" url="https://staabm.github.io/images/og-images/speedup-phpunit-code-coverage.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">New and noteworthy: PHPStan and PHPUnit integration</title><link href="https://staabm.github.io/2025/11/15/phpstan-validates-phpunit-data-provider.html" rel="alternate" type="text/html" title="New and noteworthy: PHPStan and PHPUnit integration" /><published>2025-11-15T00:00:00+00:00</published><updated>2025-11-15T00:00:00+00:00</updated><id>https://staabm.github.io/2025/11/15/phpstan-validates-phpunit-data-provider</id><content type="html" xml:base="https://staabm.github.io/2025/11/15/phpstan-validates-phpunit-data-provider.html"><![CDATA[<p>In this article we will have a brief look into the latest update to <a href="https://github.com/phpstan/phpstan-phpunit"><code>phpstan/phpstan-phpunit</code></a> 2.0.8.</p>

<h3 id="phpstan-validates-phpunit-data-providers">PHPStan validates PHPUnit data-providers</h3>

<p>One of the features, which I am most proud of is the data-provider validation.
It was requested by several people years ago, but we did not yet have a good idea how to make it happen without major changes in the PHPStan core.</p>

<p>Starting with this release, we take each data-set of a data-provider and check it against the signature of the corresponding test-case.
That way we can validate whether a data-provider yields all necessary data to fulfill the tests signature.</p>

<p>At the time of writing we support multiple kinds of data-providers:</p>
<ul>
  <li><code>@test</code></li>
  <li><code>#[Test]</code></li>
  <li>“test*” method name prefix</li>
  <li><code>@dataProvider</code></li>
  <li><code>#[DataProvider]</code></li>
  <li><code>static</code> data-provider</li>
  <li>non-<code>static</code> data-provider</li>
  <li><code>return []</code> data-providers</li>
  <li><code>yield []</code> data-providers</li>
  <li><code>yield from []</code> data-providers</li>
  <li>named arguments in data-providers</li>
</ul>

<p>See it in action:</p>

<pre><code class="language-php">#[DataProvider('aProvider')]
public function testTrim(string $expectedResult, string $input): void
{
}

public function aProvider(): array
{
   return [
      [
         'Hello World',
         " Hello World \n",
      ],
      [
         // Parameter #2 $input of method FooTest::testTrim() expects string, int given.
         'Hello World',
         123,
      ],
      [
         // Parameter #2 $input of method FooTest::testTrim() expects string, false given.
         'Hello World',
         false,
      ],
      [
         // Method FooTest::testTrim() invoked with 1 parameter, 2 required.
         'Hello World',
      ],
   ];
}
</code></pre>

<p>For this to happen we re-use existing rules for method call validation via the newly introduced <a href="https://github.com/phpstan/phpstan-src/blob/2.1.x/src/Analyser/NodeCallbackInvoker.php"><code>NodeCallbackInvoker</code></a>.
This new interface allows us to create virtual made-up AST nodes, and handle them like regular method calls.</p>

<p>Related pull requests:</p>
<ul>
  <li><a href="https://github.com/phpstan/phpstan-phpunit/pull/238">Implement DataProviderDataRule</a></li>
  <li><a href="https://github.com/phpstan/phpstan-src/pull/4429">NodeCallbackInvoker</a></li>
  <li><a href="https://github.com/phpstan/phpstan-src/pull/4438">CompositeRule</a></li>
</ul>

<h3 id="ignore-missingtypeiterablevalue-for-data-providers">Ignore <code>missingType.iterableValue</code> for data-providers</h3>

<p>You likely have been haunted by this error in your test-suite:</p>

<blockquote>
  <p>Method DataProviderIterableValueTest\Foo::dataProvider() return type has no value type specified in iterable type iterable.</p>
</blockquote>

<p>Even in the PHPStan-src codebase this error was ignored by NEON config in the past, as it was really not that useful to repeat all types in every data-provider,
which were already present in the test-case method signatures.</p>

<p>As you already saw in the above paragraph we learned how to validate data-providers with this release.
We went one step further and re-used the existing validation logic to omit the <code>missingType.iterableValue</code> error only for those data-providers which we are able to validate.
This is possible by <a href="https://github.com/phpstan/phpstan-phpunit/pull/246">implementing a new <code>IgnoreErrorExtension</code></a>.</p>

<h3 id="improved-assertarrayhaskey-inference">Improved <code>assertArrayHasKey()</code> inference</h3>

<p>Based on a <a href="https://github.com/phpstan/phpstan-src/pull/4473">fix in the PHPStan core</a>, we are now able to properly narrow types after a call to PHPUnits’ <code>assertArrayHasKey()</code>.
This will help to prevent false positive errors you may have experienced in the past.</p>

<h3 id="phpunit-version-detector">PHPUnit version detector</h3>

<p>With the addition of <a href="https://github.com/phpstan/phpstan-phpunit/pull/248"><code>PHPUnitVersionDetector</code></a>
we will be able to easily implement rules or extensions tailored to certain PHPUnit versions.</p>

<p>This will be useful in the future, so we can for example assist in PHPUnit migrations and pave the way for a smoother upgrade path.</p>

<h3 id="performance-improvements">Performance improvements</h3>

<p>People reading my blog or social media posts know <a href="https://staabm.github.io/archive.html#performance">my obsession in making things faster</a>.
This release is no difference, as some changes have been done to make most PHPUnit specific rules <a href="https://github.com/phpstan/phpstan-phpunit/pull/247">more efficient by reducing unnecessary work</a>.</p>

<h3 id="easter-eggs-included">Easter eggs included</h3>

<p>There is even more magic under the hood.</p>

<p>We have a experimental feature in PHPStan which allows us to not just report errors, but also <code>--fix</code> some of them.
This new ability was also added to a few <code>assert*</code> rules.</p>

<p>For example, we can turn <code>$this-&gt;assertSame(true, $this-&gt;returnBool());</code> into <code>$this-&gt;assertTrue($this-&gt;returnBool());</code>.</p>

<h3 id="summary">Summary</h3>

<p>I spent a lot of time over a few weeks to make the PHPUnit integration shine.
We are on a totally new level and even more new cool stuff is getting possible.</p>

<p>Make sure your boss considers <a href="https://github.com/sponsors/staabm">sponsoring my open source work</a>, so I can spend more time on your beloved code quality tooling.</p>]]></content><author><name>Markus Staab</name></author><category term="PHPStan" /><category term="PHPUnit" /><summary type="html"><![CDATA[In this article we will have a brief look into the latest update to phpstan/phpstan-phpunit 2.0.8.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://staabm.github.io/images/og-images/new-and-noteworthy-phpstan-phpunit-integration.jpg" /><media:content medium="image" url="https://staabm.github.io/images/og-images/new-and-noteworthy-phpstan-phpunit-integration.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Mutation testing with Infection in PHP</title><link href="https://staabm.github.io/2025/08/01/infection-php-mutation-testing.html" rel="alternate" type="text/html" title="Mutation testing with Infection in PHP" /><published>2025-08-01T00:00:00+00:00</published><updated>2025-08-01T00:00:00+00:00</updated><id>https://staabm.github.io/2025/08/01/infection-php-mutation-testing</id><content type="html" xml:base="https://staabm.github.io/2025/08/01/infection-php-mutation-testing.html"><![CDATA[<p>Recently I started playing with <a href="https://infection.github.io/">Infection</a>, a mutation testing framework for PHP.
Mutation testing is a technique to evaluate the quality of your tests.</p>

<p>This article describes my personal experience and ideas about mutation testing.
I am intentionally not describing the theory behind it, but focus on my personal hands-on experience.</p>

<p>It was a natural choice for me to look into this topic, as it combines several concepts I am interested in:</p>
<ul>
  <li>Automated testing</li>
  <li>Abstract syntax tree (AST) based analysis and manipulation</li>
  <li>Static analysis</li>
  <li>Type inference</li>
  <li>Code quality tooling</li>
</ul>

<p>After getting used to Infection and applying it to my own projects, I started to contribute to the project itself.
As usual, I start reporting issues with ideas for improvements and questions about my understanding of the approach.
The maintainers were very responsive and helpful, which made it easy to get started.
Within ~2 months I was able to contribute <a href="https://github.com/infection/infection/pulls?q=sort%3Aupdated-desc+is%3Apr+is%3Aopen+author%3Astaabm">~85 pull requests</a>.</p>

<p>So let’s have a top level look at the tool.</p>

<h3 id="what-to-expect-from-mutation-testing">What to expect from mutation testing?</h3>

<p>From my point of view what you get from applying mutation testing is:</p>
<ul>
  <li>more precise metric for your test suite quality</li>
  <li>a copilot for writing better tests</li>
  <li>dead code detection</li>
</ul>

<h4 id="more-precise-metric-for-your-test-suite-quality">more precise metric for your test suite quality</h4>

<p>After running Infection on your codebase, you will get a report with a <a href="https://infection.github.io/guide/#Metrics-Mutation-Score-Indicator-MSI">mutation score indicator (MSI)</a>.
It’s a metric which describes how likely you or your colleagues can introduce a new bug/regression into your codebase without your quality tooling noticing it.
The higher the MSI, the more likely it is that your tooling will catch the bug.</p>

<p>In contrast, you might expect from your code line coverage report that any bug introduced in a covered line will be caught by your tests.
However, this is not the case, as your tests might not be precise enough to catch the problem.</p>

<h4 id="a-copilot-for-writing-better-tests">a copilot for writing better tests</h4>

<p>Let’s have a look at a simple example:</p>

<pre><code class="language-php">if ($x &gt; 0) {
    echo "hello world";
}
</code></pre>

<p>To cover this simple code with tests, you can do multiple mistakes:</p>
<ul>
  <li>you could assert only the positive case but forget to assert the negative case
    <ul>
      <li>does the implementation produces the correct output depending on the condition?</li>
      <li>do your tests assert expectations when the condition is not met?</li>
    </ul>
  </li>
  <li>you could add tests which do not properly cover the boundary of the <code>x &gt; 0</code>  expression, meaning off-by-one errors will not be detected
    <ul>
      <li>e.g. you need to verify whether it should be <code>$x &gt;= 0</code> or <code>$x &gt; 1</code> or <code>$x &lt; 0</code> etc.</li>
    </ul>
  </li>
</ul>

<p>Running infection will give you examples (escaped mutants) to give some inspiration what your test-suite does not cover properly.
See the <a href="https://infection-php.dev/r/23mw">above example in the Infection playground</a> in action.</p>

<p>For instance, the following escaped mutant tells you, that your tests do not make a difference whether the condition is <code>$x &gt; 0</code> or <code>$x &gt;= 0</code>:</p>

<p><img width="740" height="160" alt="Infection playground" src="/images/post-images/infection-php-mutation-testing/greater-than-mutant.png" /></p>

<p>This does not necessarily mean that your implementation is wrong, but it tells you that your tests do not cover the condition properly.
This might also be a indicator that your assertions need to be more precise, or that you need to add additional tests to cover the edge cases.</p>

<h4 id="dead-code-detection">dead code detection</h4>

<p>From a different perspective, looking at mutation testing results (escaped mutants) can help you to detect dead code.
In case you are confident that your test suite covers all relevant cases,
a escaped mutant tells you that certain code in your implementation doesn’t make a difference for the end result.
This means that the code is dead and can be removed.</p>

<p><img width="740" height="160" alt="Image" src="/images/post-images/infection-php-mutation-testing/unwrap-trim-mutant.png" /></p>

<p>See the <a href="https://infection-php.dev/r/53ze">above example in the Infection playground</a> in action.</p>

<h3 id="summary">Summary</h3>

<p>Playing 2 months with Infection was a lot of fun.
It made me curious what else I can do to improve the tool and which value it can provide to the PHP community and my projects.</p>

<p>I think it is not that easy to start with running Infection on a existing project,
therefore I am planning to write another article about how to get started with it.</p>]]></content><author><name>Markus Staab</name></author><category term="Infection" /><category term="Mutation Testing" /><summary type="html"><![CDATA[Recently I started playing with Infection, a mutation testing framework for PHP. Mutation testing is a technique to evaluate the quality of your tests.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://staabm.github.io/images/og-images/infection-php-mutation-testing.jpg" /><media:content medium="image" url="https://staabm.github.io/images/og-images/infection-php-mutation-testing.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">PHPStan remembered types from constructor</title><link href="https://staabm.github.io/2025/04/15/phpstan-remember-constructor-types.html" rel="alternate" type="text/html" title="PHPStan remembered types from constructor" /><published>2025-04-15T00:00:00+00:00</published><updated>2025-04-15T00:00:00+00:00</updated><id>https://staabm.github.io/2025/04/15/phpstan-remember-constructor-types</id><content type="html" xml:base="https://staabm.github.io/2025/04/15/phpstan-remember-constructor-types.html"><![CDATA[<p>Over the last few days I am working on a new PHPStan capability,
which allows PHPStan to use type information from analyzing a class-constructor
with the goal to improve results when later on analyzing instance methods or property hook bodies.</p>

<p>This feature will be available starting with PHPStan 2.1.12.
In case you are curious you can play with it in the <a href="https://phpstan.org/r/c3d8e4f2-b65d-45cc-bab0-801072c4bd0b">PHPStan Playground</a> right now.
The implementation itself can be inspected in <a href="https://github.com/phpstan/phpstan-src/pull/3930">pull request #3930</a>.</p>

<p>Let’s have a look into a few example use-cases for this new feature:</p>

<h3 id="remember-class_exists-function_exists">Remember <code>class_exists()</code>, <code>function_exists()</code></h3>

<p>Checking class- or function existence in the constructor in combination with an aborting expression which prevents object creation,
will prevent errors like <code>Function some_unknown_function not found</code> in instance methods. The same is true for <code>class_exists</code>.</p>

<p>This means you no longer need to wrap a call to a conditionally defined function in a <code>function_exists</code> block everytime you use it.
PHPStan will remember for the whole class that the function will exist, when analyzing instance methods or property hook bodies.</p>

<p>Static methods will still emit a <code>Function some_unknown_function not found</code> error, as these can still be called even if the constructor failed to create an object.</p>

<pre><code class="language-php">class User
{
   public function __construct() {
      if (!function_exists('some_unknown_function')) {
         throw new \LogicException();
      }
   }

   public function doFoo(): void
   {
      some_unknown_function();
   }
}
</code></pre>

<h3 id="remember-global-constants">Remember global constants</h3>

<p>Similar to the example above its possible to check for the existence of a global constant in the constructor.
What also comes in handy is narrowing the global constant type will also be preserved for the whole class.</p>

<pre><code class="language-php">class HelloWorld
{
   public function __construct()
   {
      if (!defined('REMEMBERED_FOO')) {
         throw new LogicException();
      }
      if (!is_string(REMEMBERED_FOO)) {
         throw new LogicException();
      }
   }

   static public function staticFoo2(): void
   {
      // error, static method types are not narrowed via constructor
      echo REMEMBERED_FOO;
   }

   public function returnFoo2(): int
   {
      // error, as the constant was narrowed to string
      return REMEMBERED_FOO;
   }
}
</code></pre>

<h3 id="remember-class-property-types">Remember class property types</h3>

<p>With all the required machinery in place we went one step further and also remember type information about class properties.</p>

<h4 id="readonly-property-types"><code>readonly</code> property types</h4>

<p>When properties are declared <code>readonly</code> PHPStan is now able to remember all possible types assigned in the constructor.
You no longer need to declare a narrow phpdoc type in this case to make PHPStan aware of the concrete values.</p>

<pre><code class="language-php">class User
{
   public string $name {
      get {
         // previously we only knew $this-&gt;type is `int`.
         // new: we know the type of $this-&gt;type is `1|2`
         return $this-&gt;name . $this-&gt;type;
      }
   }

   private readonly int $type;

   public function __construct(string $name) {
      $this-&gt;name = $name;
      if (rand(0,1)) {
         $this-&gt;type = 1;
      } else {
         $this-&gt;type = 2;
      }
   }
}
</code></pre>

<h4 id="typed-properties-and-the-uninitialized-state">typed properties and the uninitialized state</h4>

<p>Until now PHPStan didn’t know which properties have been initialized in the constructor.
Thanks to the recent additions the analysis of instance methods is now aware which properties can no longer be in the uninitialized state, because they have been initialized already.</p>

<p>With this knowledge we are able to tell whether <code>isset()</code>, <code>empty()</code> or <code>??</code> is redundant.</p>

<pre><code class="language-php">class User
{
   private string $string;

   public function __construct()
   {
      if (rand(0, 1)) {
          $this-&gt;string = 'world';
      } else {
          $this-&gt;string = 'world 2';
      }
   }

   public function doFoo(): void
   {
      // Property User::$string in isset() is not nullable nor uninitialized.
      if (isset($this-&gt;string)) {
         echo $this-&gt;string;
      }
   }

   public function doBar(): void
   {
      // Property User::$string on left side of ?? is not nullable nor uninitialized.
      echo $this-&gt;string ?? 'default';
   }
}
</code></pre>

<p>Related PHPStan issues:</p>
<ul>
  <li><a href="https://github.com/phpstan/phpstan/issues/12860">#12860: Remember narrowed types from the constructor when analysing other methods</a></li>
  <li><a href="https://github.com/phpstan/phpstan/issues/10048">#10048: False positive “Access to an uninitialized readonly property”</a></li>
  <li><a href="https://github.com/phpstan/phpstan/issues/11828">#11828: False-positive “property.uninitializedReadonly” when used indirectly by anonymous function</a></li>
  <li><a href="https://github.com/phpstan/phpstan/issues/9075">#9075: Improve type inference using information from the Class constructor</a></li>
  <li><a href="https://github.com/phpstan/phpstan/issues/6063">#6063: Expression on left side of ?? is only nullable, but non-nullable properties do not cause an error</a></li>
  <li><a href="https://github.com/phpstan/phpstan/issues/12723">#12723: Typed property mistaken as nullable</a></li>
</ul>

<hr />

<p>Do you like PHPStan and use it every day? Consider <a href="https://github.com/sponsors/staabm">sponsoring my open source work</a>.</p>]]></content><author><name>Markus Staab</name></author><category term="PHPStan" /><summary type="html"><![CDATA[Over the last few days I am working on a new PHPStan capability, which allows PHPStan to use type information from analyzing a class-constructor with the goal to improve results when later on analyzing instance methods or property hook bodies.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://staabm.github.io/images/og-images/phpstan-remember-constructor-types.jpg" /><media:content medium="image" url="https://staabm.github.io/images/og-images/phpstan-remember-constructor-types.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Thank You</title><link href="https://staabm.github.io/2025/01/24/thank-you.html" rel="alternate" type="text/html" title="Thank You" /><published>2025-01-24T00:00:00+00:00</published><updated>2025-01-24T00:00:00+00:00</updated><id>https://staabm.github.io/2025/01/24/thank-you</id><content type="html" xml:base="https://staabm.github.io/2025/01/24/thank-you.html"><![CDATA[<p>Hey supporter :).</p>

<p>First let me say thank you.
It means at lot to me that people like you see value in my open source work.</p>

<p>Behind every bullet point with my name on it in release notes of PHPStan, PHPUnit, Rector is at least one person with a problem.
I wouldn’t be able to dive into all the necessary details to figure them out and contribute improvements, without your support.</p>

<p>Over the course of 2024 I was able to fix ~69 reported problems while contributing ~235 pull requests to PHPStan alone.
This work is necessary to make the enduser experience as frictionless as possible.
PHPStan gets a better understanding of your code, provides less false positives and is able to point out problems it wouldn’t recognize otherwise.
When not working on features I have an eye on performance bottlenecks to make the process more efficient and you waste less time waiting.</p>

<p>I can share similar stories when working on PHPUnit or Rector.
Iron out problems is the magic sauce which keep the PHP open source ecosystem alive and useful.</p>

<p>If you got curious, please have a look at my <a href="https://staabm.github.io/2024/12/11/contribution-summary-2024.html">2024 contribution summary</a>.</p>

<p>Besides all the implementation work, I also had the pleasure to meet with the people behind these projects in person in 2024 for the very first time.
Meeting in person with people you have worked with for multiple years remote only is always exciting.
Thanks to your financial support I was able to attend the PHP Days in Dresden to meet Ondrej Mirtes (PHPStan inventor),
and later on I had a <a href="https://staabm.github.io/2024/10/19/phpunit-codesprint-munich.html">PHPUnit Codesprint with Sebastian Bergmann</a> (PHPUnit inventor) in Munich.</p>

<p>Coming together and spending some time together also helped to improve our teamwork for future collaborations.</p>

<p>For 2025 there is still a lot of stuff todo and I don’t plan to stop contributing to the mentioned projects.
I have plans to join a few PHP conferences/meetups and would love to see you there.
Have a look at my blog to get the latest news about my open source work and please spread the word.</p>

<p>If you need a helping hand with one of those tools with your projects, feel free to get in contact.
In addition, I am still looking for more sponsors - especially from companies which use above mentioned tools.
I would love to reduce a few hours a week of my primary job and push the PHP open source ecosystem onto the next level.</p>

<p>Thanks again!</p>

<p>Markus</p>]]></content><author><name>Markus Staab</name></author><category term="Activities" /><summary type="html"><![CDATA[Hey supporter :).]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://staabm.github.io/images/og-images/thanks.jpg" /><media:content medium="image" url="https://staabm.github.io/images/og-images/thanks.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Contribution Summary 2024</title><link href="https://staabm.github.io/2024/12/11/contribution-summary-2024.html" rel="alternate" type="text/html" title="Contribution Summary 2024" /><published>2024-12-11T00:00:00+00:00</published><updated>2024-12-11T00:00:00+00:00</updated><id>https://staabm.github.io/2024/12/11/contribution-summary-2024</id><content type="html" xml:base="https://staabm.github.io/2024/12/11/contribution-summary-2024.html"><![CDATA[<p>2024 marks the year in which Tomas Votruba (Rector) and Ondřej Mirtes (PHPStan) started sponsoring my open source work, which means a lot to me 🥰.</p>

<p>Another outstanding moment this year was the addition of my <a href="https://github.com/staabm/side-effects-detector">staabm/side-effects-detector</a> library <a href="https://github.com/sebastianbergmann/phpunit/pull/5998">in PHPUnit</a>.</p>

<p>Let’s have a look at my contribution highlights of 547 merged pull requests across 80 open-source projects:</p>
<ul>
  <li><a href="https://staabm.github.io/2024/11/26/phpstan-mixed-types.html">A <code>mixed</code> type PHPStan journey</a></li>
  <li><a href="https://phpstan.org/blog/phpstan-1-9-0-with-phpdoc-asserts-list-type#parameter-type-assigned-by-reference"><code>@param-out</code> implementation</a> in PHPStan</li>
  <li><a href="https://phpstan.org/blog/phpstan-1-12-road-to-phpstan-2-0#general-availability-of-precise-type-inference-for-regular-expressions">type inference for regular expressions</a> in PHPStan, see also <a href="https://staabm.github.io/2024/07/05/array-shapes-for-preg-match-matches.html">my blog post</a></li>
  <li><a href="https://phpstan.org/writing-php-code/phpdocs-basics#enforcing-class-inheritance-for-interfaces-and-traits"><code>@require-extends</code> and <code>@require-implements</code></a> in PHPStan, see also <a href="https://staabm.github.io/2024/01/15/phpstan-require-extends-implements.html">my blog post</a></li>
  <li><a href="https://staabm.github.io/2024/11/14/phpstan-php-version-narrowing.html">Dead code detection for PHP_* constants</a></li>
  <li>Took part in the <a href="https://staabm.github.io/2024/10/19/phpunit-codesprint-munich.html">PHPUnit Codesprint in Munich</a></li>
  <li>phpstan-todo-by - check for <a href="https://staabm.github.io/2023/12/17/phpstan-todo-by-published.html">TODO comments with expiration</a></li>
  <li>Endless pull requests to improve performance and efficiency in PHPStan and Rector</li>
  <li>Hundreds of bug fixes in PHPStan, Rector, PHPUnit, Composer…</li>
</ul>

<p>In a recent blog post you can in addition find my <a href="https://staabm.github.io/2024/11/28/phpstan-php-version-in-scope.html">current plan for the upcoming months</a>.
I am still looking for <a href="https://github.com/sponsors/staabm">financial support for this effort</a>.</p>

<p>The following table shows the distribution of freetime contributions across the different projects I am working on.</p>

<table>
  <thead>
    <tr>
      <th>project</th>
      <th>merged pull requests</th>
      <th>addressed issues</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>phpstan/phpstan*</td>
      <td>~234   (~116 in 2023)</td>
      <td>69    (33 in 2023)</td>
    </tr>
    <tr>
      <td>rector/rector*</td>
      <td>~31   (~178 in 2023)</td>
      <td>2</td>
    </tr>
    <tr>
      <td>staabm/phpstandba</td>
      <td>38  (~44 in 2023)</td>
      <td>8</td>
    </tr>
    <tr>
      <td>sebastianbergmann/phpunit</td>
      <td>37</td>
      <td>-</td>
    </tr>
    <tr>
      <td>staabm/phpstan-todo-by</td>
      <td>35  (~33 in 2023)</td>
      <td>7</td>
    </tr>
    <tr>
      <td>TomasVotruba/unused-public</td>
      <td>28</td>
      <td>1</td>
    </tr>
    <tr>
      <td>staabm/phpstan-baseline-analysis</td>
      <td>22</td>
      <td>-</td>
    </tr>
    <tr>
      <td>Roave/BetterReflection</td>
      <td>12</td>
      <td>-</td>
    </tr>
    <tr>
      <td>composer/*</td>
      <td>12</td>
      <td>-</td>
    </tr>
    <tr>
      <td>FriendsOfREDAXO/rexstan</td>
      <td>11</td>
      <td>-</td>
    </tr>
    <tr>
      <td>easy-coding-standard/easy-coding-standard</td>
      <td>9</td>
      <td>1</td>
    </tr>
    <tr>
      <td>php/doc-en</td>
      <td>7</td>
      <td>-</td>
    </tr>
    <tr>
      <td>thecodingmachine/safe</td>
      <td>8</td>
      <td>-</td>
    </tr>
    <tr>
      <td>sebastianbergmann/exporter</td>
      <td>5</td>
      <td>-</td>
    </tr>
    <tr>
      <td>nikic/PHP-Parser</td>
      <td>5</td>
      <td>-</td>
    </tr>
    <tr>
      <td>PHP-CS-Fixer/PHP-CS-Fixer</td>
      <td>4</td>
      <td>-</td>
    </tr>
    <tr>
      <td>easy-coding-standard/easy-coding-standard</td>
      <td>4</td>
      <td>-</td>
    </tr>
    <tr>
      <td>symfony/symfony</td>
      <td>3</td>
      <td>-</td>
    </tr>
    <tr>
      <td>TomasVotruba/class-leak</td>
      <td>3</td>
      <td>-</td>
    </tr>
    <tr>
      <td>TomasVotruba/type-coverage</td>
      <td>2</td>
      <td>-</td>
    </tr>
    <tr>
      <td>infection/infection</td>
      <td>2</td>
      <td>-</td>
    </tr>
    <tr>
      <td>shipmonk-rnd/dead-code-detector</td>
      <td>2</td>
      <td>-</td>
    </tr>
    <tr>
      <td>TomasVotruba/cognitive-complexity</td>
      <td>1</td>
      <td>-</td>
    </tr>
    <tr>
      <td>vimeo/psalm</td>
      <td>1</td>
      <td>1</td>
    </tr>
    <tr>
      <td>symfony/symfony</td>
      <td>1</td>
      <td>-</td>
    </tr>
    <tr>
      <td>larastan/larastan</td>
      <td>1</td>
      <td>-</td>
    </tr>
    <tr>
      <td>… a lot more</td>
      <td>-</td>
      <td>-</td>
    </tr>
  </tbody>
</table>

<p><em>numbers crunched with <a href="https://github.com/staabm/oss-contribs">staabm/oss-contribs</a></em></p>

<p>I am pretty happy with contributing to so many popular and important projects of the PHP ecosystem.
If you look closely you can easily see my focus on quality assurance and static analysis tools :-).</p>

<h3 id="thank-you">Thank you</h3>

<p>I want to say thank you to everyone supporting my efforts.
Whether you sponsor my time or you invest your own time to review my pull requests or maintain one of the above projects.</p>

<p>I couldn’t deliver so much value in the PHP ecosystem without you 🥰.</p>

<p><img width="300" src="/images/post-images/contribution-summary-2024/git-wrapped.png" alt="git-wrapped.com stats for @staabm for 2024" /></p>

<h3 id="2025-is-just-around-the-corner">2025 is just around the corner</h3>

<p>I wish you all the best for the upcoming year. I am looking forward to continue my open source work and I hope you will support me in doing so.</p>

<p>If one of those open source projects is critical for your business, please <a href="https://github.com/sponsors/staabm">consider supporting my work with your sponsoring 💕</a></p>]]></content><author><name>Markus Staab</name></author><category term="Activities" /><summary type="html"><![CDATA[2024 marks the year in which Tomas Votruba (Rector) and Ondřej Mirtes (PHPStan) started sponsoring my open source work, which means a lot to me 🥰.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://staabm.github.io/images/og-images/oss-contributions-summary-2024.jpg" /><media:content medium="image" url="https://staabm.github.io/images/og-images/oss-contributions-summary-2024.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">My new PHPStan focus: multi-phpversion support</title><link href="https://staabm.github.io/2024/11/28/phpstan-php-version-in-scope.html" rel="alternate" type="text/html" title="My new PHPStan focus: multi-phpversion support" /><published>2024-11-28T00:00:00+00:00</published><updated>2024-11-28T00:00:00+00:00</updated><id>https://staabm.github.io/2024/11/28/phpstan-php-version-in-scope</id><content type="html" xml:base="https://staabm.github.io/2024/11/28/phpstan-php-version-in-scope.html"><![CDATA[<p><a href="https://staabm.github.io/2024/11/28/phpstan-php-version-in-scope.html#whats-already-done"><em>TL;DR: What’s already done?</em></a></p>

<p>In a recent article I was summarizing the problems and results of my work on <a href="https://staabm.github.io/2024/11/26/phpstan-mixed-types.html"><code>mixed</code> types in PHPStan</a>.
Now we will have a look at what comes next.</p>

<p>For a few years, I am now contributing to PHPStan with a focus on improving the type inference,
which means I am looking into code where <code>mixed</code> types are involved and how I can improve the situation.</p>

<p>In my opinion we are in a pretty good <code>mixed</code> type shape, as the most common problems I can think of seem to be addressed.
For sure new examples will show up, and we still will and have to continue to improve the situation.
I am no longer prioritizing <code>mixed</code> problems over other things in my PHPStan work, though.</p>

<h4 id="problem-space">Problem space</h4>

<p>So what’s ahead? My new focus area will be improving the PHPStan story around multi-phpversion supporting code.
This means focusing on stuff which is different between PHP versions and tasks/hurdles common to projects which are in the process of a PHP version upgrade.</p>

<p>If you want to cover your codebase cross several PHP versions, you need to set up a CI matrix with different PHP versions.
You also need multiple PHPStan baselines to ignore errors which are only relevant for a specific PHP version.
Such a setup brings additional complexity not everyone is willing to deal with.</p>

<p>In my experience most projects set up PHPStan only for a single PHP version and ignore the rest, which leaves a lot of potential errors undetected.</p>

<p>Another challenge you face over and over when upgrading PHP versions is the <code>resource</code> to objects migration.
There are <a href="https://php.watch/articles/resource-object">articles on the web</a> on this problem alone.
Different PHP versions use different types for some APIs - e.g. <a href="https://www.php.net/curl_init"><code>curl_init</code></a> or <a href="https://www.php.net/socket_create"><code>socket_create</code></a>,
to name a few - and as soon as you are planning a PHP upgrade you usually need to deal with supporting both signatures - <code>resource</code> and the corresponding object-types - in tandem for a while,
so can run your application on your current and your future production system at the same time.</p>

<p>The topic gets even more complicated in case you are building a tool, library or a framework as you usually need to support multiple PHP version for a longer time.
You also need to handle phasing out and adding support for new PHP versions to your compatibility matrix over and over,
which means you constantly need to answer questions like:</p>
<ul>
  <li>which code is going to be dead because of a min-php version raise?</li>
  <li>which code needs adjustments to support a new PHP version?</li>
  <li>how can we make sure that code which gets adapted for the new PHP version still works on the old PHP version?</li>
  <li>do we have a rough idea how many problems we need to solve?</li>
</ul>

<p>To help you answer this questions my goals are:</p>
<ul>
  <li>Projects which can only afford a single PHPStan CI job should detect as many cross-php version related errors as possible</li>
  <li>Running PHPStan on multiple PHP versions should be as frictionless as possible</li>
</ul>

<h4 id="how-can-you-support-my-effort">How can you support my effort?</h4>

<p>I think working on this thing will be a multi month freetime effort and will at least take several dozens of pull requests.</p>

<p>If you are hit by at least one of the problems I described above and feel the pain you should talk to your boss to <a href="https://github.com/sponsors/staabm">sponsor my free time efforts</a>,
so I can spend more time on it, and you have less problems to deal with in your daily job.</p>

<p>Your task to upgrade your employers codebases to PHP 8.4 may be already in the pipeline :-).</p>

<h3 id="whats-next">What’s next?</h3>

<p>The current plan is to make PHPStan aware of a narrowed <a href="https://github.com/phpstan/phpstan-src/pull/3642">PHP-Version within the current scope</a> and utilize this information in type inference and error reporting.
This means while analyzing code we no longer just use a fixed PHP version configured in e.g. PHPStan NEON configuration, but also narrow it further down based on the code at hand.
Nearly all rules in the PHPStan core and 1st party extensions need to be adjusted.</p>

<p>Let me give you a few examples which currently don’t work well, but should work much better after the project evolves:</p>

<p>At the time of writing PHPStan 2.0.2 will report null coalescing errors in your code only if you narrow down the PHP version by configuration.
This means you define the PHP version or version-range by NEON config, <code>composer.json</code> (as of PHPStan 2+) or implicitly by the PHP runtime version you are using for PHPStan.</p>

<p>Running the below example without additional configuration on PHP8 only will not yield any errors.
As of now you would need e.g. a separate CI job configured for PHP 7.3 or lower to catch the error.
In the future, I want PHPStan catch this error even when running on PHP8 or later and without additional configuration required:</p>

<pre><code class="language-php">
if (PHP_VERSION_ID &lt; 70400) {
    // should error about null coalescing assign operator,
    // which requires PHP 7.4+
    $y['y'] ??= [];
} else {
    $y['x'] ??= [];
}

</code></pre>

<p>Another example: PHPStan is using a single knowledge base for return and parameter types of functions and methods.
This information is narrowed down by PHPStan Extensions when e.g. parameter values are known at static analysis time.
In the future I want to improve the type inference e.g. for cases where PHP used <code>resource</code> types in the past, but uses class/object types in more modern versions:</p>

<pre><code class="language-php">
class MySocket
{
  public function create(): ?Socket
  {
    if (PHP_VERSION_ID &lt; 80000) {
        throw new RuntimeException('PHP 8.0 required');
    }

    // can only return `\Socket|false` but PHPStan sometimes
    // mixes it up with PHP7 `resource` type
    return socket_create(AF_INET, SOCK_DGRAM, SOL_UDP) ?: null;
  }
}

</code></pre>

<p>There are a lot of other problem areas, for which you see the errors only when PHPStan is configured with certain PHP versions:</p>
<ul>
  <li>named arguments</li>
  <li>parameter contravariance</li>
  <li>return type covariance</li>
  <li>non-capturing exception catches</li>
  <li>native union types</li>
  <li>several deprecated features around how php-src handles parameters</li>
  <li>class constants</li>
  <li>legacy constructors</li>
  <li>parameter type widening</li>
  <li><code>unset</code> cast</li>
  <li>multibyte string handling functions</li>
  <li>readonly properties</li>
  <li>readonly classes</li>
  <li>enums</li>
  <li>intersection types</li>
  <li>tentative return types</li>
  <li>array unpacking</li>
  <li>dynamic properties</li>
  <li>constants in traits</li>
  <li>php native attributes</li>
  <li>implicit parameter nullability</li>
</ul>

<p>What you just read about is the result of my initial research.
I am pretty sure we will shape new ideas after iterating on the problems involved and the solutions we come up with.</p>

<p>I will work through the mentioned problem areas one after another, which also means your developer experience when using PHP
version specific language features with PHPStan should improve over time, release after release.</p>

<p>Do these problems sound relevant to you?
Please spread the word about my free time project and <a href="https://phpc.social/@markusstaab/113559437972344037">retoot on Mastodon</a> or <a href="https://x.com/markusstaab/status/1862037669833769276">retweet on Twitter/X</a>.</p>

<h3 id="whats-already-done">What’s already done?</h3>

<p><em>this chapter will be updated to reflect the ongoing progress</em></p>

<h4 id="narrow-types-by-php_version_id">Narrow types by PHP_VERSION_ID</h4>

<p>The first step in this direction was already achieved by making PHPStan aware of <code>composer.json</code> defined PHP version requirements
and taking this knowledge into account to narrow constants like <code>PHP_VERSION_ID</code> et. all. since PHPStan 2.0.</p>

<p>There is a dedicated blog post about this topic already: <a href="https://staabm.github.io/2024/11/14/phpstan-php-version-narrowing.html">PHPStan PHP Version Narrowing</a></p>

<h4 id="report-deprecations-in-ini_-functions">Report deprecations in <code>ini_*()</code> functions</h4>

<p>At the time of writing there are ~20 deprecated php.ini options.
A <a href="https://github.com/phpstan/phpstan-deprecation-rules/pull/120">new PHPStan rule</a> was implemented which reports usages of <code>ini_*()</code> functions which use a deprecated option:</p>

<pre><code class="language-php">&lt;?php declare(strict_types = 1);

// new error:
// Call to function ini_get() with deprecated option 'assert.active'.
var_dump(ini_get('assert.active'));
</code></pre>

<p><a href="https://phpstan.org/r/e0076edd-fb0d-4490-96cc-d3ff4356b0ae">PHPStan playground snippet</a></p>

<h4 id="scope-narrowed-php-version">Scope narrowed PHP Version</h4>

<p>Starting with PHPStan 2.0.3 the php-version in will be narrowed down and builtin rules have been adjusted to report errors in more cases:</p>

<ul>
  <li><a href="https://github.com/phpstan/phpstan-src/pull/3642">warn for private final methods</a></li>
  <li><a href="https://github.com/phpstan/phpstan-src/pull/3663">non-capturing catch</a></li>
  <li><a href="https://github.com/phpstan/phpstan-src/pull/3662">named arguments</a></li>
</ul>

<p>For more details see the <a href="https://staabm.github.io/2024/11/28/phpstan-php-version-in-scope.html#whats-next">above background story</a>.</p>]]></content><author><name>Markus Staab</name></author><category term="PHPStan" /><summary type="html"><![CDATA[TL;DR: What’s already done?]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://staabm.github.io/images/og-images/phpstan-php-version-in-scope.jpg" /><media:content medium="image" url="https://staabm.github.io/images/og-images/phpstan-php-version-in-scope.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">A mixed type PHPStan journey</title><link href="https://staabm.github.io/2024/11/26/phpstan-mixed-types.html" rel="alternate" type="text/html" title="A mixed type PHPStan journey" /><published>2024-11-26T00:00:00+00:00</published><updated>2024-11-26T00:00:00+00:00</updated><id>https://staabm.github.io/2024/11/26/phpstan-mixed-types</id><content type="html" xml:base="https://staabm.github.io/2024/11/26/phpstan-mixed-types.html"><![CDATA[<p>A <code>mixed</code> typed represents the absence of type information. It is a union of all types, which means it can be anything.
This in turn leads to suboptimal PHPStan analysis results which can lead to missing errors or even false positives.</p>

<p>For a <a href="https://github.com/phpstan/phpstan-src/pulls?q=sort%3Aupdated-desc+is%3Apr+author%3Astaabm+is%3Amerged">few years</a>, I am now contributing to PHPStan with a focus on improving the type inference,
which means looking into code where <code>mixed</code> types are involved and figure out how the situation can be improved.</p>

<p>I will start to focus on a different PHPStan area soon, so I thought it would be a good time to summarize the achievements made.</p>

<p>In this article I want to share the most meaningful contributions to PHPStan core,
but also look at PHPStan extensions work which was helpful along the way.</p>

<h4 id="narrow-types-from-if-conditions">Narrow types from if-conditions</h4>

<p>What most situations we are looking at have in common is, that we have very little information at the beginning.
One useful tool to handle that is a subtractable type, which PHPStan is using for <code>mixed</code> for a long time already.
This means we don’t describe what we know about a type, but instead we narrow it down by what we know it is not:</p>

<pre><code class="language-php">
function doFoo($mixed) {
  if ($mixed) {
    // $mixed can be anything but a falsey type
    \PHPStan\dumpType($mixed); // mixed~(0|0.0|''|'0'|array{}|false|null)
  }

  if (!$mixed) {
    // $mixed can be anything but a truethy type
    \PHPStan\dumpType($mixed); // 0|0.0|''|'0'|array{}|false|null
  }
}

</code></pre>

<p>The subtractable type narrowing is used in PHPStan strict-comparisons (e.g. <code>===</code> or <code>!==</code>) and for some very specific but often used code patterns already.
I was looking at cases where it was still missing like, type-casts in conditions:</p>

<pre><code class="language-php">
class Test {

  private ?string $param;

  function show() : void {
    if ((int) $this-&gt;param) {
      \PHPStan\dumpType($this-&gt;param); // string
    } elseif ($this-&gt;param) {
      \PHPStan\dumpType($this-&gt;param); // non-falsy-string
    }
  }

  function show2() : void {
    if ((float) $this-&gt;param) {
      \PHPStan\dumpType($this-&gt;param); // string|null
    } elseif ($this-&gt;param) {
      \PHPStan\dumpType($this-&gt;param); // non-falsy-string
    }
  }

  function show3() : void {
    if ((bool) $this-&gt;param) {
      \PHPStan\dumpType($this-&gt;param); // non-falsy-string
    } elseif ($this-&gt;param) { // Elseif condition is always false.
      \PHPStan\dumpType($this-&gt;param); // *NEVER*
    }
  }

  function show4() : void {
    if ((string) $this-&gt;param) {
      \PHPStan\dumpType($this-&gt;param); // non-empty-string
    } elseif ($this-&gt;param) { // Elseif condition is always false.
      \PHPStan\dumpType($this-&gt;param); // *NEVER*
    }
  }
}

</code></pre>

<p>Next I was looking into how a cast on already subtracted mixed types influences the results:</p>

<pre><code class="language-php">
/**
 * @param int|0.0|''|'0'|array{}|false|null $moreThenFalsy
 */
function subtract(mixed $m, $moreThenFalsy) {
  if ($m !== true) {
    assertType("mixed~true", $m);
    assertType('bool', (bool) $m); // mixed could still contain something truthy
  }
  if ($m !== false) {
    assertType("mixed~false", $m);
    assertType('bool', (bool) $m); // mixed could still contain something falsy
  }
  if (!is_bool($m)) {
    assertType('mixed~bool', $m);
    assertType('bool', (bool) $m);
  }
  if (!is_array($m)) {
    assertType('mixed~array&lt;mixed, mixed&gt;', $m);
    assertType('bool', (bool) $m);
  }

  if ($m) {
    assertType("mixed~(0|0.0|''|'0'|array{}|false|null)", $m);
    assertType('true', (bool) $m);
  }
  if (!$m) {
    assertType("0|0.0|''|'0'|array{}|false|null", $m);
    assertType('false', (bool) $m);
  }
  if (!$m) {
    if (!is_int($m)) {
      assertType("0.0|''|'0'|array{}|false|null", $m);
      assertType('false', (bool)$m);
    }
    if (!is_bool($m)) {
      assertType("0|0.0|''|'0'|array{}|null", $m);
      assertType('false', (bool)$m);
    }
  }

  if (!$m || is_int($m)) {
    assertType("0.0|''|'0'|array{}|int|false|null", $m);
    assertType('bool', (bool) $m);
  }

  if ($m !== $moreThenFalsy) {
    assertType('mixed', $m);
    assertType('bool', (bool) $m); // could be true
  }

  if ($m != 0 &amp;&amp; !is_array($m) &amp;&amp; $m != null &amp;&amp; !is_object($m)) { // subtract more types then falsy
    assertType("mixed~(0|0.0|''|'0'|array&lt;mixed, mixed&gt;|object|false|null)", $m);
    assertType('true', (bool) $m);
  }
}

</code></pre>

<p>A case where we did not properly narrow types was in comparisons with <code>strlen()</code> and integer-range types:</p>

<pre><code class="language-php">
function narrowString(string $s) {
  $i = range(1, 5);
  if (strlen($s) == $i) {
    \PHPStan\dumpType($s); // non-empty-string
  }

  $i = range(2, 5);
  if (strlen($s) == $i) {
    \PHPStan\dumpType($s); // non-falsey-string
  }
}

</code></pre>

<p>This also works in a similar fashion when comparing the results of <code>substr()</code>:</p>

<pre><code class="language-php">
/**
 * @param non-empty-string $nonES
 * @param non-falsy-string $falsyString
 */
public function stringTypes(string $s, $nonES, $falsyString): void
{
  if (substr($s, 10) === $nonES) {
    assertType('non-empty-string', $s);
  }

  if (substr($s, 10) === $falsyString) {
    assertType('non-falsy-string', $s);
  }
}

</code></pre>

<p>A pretty complex field was to think about what <code>isset($array[$key])</code> means for the type of <code>$key</code>:</p>

<pre><code class="language-php">
/**
 * @param array&lt;int, string&gt; $intKeyedArr
 * @param array&lt;string, string&gt; $stringKeyedArr
 */
function narrowKey($mixed, string $s, int $i, array $generalArr, array $intKeyedArr, array $stringKeyedArr): void {
  if (isset($generalArr[$mixed])) {
    assertType('mixed~(array|object|resource)', $mixed);
  } else {
    assertType('mixed', $mixed);
  }
  assertType('mixed', $mixed);

  if (isset($generalArr[$i])) {
    assertType('int', $i);
  } else {
    assertType('int', $i);
  }
  assertType('int', $i);

  if (isset($intKeyedArr[$mixed])) {
    assertType('mixed~(array|object|resource)', $mixed);
  } else {
    assertType('mixed', $mixed);
  }
  assertType('mixed', $mixed);

  if (isset($intKeyedArr[$s])) {
    assertType("lowercase-string&amp;numeric-string&amp;uppercase-string", $s);
  } else {
    assertType('string', $s);
  }
  assertType('string', $s);

  if (isset($stringKeyedArr[$mixed])) {
    assertType('mixed~(array|object|resource)', $mixed);
  } else {
    assertType('mixed', $mixed);
  }
  assertType('mixed', $mixed);
}

function emptyString($mixed)
{
  // see https://3v4l.org/XHZdr
  $arr = ['' =&gt; 1, 'a' =&gt; 2];
  if (isset($arr[$mixed])) {
    assertType("''|'a'|null", $mixed);
  } else {
    assertType('mixed', $mixed); // could be mixed~(''|'a'|null)
  }
  assertType('mixed', $mixed);
}

function numericString($mixed, int $i, string $s)
{
  $arr = ['1' =&gt; 1, '2' =&gt; 2];
  if (isset($arr[$mixed])) {
    assertType("1|2|'1'|'2'|float|true", $mixed);
  } else {
    assertType('mixed', $mixed);
  }
  assertType('mixed', $mixed);
}

function arrayAccess(\ArrayAccess $arr, $mixed) {
  if (isset($arr[$mixed])) {
    assertType("mixed", $mixed);
  } else {
    assertType('mixed', $mixed);
  }
  assertType('mixed', $mixed);
}

</code></pre>

<h4 id="immediate-invoked-function-expression-iife">Immediate-invoked-function-expression (IIFE)</h4>

<p>A pattern know from javascript projects and sometimes also popping up in PHP code is immediately-invoked-function-expressions.
Type inference improvements for this pattern in particular was implemented to support <a href="https://github.com/twigstan/twigstan">TwigStan</a>:</p>

<pre><code class="language-php">
/** @param array{date: DateTime} $c */
function main(mixed $c): void{
  assertType('array{date: DateTime}', $c);
  $c['id']=1;
  assertType('array{date: DateTime, id: 1}', $c);

  $x = (function() use (&amp;$c) {
    assertType("array{date: DateTime, id: 1}", $c);
    $c['name'] = 'ruud';
    assertType("array{date: DateTime, id: 1, name: 'ruud'}", $c);
    return 'x';
  })();

  assertType("array{date: DateTime, id: 1, name: 'ruud'}", $c);
}


/** @param array{date: DateTime} $c */
function main2(mixed $c): void{
  assertType('array{date: DateTime}', $c);
  $c['id']=1;
  $c['name'] = 'staabm';
  assertType("array{date: DateTime, id: 1, name: 'staabm'}", $c);

  $x = (function() use (&amp;$c) {
    assertType("array{date: DateTime, id: 1, name: 'staabm'}", $c);
    $c['name'] = 'ruud';
    assertType("array{date: DateTime, id: 1, name: 'ruud'}", $c);
    return 'x';
  })();

  assertType("array{date: DateTime, id: 1, name: 'ruud'}", $c);
}

</code></pre>

<h4 id="new-phpstan-doc-types">New PHPStan doc-types</h4>

<p>To express types better a few phpdoc improvements have been implemented</p>
<ul>
  <li><a href="https://staabm.github.io/2022/08/11/phpstan-non-falsy-string.html"><code>non-falsy-string</code></a></li>
  <li><a href="https://github.com/phpstan/phpstan-src/pull/1804"><code>@param-out</code></a></li>
  <li>support for <a href="https://github.com/phpstan/phpstan-src/pull/1082"><code>value-of&lt;BackedEnum&gt;</code></a></li>
  <li><a href="https://github.com/phpstan/phpdoc-parser/pull/253"><code>@pure-unless-callable-is-impure</code></a></li>
  <li><a href="https://github.com/phpstan/phpdoc-parser/pull/259"><code>@pure-unless-parameter-passed</code></a></li>
  <li><a href="https://staabm.github.io/2024/01/15/phpstan-require-extends-implements.html"><code>@require-extends and @require-implements</code></a></li>
</ul>

<p>Whats great about new phpdoc types is, that we can utilize them in stubs shipped with PHPStan releases, but they can also be used in any userland php codebase to help improve static analysis results.</p>

<p>If PHPStan would infer all this information from the source it would be a lot slower as it is right now.</p>

<p>By adding doc-types you also give information/semantic to the code and tell about your intents.
This is something not only helpful for the tooling but also developers reading your implementation.</p>

<p>That way PHPStan can tell you whether expectations are right or whether you are lying :-).</p>

<h4 id="new-phpstan-extension-types">New PHPStan Extension types</h4>

<p>Using <a href="https://github.com/phpstan/phpstan-src/pull/3083">ParameterOutTypeExtensions</a> by-reference parameters can be programmatically and context-sensitively narrowed for functions/methods since PHPStan 1.11.6.
This was later on used to improve by-reference parameter type inference after calls to <code>parse_str</code> and <code>preg_match*</code>.</p>

<h4 id="utilizing-information-outside-the-php-source">Utilizing information outside the PHP Source</h4>

<p>A different take was used to improve type inference of the <code>$matches</code> by-ref parameter of <code>preg_match()</code> based on a REGEX abstract syntax tree.
It’s a complex story on its own with a <a href="https://staabm.github.io/2024/07/05/array-shapes-for-preg-match-matches.html">dedicated array shape match inference article</a>.</p>

<p>Similar improvements landed for the <code>printf()</code> family of functions, see <a href="https://staabm.github.io/2022/06/23/phpstan-sprintf-sscanf-inference.html">PHPStan sprintf/sscanf type inference</a>.</p>

<p>Last but not least a PHPStan extension named <code>phpstan-dba</code> was created which introspects the database schema
to implement type inference for the database access layer via SQL abstract syntax tree.
This is covered by a <a href="https://staabm.github.io/archive.html#phpstan-dba">series of blog posts</a>.</p>

<h3 id="after-one-focus-area-is-before-the-next">After one focus area is before the next</h3>

<p>This article highlighted only a few of the many contributions I made to PHPStan in the <a href="https://staabm.github.io/2023/12/07/contribution-summary-2023.html">last</a> <a href="https://staabm.github.io/2022/12/20/2022-wrap-up.html">years</a>.</p>

<p>A big thank-you goes out to <a href="https://github.com/sponsors/staabm">all my sponsors and supporters</a>, who make it possible for me to work on PHPStan and other open-source projects.</p>

<p>While closing this type inference focus chapter, I am looking forward to the next challenges.
What comes up next will be the topic of a <a href="https://staabm.github.io/2024/11/28/phpstan-php-version-in-scope.html">future blog post</a>.</p>

<p>stay tuned ⚡️</p>]]></content><author><name>Markus Staab</name></author><category term="PHPStan" /><summary type="html"><![CDATA[A mixed typed represents the absence of type information. It is a union of all types, which means it can be anything. This in turn leads to suboptimal PHPStan analysis results which can lead to missing errors or even false positives.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://staabm.github.io/images/og-images/phpstan-mixed-types.jpg" /><media:content medium="image" url="https://staabm.github.io/images/og-images/phpstan-mixed-types.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>