PHPStan BlogFind Bugs In Your Code Without Writing Tests!2025-04-27T00:00:00Zhttps://phpstan.org/Ondřej Mirtesondrej@mirtes.czRestricted Usage Extensions - You Don't Always Need a Custom Rule2025-04-27T00:00:00Zhttps://phpstan.org/blog/restricted-usage-extensions-you-dont-always-need-custom-rule<p>Recently I’ve wanted to implement support for the <code>@internal</code> PHPDoc tag. The good news was there was already a similar set of rules in <a href="https://github.com/phpstan/phpstan-deprecation-rules">phpstan-deprecation-rules</a> for the <code>@deprecated</code> tag. The bad news was the logic around <code>@deprecated</code> was hardcoded there, meaning I’d have to copy and adjust all the rules.</p>
<p>I thought there should be a better way because this is a very common use-case and not everyone should be forced to reinvent the same wheel. If people have a need for rules around attributes like <a href="https://github.com/DaveLiddament/php-language-extensions#namespacevisibility"><code>#[NamespaceVisibility]</code></a> or <a href="https://github.com/DaveLiddament/php-language-extensions#friend"><code>#[Friend]</code></a>, it should not be their job to figure out all the rules needed for a comprehensive coverage of these restrictions.</p>
<p>Take methods for example: Everyone would implement a rule for a traditional method call like <code>$foo->doBar(1, 2, 3)</code>, but only a few would remember that <a href="https://wiki.php.net/rfc/first_class_callable_syntax">first-class callables</a> like <code>$foo->doBar(...)</code> should be covered as well.</p>
<p>Or class names. The new extension currently <a href="https://github.com/phpstan/phpstan-src/blob/ecfc5417bb2bd2f9f478f0c005b10ab1937cf149/src/Rules/ClassNameUsageLocation.php#L18-L43">covers 26 locations</a> where a class name can occur in PHP code or <a href="https://phpstan.org/writing-php-code/phpdocs-basics">PHPDocs</a>. The great advantage of the extension interface is that it’s future-proof and we can call it as new places with class names appear in the PHP language or as we add new PHPDoc features.</p>
<p>Besides taking advantage of the new extensions to implement the <a href="https://github.com/phpstan/phpstan-src/tree/2.1.x/src/Rules/InternalTag"><code>@internal</code> tag rules</a>, I went back to phpstan-deprecation-rules and <a href="https://github.com/phpstan/phpstan-deprecation-rules/compare/96f93574dd20a293df14700e84502123103178d7...9d8e7d4e32711715ad78a1fb6ec368df9af01fdf">refactored</a> it with these new capabilities. It fixed small inconsistencies like missing class name check for static property fetch, wrong class names in some error messages, or reported line numbers in multi-line function signatures. With <a href="/blog/what-is-bleeding-edge">bleeding edge</a> enabled, it will report deprecated class names in more places.</p>
<p>The Restricted Usage Extensions were released in <a href="https://github.com/phpstan/phpstan/releases/tag/2.1.13">PHPStan 2.1.13</a>. Head over to the <a href="/developing-extensions/restricted-usage-extensions">developer guide</a> to learn how to use them!</p>
<hr>
<p>Do you like PHPStan and use it every day? <a href="/sponsor"><strong>Consider supporting further development of PHPStan</strong></a>. I’d really appreciate it!</p>
PHPStan 2.1: Support For PHP 8.4's Property Hooks, and More!2024-12-31T00:00:00Zhttps://phpstan.org/blog/phpstan-2-1-support-for-php-8-4-property-hooks-more<p>This release is a culmination of the last four months of my work. Beginning in September, I branched 2.0.x off of 1.12.x. In order to support PHP 8.4 syntax, I had to upgrade to <a href="https://github.com/nikic/PHP-Parser/releases/tag/v5.0.0">PHP-Parser v5</a>. In order to upgrade to PHP-Parser v5, I had to release PHPStan 2.0. <a href="/blog/phpstan-2-0-released-level-10-elephpants">Which I did on November 11th</a>, alongside the cute and also long-awaited PHPStan elephpant. PHPStan’s fans ordered 750 of those! They will be manufactured in China and sent on a ship to Europe which is going to take some time. I hope they’re going to find themselves in the hands of their happy owners in May-June 2025, as promised in the order confirmation emails.</p>
<p>Today’s <a href="https://github.com/phpstan/phpstan/releases/tag/2.1.0">PHPStan 2.1</a> brings full understanding of flagship PHP 8.4 features like <a href="https://wiki.php.net/rfc/property-hooks">property hooks</a>, <a href="https://wiki.php.net/rfc/asymmetric-visibility-v2">asymmetric visibility</a>, and the <a href="https://wiki.php.net/rfc/deprecated_attribute"><code>#[Deprecated]</code> attribute</a>. As usual I’m going to describe what considerations went into supporting these language features in PHPStan.</p>
<h2 id="property-hooks" tabindex="-1">Property hooks <a class="header-anchor ml-1 text-gray-300 hover:text-black" href="#property-hooks">#</a></h2>
<p>This is a massive and complex new language feature which is obvious just by looking at the length of <a href="https://wiki.php.net/rfc/property-hooks">the RFC</a>. After reading it I wrote down about 70-80 todos about what should be done in PHPStan’s codebase in order to properly support it 😅 Out of those there are still <a href="https://github.com/phpstan/phpstan/issues/12336">around 32 todos left</a>. They are not necessary enough to be included in this initial release, but it’d be nice to have them.</p>
<p>In short, property hooks let you intercept reading and writing properties with your own code. They also allow properties to be declared on interfaces for the first time in PHP. You can have virtual properties without a backing value stored in the object. And last but not least, hook bodies can be written with either long or short syntax.</p>
<p>Property hooks are very similar to class methods, but they are also their own thing. Which means I had to adjust or replicate a lot of existing rules around methods:</p>
<ul>
<li><a href="/r/041c903d-87c4-4de1-97aa-12e5c80975e5">Missing return</a></li>
<li><a href="/r/1d82e9b9-0e10-4b3b-a444-f5ad65523b48">Returned type</a></li>
<li><a href="/r/e5d7d8c8-4133-4f3a-b003-7c877931d152">Existence of parameter types</a></li>
<li><a href="/r/0f0103a7-6226-4dbb-894b-3462b831ab8c">Compatibility of PHPDocs with parameter types</a></li>
<li><a href="/r/611182a8-ac7f-4155-ab1a-d7553511282e">Syntax errors in PHPDocs</a></li>
<li><a href="/r/02f64b10-6579-4cbc-b1b8-939a32d89f13">Unknown <code>@phpstan-</code> tags in PHPDocs</a></li>
<li><a href="/blog/bring-your-exceptions-under-control">Checked exceptions thrown in the body must either be handled or documented with <code>@throws</code></a></li>
<li><a href="/r/b721ee1f-d2df-4d28-b84c-fb072f19b97a">Rule for unused private properties must ignore property access inside hooks</a></li>
<li><a href="/r/7083a3bb-fc8b-4e2b-aa68-722a87e3a6e6">Attributes above property hooks</a></li>
</ul>
<p>Additionally, there are new special rules for property hooks:</p>
<ul>
<li>Backing value of non-virtual property <a href="/r/386f6fa6-2c6e-4e22-818f-47d35fa1ee28">must be read</a> in get hook</li>
<li>Backing value of non-virtual property <a href="/r/ce9480ce-fdf1-4ff7-9377-105ca7fa4313">must be always assigned</a> in set hook</li>
</ul>
<p>Set hook parameter type can be wider than the actual property type so PHPStan also has to be aware of the types it <a href="/r/5b12238d-8206-4194-a30c-19ad4071bbf9">allows to assign to properties in and out of hooks</a>:</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token keyword">class</span> <span class="token class-name-definition class-name">Foo</span>
<span class="token punctuation">{</span>
<span class="token keyword">public</span> <span class="token keyword type-declaration">int</span> <span class="token variable">$i</span> <span class="token punctuation">{</span>
get <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token variable">$this</span><span class="token operator">-></span><span class="token property">i</span> <span class="token operator">-</span> <span class="token number">10</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token function">set</span> <span class="token punctuation">(</span><span class="token keyword type-declaration">int</span><span class="token operator">|</span><span class="token keyword type-declaration">string</span> <span class="token variable">$value</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token variable">$this</span><span class="token operator">-></span><span class="token property">i</span> <span class="token operator">=</span> <span class="token variable">$value</span><span class="token punctuation">;</span> <span class="token comment">// string not allowed</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token variable">$f</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Foo</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token variable">$f</span><span class="token operator">-></span><span class="token property">i</span> <span class="token operator">=</span> <span class="token function">rand</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token string single-quoted-string">'10'</span> <span class="token punctuation">:</span> <span class="token string single-quoted-string">'foo'</span><span class="token punctuation">;</span> <span class="token comment">// string allowed</span></code></pre>
<p>Property hooks also bring properties that <a href="/r/2494f530-17c0-4da7-8d91-263d171766a9">can only be read, or can only be written</a>, to the language:</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token keyword">interface</span> <span class="token class-name-definition class-name">Foo</span>
<span class="token punctuation">{</span>
<span class="token keyword">public</span> <span class="token keyword type-declaration">int</span> <span class="token variable">$i</span> <span class="token punctuation">{</span> get<span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token keyword">public</span> <span class="token keyword type-declaration">int</span> <span class="token variable">$j</span> <span class="token punctuation">{</span> set<span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token class-name type-declaration">Foo</span> <span class="token variable">$f</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">void</span> <span class="token punctuation">{</span>
<span class="token variable">$f</span><span class="token operator">-></span><span class="token property">i</span> <span class="token operator">=</span> <span class="token number">42</span><span class="token punctuation">;</span> <span class="token comment">// invalid</span>
<span class="token keyword">echo</span> <span class="token variable">$f</span><span class="token operator">-></span><span class="token property">j</span><span class="token punctuation">;</span> <span class="token comment">// invalid </span>
<span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>And my final remark is that access to properties can now be <a href="/r/39b8d6a8-99db-4bfc-a99b-9af4b214abee">expected to throw any exception</a>:</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token keyword">interface</span> <span class="token class-name-definition class-name">Foo</span>
<span class="token punctuation">{</span>
<span class="token keyword">public</span> <span class="token keyword type-declaration">int</span> <span class="token variable">$i</span> <span class="token punctuation">{</span> get<span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token class-name type-declaration">Foo</span> <span class="token variable">$f</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">void</span> <span class="token punctuation">{</span>
<span class="token keyword">try</span> <span class="token punctuation">{</span>
<span class="token keyword">echo</span> <span class="token variable">$f</span><span class="token operator">-></span><span class="token property">i</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">MyCustomException</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// dead catch on PHP 8.3-, but not on 8.4+</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>Even non-hooked properties are subject to this, because they can be overwritten in a subclass with a hook. If you want to persuade PHPStan that access to your properties cannot throw exceptions, make them <code>private</code> or <code>final</code>.</p>
<p>Hooks can be documented with <code>@throws</code> PHPDoc tag which <a href="/blog/precise-try-catch-finally-analysis">improves analysis of try-catch blocks</a>, same as with functions and methods.</p>
<p>I also posted a <a href="https://github.com/phpstan/phpstan/discussions/12337">community update</a> which goes into depth what you should do if you want to support property hooks in your custom rules.</p>
<p>A huge thanks to <a href="https://github.com/kukulich">Jarda Hanslík</a> for <a href="https://github.com/Roave/BetterReflection/pull/1462">initial property hooks support</a> in BetterReflection, and for <a href="https://github.com/Roave/BetterReflection/pull/1465">many</a> <a href="https://github.com/Roave/BetterReflection/pull/1466">many</a> <a href="https://github.com/Roave/BetterReflection/pull/1470">subsequent</a> <a href="https://github.com/Roave/BetterReflection/pull/1471">fixes</a> for bugs I found 😅</p>
<p>And also a huge thanks to <a href="https://github.com/nikic">Nikita Popov</a>, not just for his work on PHP-Parser in general, but also for merging a <a href="https://github.com/nikic/PHP-Parser/pull/1049">couple</a> of <a href="https://github.com/nikic/PHP-Parser/pull/1051">fixes</a> I made, and also for fixing a <a href="https://github.com/nikic/PHP-Parser/issues/1053">couple</a> of other <a href="https://github.com/nikic/PHP-Parser/issues/1050">issues</a> I reported.</p>
<h2 id="asymmetric-visibility" tabindex="-1">Asymmetric visibility <a class="header-anchor ml-1 text-gray-300 hover:text-black" href="#asymmetric-visibility">#</a></h2>
<p>PHP now allows for different properties visibility when reading then and when assigning them. So property can for example be publicly readable, but only privately writable.</p>
<p>This feature is much <a href="https://wiki.php.net/rfc/asymmetric-visibility-v2">easier to grasp</a> than property hooks, but also comes with a few gotchas.</p>
<ul>
<li>Property that’s public-readable or protected-readable, but only privately writable, is <a href="/r/eb32ab7b-1f3d-4abf-898a-b22523e88e8c">implicitly final and cannot be overridden</a>.</li>
<li>Acquiring reference to a property follows write visibility, not read visibility, so I added <a href="/r/add82dc4-8cd2-4d5a-bed4-05f8d51e8cfa">a new rule for that</a>.</li>
</ul>
<p>Similarly to property hooks, I also have a few nice-to-have todos <a href="https://github.com/phpstan/phpstan/issues/12347">left for later</a>.</p>
<h2 id="%23%5Bdeprecated%5D-attribute" tabindex="-1"><code>#[Deprecated]</code> attribute <a class="header-anchor ml-1 text-gray-300 hover:text-black" href="#%23%5Bdeprecated%5D-attribute">#</a></h2>
<p><a href="https://wiki.php.net/rfc/deprecated_attribute">This attribute</a> allows the PHP engine <a href="https://3v4l.org/MEJTq">trigger deprecated warnings</a>.</p>
<p>I suspect it’s not that useful in practice because it’s allowed only above functions, methods, and class constants. Most notably, it can’t be used to mark entire classes deprecated. It also does not work for properties. This is something that PHP could address in the future.</p>
<p>Nevertheless, you can use it in PHPStan 2.1 in tandem with <a href="https://github.com/phpstan/phpstan-deprecation-rules">phpstan-deprecation-rules</a> to mark deprecated code and have it reported when used.</p>
<hr>
<p>Do you like PHPStan and use it every day? <a href="/sponsor"><strong>Consider sponsoring</strong> further development of PHPStan on GitHub Sponsors and also <strong>subscribe to PHPStan Pro</strong></a>! I’d really appreciate it!</p>
PHPStan 2.0 Released With Level 10 and Elephpants!2024-11-11T00:00:00Zhttps://phpstan.org/blog/phpstan-2-0-released-level-10-elephpants<img src="/images/phpstan-2-0.jpg" alt="PHPStan 2.0" class="rounded-lg mb-8">
<p>PHPStan 1.0 was released a little over <a href="/blog/phpstan-1-0-released">three years ago</a>. I’m happy to report the project is thriving! We did about 176 new releases since then, implementing new features, fixing bugs, and laying the groundwork for 2.0. Yeah, we didn’t catch a break and we didn’t rest on our laurels.</p>
<p>I’ve been looking forward to 2.0 for a long time. Everyone will finally be able to enjoy new features we’ve been working on. Some of them have already been enjoyed by <a href="/blog/what-is-bleeding-edge">early adopters</a> for more than two years.</p>
<p>But code and analysis changes are not the only things being released today! PHPStan joins <a href="https://elephpant.me/">the family of elephpants</a> with its own take on the legendary PHP mascot.</p>
<p class="mt-4 rounded-lg mb-8 border border-blue-500 p-4 hover:border-blue-400"><a href="/merch"><img src="/images/elephpant-trio.png" alt="PHPStan Elephpant"></a></p>
<p>You can also order PHPStan T-shirts again, in both blue and white, and straight/fitted cut:</p>
<a href="/merch" class="flex w-full justify-center mt-4 rounded-lg mb-8 border border-blue-500 py-2 hover:border-blue-400">
<img class="aspect-[1610/913] max-w-xs w-1/2" src="/images/tshirt-2024-blue-straight.jpg">
<img class="aspect-[1610/913] max-w-xs w-1/2" src="/images/tshirt-2024-white-fitted.jpg">
</a>
<p>We’re <a href="/merch">accepting orders</a> for the next four weeks (until Sunday, December 8th), so don’t miss the opportunity and order the elephpant and T-shirt today! I can’t wait to see them in the wild.</p>
<hr>
<p>And now to the code part. The <a href="https://github.com/phpstan/phpstan/releases/tag/2.0.0">comprehensive release notes</a> are massive, consisting of over 180 items. That’s why, for the first time ever, PHPStan 2.0 also comes with easy-to-follow <a href="https://github.com/phpstan/phpstan/blob/2.1.x/UPGRADING.md">upgrading guide</a>, for both end users and extension developers.</p>
<p>It’s really hard to pick and choose my favourite features and changes from 2.0, but I’ll try. Here we go:</p>
<h2 id="level-10" tabindex="-1">Level 10 <a class="header-anchor ml-1 text-gray-300 hover:text-black" href="#level-10">#</a></h2>
<p>New challenge for the level max enthusiasts! Previously added level 9 acknowledges using <code>mixed</code> in your code isn’t actually safe at all, and that you should really do something about it. But it has some blind spots and still lets some errors through. Internally it’s named <code>checkExplicitMixed</code>. Meaning it will report errors for explicitly-typed <code>mixed</code> values in your code.</p>
<p>Level 6 forces you to add missing types. If you manage to skip that with the help of <del>cheating</del>, ahem, <a href="/user-guide/baseline">the baseline</a>, or if you call a third party code that doesn’t use a lot of return types, unknown types may be present in your code during analysis. These are implicitly-typed <code>mixed</code>, and they will be picked up by level 10</p>
<h2 id="list-type" tabindex="-1">List type <a class="header-anchor ml-1 text-gray-300 hover:text-black" href="#list-type">#</a></h2>
<p>PHP arrays are really powerful, but they represent several computer science concepts in a single data structure, and sometimes it’s difficult to work with that. That’s why it’s useful to narrow it down when we’re sure we only want a single concept like a list.</p>
<p><a href="https://en.wikipedia.org/wiki/List_(abstract_data_type)">List</a> in PHPStan is an array with sequential integer keys starting at 0 and with no gaps. It joins <a href="/writing-php-code/phpdoc-types">many other advanced types expressible in PHPDocs</a>:</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token comment">/** @param list<int> $listOfIntegers */</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function-definition function">doFoo</span><span class="token punctuation">(</span><span class="token keyword type-hint">array</span> <span class="token variable">$listOfIntegers</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">void</span>
<span class="token punctuation">{</span>
<span class="token punctuation">}</span></code></pre>
<h2 id="lower-memory-consumption-leads-to-faster-performance" tabindex="-1">Lower memory consumption leads to faster performance <a class="header-anchor ml-1 text-gray-300 hover:text-black" href="#lower-memory-consumption-leads-to-faster-performance">#</a></h2>
<p>PHPStan used to be a really hungry beast. To the point of being killed by CI runners because it consumed not just all the memory up to the <code>memory_limit</code> in php.ini, but also all the memory assigned to the runner hardware.</p>
<p>Now it’s a less hungry beast. How did I make it happen? It’s useful to realize what’s going on inside a running program. It’s fine to consume memory as useful things are being achieved, but in order to consume less memory in total, it has to be freed afterwards so that it can be used again by different data needed when analysing the next file in line.</p>
<p>To debug memory leaks, I use the <a href="https://github.com/BitOne/php-meminfo"><code>php-meminfo</code></a> extension. I quickly realized that most of the memory is occupied by <a href="/developing-extensions/abstract-syntax-tree">AST</a> nodes. PHP frees the memory occupied by an object when there are no more references to it <sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup>. It didn’t work in case of AST nodes because they kept pointing at each other:</p>
<img class="mb-8" src="/images/mermaid-6d0ce15eb0039ff974c3884feda067b5eec0efe304ee41e31933d7bf3ac748a0.svg" />
<p>Getting rid of <code>parent</code>/<code>previous</code>/<code>next</code> node attributes is a backward compatibility break for custom rules that read them. I’ve written <a href="/blog/preprocessing-ast-for-custom-rules">an article on how to make these rules work again</a> even without those memory-consuming references.</p>
<p>The object graph is now obviously cleaner and with no reference cycles:</p>
<img class="mb-8" src="/images/mermaid-46470766f874d0ff9817a88d3b95b15dfaed05f2782e7cb6db9ff8e2cf7879fb.svg" />
<p>In my testing PHPStan now consumes around 50–70 % less memory on huge projects with thousands of files. Analysing PrestaShop 8.0 codebase now takes 3 minutes instead of 9 minutes in GitHub Actions with 2 CPU cores.</p>
<h2 id="validate-inline-phpdoc-%40var-tag-type" tabindex="-1">Validate inline PHPDoc <code>@var</code> tag type <a class="header-anchor ml-1 text-gray-300 hover:text-black" href="#validate-inline-phpdoc-%40var-tag-type">#</a></h2>
<p>There are multiple problems with inline <code>@var</code> PHPDoc tag. PHP developers use it for two main reasons:</p>
<ul>
<li>To fix wrong 3rd party PHPDocs. A dependency <sup class="footnote-ref"><a href="#fn2" id="fnref2">[2]</a></sup> might have <code>@return string</code> in a PHPDoc but in reality can return <code>null</code> as well.</li>
<li>To narrow down the returned type. When a function returns <code>string|null</code> but we know that in this case it can only return <code>string</code>.</li>
</ul>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token comment">/** @var Something $a */</span>
<span class="token variable">$a</span> <span class="token operator">=</span> <span class="token function">makeSomething</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>By looking at the analysed code we can’t really tell which scenario it is. That’s why PHPStan always trusted the type in <code>@var</code> and didn’t report any possible mistakes. Obviously that’s dangerous because the type in <code>@var</code> can get out of sync and be wrong really easily. But I came up with an idea what we could report without any false positives, keeping existing use-cases in mind.</p>
<p>PHPStan 2.0 validates the inline <code>@var</code> tag type against the native type of the assigned expression. It finds the lies spread around in <code>@var</code> tags:</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token keyword">function</span> <span class="token function-definition function">doFoo</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">string</span>
<span class="token punctuation">{</span>
<span class="token comment">// ...</span>
<span class="token punctuation">}</span>
<span class="token comment">/** @var string|null $a */</span>
<span class="token variable">$a</span> <span class="token operator">=</span> <span class="token function">doFoo</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// PHPDoc tag @var with type string|null is not subtype of native type string.</span></code></pre>
<p>It doesn’t make sense to allow <code>string|null</code>, because the type can never be <code>null</code>. PHPStan says “string|null is not subtype of native type string”, implying that only subtypes are allowed. Subtype is the same type or narrower, meaning that <code>string</code> or <code>non-empty-string</code> would be okay.</p>
<p>By default PHPStan isn’t going to report anything about the following code:</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token comment">/** @return string */</span>
<span class="token keyword">function</span> <span class="token function-definition function">doFoo</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token comment">// ...</span>
<span class="token punctuation">}</span>
<span class="token comment">/** @var string|null $a */</span>
<span class="token variable">$a</span> <span class="token operator">=</span> <span class="token function">doFoo</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Because the <code>@return</code> PHPDoc might be wrong and that’s what the <code>@var</code> tag might be trying to fix. If you want this scenario to be reported too, enable <a href="/config-reference#reportwrongphpdoctypeinvartag"><code>reportWrongPhpDocTypeInVarTag</code></a>, or install <a href="https://github.com/phpstan/phpstan-strict-rules">phpstan-strict-rules</a>.</p>
<p>I’d like the PHP community to use inline <code>@var</code> tags less and less over time. There are many great alternatives that promote good practices and code deduplication: <a href="/writing-php-code/phpdoc-types#conditional-return-types">Conditional return types</a>, <a href="/writing-php-code/narrowing-types#custom-type-checking-functions-and-methods"><code>@phpstan-assert</code></a>, <a href="/blog/generics-in-php-using-phpdocs">generics</a>, <a href="/user-guide/stub-files">stub files</a> for overriding 3rd party PHPDocs, or <a href="/developing-extensions/dynamic-return-type-extensions">dynamic return type extensions</a>.</p>
<h2 id="checking-truthiness-of-%40phpstan-pure-with-impure-points" tabindex="-1">Checking truthiness of <code>@phpstan-pure</code> with impure points <a class="header-anchor ml-1 text-gray-300 hover:text-black" href="#checking-truthiness-of-%40phpstan-pure-with-impure-points">#</a></h2>
<p>Pure functions always return the same value for the same input (arguments and current object state). They also don’t have any side effects like depending on current time, random number generator, or IO like reading file contents or accessing resources over the network.</p>
<p>It’s useful to mark functions and methods with <code>@phpstan-pure</code> if their logic should be pure. PHPStan 2.0 now enforces this annotation, meaning it will report any impure code inside as an error.</p>
<p>We achieved that with the help of impure points. For every statement and expression type PHP supports PHPStan decides if it’s pure or not.</p>
<p>Knowing impure points for any code also helps PHPStan to report more dead code. There’s no point in calling a pure method on a separate line without using its result. If it does not have any side effect, and we’re not using the returned value, then we can safely delete this line:</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token comment">// Call to method Foo::pureMethod() on a separate line has no effect.</span>
<span class="token variable">$this</span><span class="token operator">-></span><span class="token function">pureMethod</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Also: if a <code>void</code> method does not have any impure points, it doesn’t have to exist:</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token comment">// Method Foo::add() returns void but does not have any side effects.</span>
<span class="token keyword">private</span> <span class="token keyword">function</span> <span class="token function-definition function">add</span><span class="token punctuation">(</span><span class="token keyword type-hint">int</span> <span class="token variable">$a</span><span class="token punctuation">,</span> <span class="token keyword type-hint">int</span> <span class="token variable">$b</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">void</span>
<span class="token punctuation">{</span>
<span class="token variable">$c</span> <span class="token operator">=</span> <span class="token variable">$a</span> <span class="token operator">+</span> <span class="token variable">$b</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre>
<h2 id="less-caching%2C-disk-space-cleanup" tabindex="-1">Less caching, disk space cleanup <a class="header-anchor ml-1 text-gray-300 hover:text-black" href="#less-caching%2C-disk-space-cleanup">#</a></h2>
<p>Getting rid of a cache without slowing down the analysis is a clear win. Less stuff to invalidate, less stuff to worry about, less disk space to occupy.</p>
<p>PHPStan used to cache information whether a function is variadic because of <a href="https://www.php.net/manual/en/function.func-get-args.php"><code>func_get_args()</code></a> call or similar in its body. I realized we have this information freshly available in each run anyway, so we don’t need to cache it. This saves literally tens of thousands of files that PHPStan previously needed to save to disk and then read.</p>
<p>PHPStan now solely relies on the <a href="https://phpstan.org/user-guide/result-cache">result cache</a> to speed up the analysis.</p>
<p>We took this opportunity to clean up old cache items on disk, so on the first PHPStan 2.0 run you might see some previously occupied disk space free up. On a proprietary project I test PHPStan on it freed up about 1 GB of space.</p>
<hr>
<h2 id="the-future" tabindex="-1">The future <a class="header-anchor ml-1 text-gray-300 hover:text-black" href="#the-future">#</a></h2>
<p>I said it <a href="/blog/phpstan-1-0-released#a-bright-future">three years ago</a>, and I’m saying it again. PHPStan’s future is bright. It’s my full-time job thanks to <a href="https://phpstan.org/blog/introducing-phpstan-pro">PHPStan Pro</a> and <a href="/sponsor">GitHub Sponsors</a>, splitting the revenue roughly 50-50 between them.</p>
<p>I love my job, I love PHP and I don’t plan to stop. Contributors are <a href="https://github.com/phpstan/phpstan-src/graphs/contributors?from=11.+11.+2023">very active</a> and indispensable. PHPStan has a thriving <a href="/user-guide/extension-library">ecosystem of extensions</a> as well.</p>
<p>I have a lot of ambitious and even crazy ideas I’d like to try out. But the first thing I will address once the dust settles on 2.0 is adding support for PHP 8.4. You can expect it to be added before the end of 2024.</p>
<hr>
<p>Do you like PHPStan and use it every day? <a href="/sponsor"><strong>Consider sponsoring</strong> further development of PHPStan on GitHub Sponsors and also <strong>subscribe to PHPStan Pro</strong></a>! I’d really appreciate it!</p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>That’s called reference counting. <a href="#fnref1" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn2" class="footnote-item"><p>That probably doesn’t use static analysis. <a href="#fnref2" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>
PHPStan 1.12: Road to PHPStan 2.02024-08-27T00:00:00Zhttps://phpstan.org/blog/phpstan-1-12-road-to-phpstan-2-0<p>After three years since the initial <a href="/blog/phpstan-1-0-released">PHPStan 1.0 release</a>, we’re getting closer to PHPStan 2.0. After sifting through my list of ideas for the new major version, I realized I can move some of them forward and release them in 1.x series and hide them behind the <a href="/blog/what-is-bleeding-edge">Bleeding Edge config toggle</a>, so they can be enjoyed by PHPStan users sooner.</p>
<p>This isn’t true just for PHPStan 1.12 but ever since 1.0. If you enable Bleeding Edge, you basically live in the future. You get new rules and behavior changes that will be enabled for everyone in the next major version. That’s your reward as an early adopter.</p>
<p>Here’s an equation:</p>
<p class="text-center font-bold text-lg">PHPStan 2.0 = PHPStan 1.12 + Bleeding Edge + BC breaks</p>
<p>When you upgrade to PHPStan 1.12 and enable Bleeding Edge, you can get mostly ready for PHPStan 2.0 today.</p>
<p>But enough about the future. Here’s what’s new in <a href="https://github.com/phpstan/phpstan/releases/tag/1.12.0">today’s 1.12 release</a>.</p>
<h2 id="general-availability-of-precise-type-inference-for-regular-expressions" tabindex="-1">General availability of precise type inference for regular expressions <a class="header-anchor ml-1 text-gray-300 hover:text-black" href="#general-availability-of-precise-type-inference-for-regular-expressions">#</a></h2>
<p>This is a rare example of a feature that began its life in Bleeding Edge but got out of it sooner than the next major version. Because of its complexity we needed a staged rollout to weed out the bugs.</p>
<p>First <a href="https://github.com/phpstan/phpstan/releases/tag/1.11.6">introduced in 1.11.6</a>, and improved by dozens of pull requests since then, this feature is about figuring out the precise type for <code>$matches</code> by-ref argument coming from <code>preg_match()</code> and other related functions:</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">preg_match</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'/Price: (?<currency>£|€)\d+/'</span><span class="token punctuation">,</span> <span class="token variable">$s</span><span class="token punctuation">,</span> <span class="token variable">$matches</span><span class="token punctuation">,</span> <span class="token constant">PREG_UNMATCHED_AS_NULL</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">// array{0: string, currency: non-empty-string, 1: non-empty-string}</span>
<span class="token function"><span class="token punctuation">\</span>PHPStan<span class="token punctuation">\</span>dumpType</span><span class="token punctuation">(</span><span class="token variable">$matches</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre>
<p>Markus Staab and Jordi Boggiano of Composer fame <a href="https://staabm.github.io/2024/07/05/array-shapes-for-preg-match-matches.html">worked really hard on this</a>. It brings a whole new level of precision to PHPStan’s type inference.</p>
<p>From what I gathered, this ability is pretty unique and not many programming languages offer it. And now we have it in PHP!</p>
<h2 id="fix-blind-spots-in-phpdoc-tags-type-checks" tabindex="-1">Fix blind spots in PHPDoc tags type checks <a class="header-anchor ml-1 text-gray-300 hover:text-black" href="#fix-blind-spots-in-phpdoc-tags-type-checks">#</a></h2>
<p>PHPStan performs four categories of sanity checks on <a href="/writing-php-code/phpdoc-types">types in PHPDocs</a>:</p>
<ul>
<li>Missing types: performed on level 6 and up, it enforces specifying array value types, generic types, and <a href="/config-reference#vague-typehints">optionally also callable signatures</a>.</li>
<li>Existing classes: looks for nonexistent classes and also trait references</li>
<li>Unresolvable types: looks for <code>never</code> bottom type that resulted from impossible intersections like <code>string&int</code>, referencing undefined constants like <code>Foo::BAR</code>, etc.</li>
<li>Generics type checks: checks sanity of generic types. Compares the number of type variables in a type against the number of <code>@template</code> above the class declaration, checks the subtyping of bounds, etc.</li>
</ul>
<p>These type checks weren’t performed consistently for all <a href="/writing-php-code/phpdocs-basics">supported PHPDocs tags</a>. We were missing most or all of these checks for these tags:</p>
<ul>
<li><code>@param-out</code></li>
<li><code>@param-closure-this</code></li>
<li><code>@param-immediately-invoked-callable</code> and <code>@param-later-invoked-callable</code></li>
<li><code>@phpstan-self-out</code></li>
<li><code>@mixin</code></li>
<li><code>@property</code></li>
<li><code>@method</code></li>
<li><code>@extends</code>, <code>@implements</code>, <code>@use</code></li>
</ul>
<p>PHPStan 1.12 fixes that. Wrong and corrupted types are checked consistently across the board. Because these are essentially new rules which would cause new errors reported for most codebases, they were added only to Bleeding Edge, and will be enabled for everyone in PHPStan 2.0.</p>
<h2 id="too-wide-private-property-type" tabindex="-1">Too wide private property type <a class="header-anchor ml-1 text-gray-300 hover:text-black" href="#too-wide-private-property-type">#</a></h2>
<p>Another addition to Bleeding Edge checks if a type could be removed from a private property union type, without affecting anything.</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token comment">// if string is never assigned to `$this->a`, we can remove it from the type</span>
<span class="token keyword">private</span> <span class="token keyword type-declaration">int</span><span class="token operator">|</span><span class="token keyword type-declaration">string</span> <span class="token variable">$a</span><span class="token punctuation">;</span></code></pre>
<p>PHPStan already had this rule for function and method return types, and now we can enjoy it for properties too.</p>
<h2 id="php-8.4-runtime-support" tabindex="-1">PHP 8.4 runtime support <a class="header-anchor ml-1 text-gray-300 hover:text-black" href="#php-8.4-runtime-support">#</a></h2>
<p>Implementing support for new PHP version in PHPStan comes in four phases:</p>
<ul>
<li>Make sure PHPStan runs on the new PHP version and all tests pass.</li>
<li>Understand added functions and changed signatures of existing functions in the new PHP version.</li>
<li>Understand new syntax and features. This removes false errors when analysing code written against the new PHP version.</li>
<li>Implement new rules specific to new syntax and features. Discover which parts of the new PHP features are tricky and need to be checked with new static analysis rules.</li>
</ul>
<p>PHPStan 1.12 implements the first two phases. It runs on PHP 8.4 without any deprecation notices, and new functions like <a href="/r/15c8954d-d090-4c01-8c24-46ef5fd93541"><code>array_find()</code></a> are already known.</p>
<p>Because of new syntax, the rest is dependent on upgrading to <a href="https://github.com/nikic/PHP-Parser">nikic/PHP-Parser</a> v5 which has to be done in a major version.</p>
<p>My plan for the upcoming months is straightforward: finish and release PHPStan 2.0, and then work on PHP 8.4-specific features.</p>
<hr>
<p>Me and PHPStan contributors put a lot of hard work into this release. I hope that you’ll really enjoy it and take advantage of these new features! We’re going to be <a href="https://github.com/phpstan/phpstan/discussions">waiting for everyone’s feedback on GitHub</a>.</p>
<hr>
<p>Do you like PHPStan and use it every day? <a href="/sponsor"><strong>Consider sponsoring</strong> further development of PHPStan on GitHub Sponsors and also <strong>subscribe to PHPStan Pro</strong></a>! I’d really appreciate it!</p>
PHPStan Reports Different Errors Locally & in CI. What Should I Do?2024-08-20T00:00:00Zhttps://phpstan.org/blog/phpstan-reports-different-errors-locally-ci-what-should-i-do<p>If you find that PHPStan reports different code on your machine and in continuous integration build system, here are the steps to follow to get rid of this problem.</p>
<h2 id="is-it-analysing-the-same-code%3F" tabindex="-1">Is it analysing the same code? <a class="header-anchor ml-1 text-gray-300 hover:text-black" href="#is-it-analysing-the-same-code%3F">#</a></h2>
<p>Make sure you’re looking at the results of the same Git commit in CI as you have checked out locally, and that you have committed all the files in your working directory.</p>
<p>Run <code>git rev-parse HEAD</code> or look at <code>git log</code> to get the Git commit hash. CI systems usually print the current Git commit hash somewhere early in the build log.</p>
<p>Run <code>git status</code> to make sure there are no uncommitted changes you’re running the analysis against.</p>
<h2 id="is-it-running-the-same-php-version%3F" tabindex="-1">Is it running the same PHP version? <a class="header-anchor ml-1 text-gray-300 hover:text-black" href="#is-it-running-the-same-php-version%3F">#</a></h2>
<p>Make sure to check that you’re running the same PHP version locally and in CI. You can check that with <code>php -v</code>.</p>
<h2 id="require-phpstan-in-your-composer-project" tabindex="-1">Require PHPStan in your Composer project <a class="header-anchor ml-1 text-gray-300 hover:text-black" href="#require-phpstan-in-your-composer-project">#</a></h2>
<p>Do not run PHPStan installed somewhere globally in your operating system, nor locally, neither in CI. Install PHPStan in <code>composer.json</code> with your project dependencies with this command:</p>
<pre class="language-bash"><code class="language-diff-bash diff-highlight"><span class="token function">composer</span> require <span class="token parameter variable">--dev</span> phpstan/phpstan</code></pre>
<p>This way you will make sure everyone and everything is on the same PHPStan version - your machine, your teammates and most importantly, CI.</p>
<h2 id="make-sure-your-composer.lock-is-committed-in-the-git-repository" tabindex="-1">Make sure your <code>composer.lock</code> is committed in the Git repository <a class="header-anchor ml-1 text-gray-300 hover:text-black" href="#make-sure-your-composer.lock-is-committed-in-the-git-repository">#</a></h2>
<p>Commit and version your <code>composer.lock</code> file in Git to make sure your dependencies are locked and always the same until you update them.</p>
<h2 id="run-composer-install%2C-not-composer-update" tabindex="-1">Run <code>composer install</code>, not <code>composer update</code> <a class="header-anchor ml-1 text-gray-300 hover:text-black" href="#run-composer-install%2C-not-composer-update">#</a></h2>
<p>Even with <code>composer.lock</code> committed, make sure you run <code>composer install</code> to install dependencies in CI. Running <code>composer update</code> discards the contents of your <code>composer.lock</code> file and updates all dependencies. Which is something we don’t want.</p>
<h2 id="run-phpstan%E2%80%99s-diagnose-command-to-compare-the-environments" tabindex="-1">Run PHPStan’s <code>diagnose</code> command to compare the environments <a class="header-anchor ml-1 text-gray-300 hover:text-black" href="#run-phpstan%E2%80%99s-diagnose-command-to-compare-the-environments">#</a></h2>
<p>Since <a href="https://github.com/phpstan/phpstan/releases/tag/1.11.8">release 1.11.8</a> PHPStan ships with a <code>diagnose</code> command. It prints useful information about its configuration and more. Do not forget to pass the correct <a href="/config-reference">configuration file</a> and <a href="/user-guide/rule-levels">rule level</a> when running the command.</p>
<p>The output looks like this:</p>
<pre class="font-mono text-sm">
<span class="text-green-500">PHP runtime version:</span> 8.3.1
<span class="text-green-500">PHP version for analysis:</span> 8.3.99 (from config.platform.php in composer.json)
<span class="text-green-500">PHPStan version:</span> 1.11.11
<span class="text-green-500">PHPStan running from:</span>
/var/www/project/vendor/phpstan/phpstan
<span class="text-green-500">Extension installer:</span>
composer/pcre: 3.2.0
phpstan/phpstan-deprecation-rules: 1.2.0
phpstan/phpstan-doctrine: 1.4.8
phpstan/phpstan-nette: 1.3.5
phpstan/phpstan-phpunit: 1.4.0
phpstan/phpstan-strict-rules: 1.6.0
phpstan/phpstan-symfony: 1.4.6
phpstan/phpstan-webmozart-assert: 1.2.7
shipmonk/phpstan-rules: 3.1.0
<span class="text-green-500">Discovered Composer project root:</span>
/var/www/project
<span class="text-green-500">Doctrine's objectManagerLoader:</span> In use
Installed Doctrine packages:
doctrine/dbal: 3.8.6
doctrine/orm: 2.19.6
doctrine/common: 3.4.4
doctrine/collections: 2.2.2
doctrine/persistence: 3.3.3
<span class="text-green-500">Symfony's consoleApplicationLoader:</span> No
</pre>
<p>Compare the output between your local machine and CI to find possible differences.</p>
<h2 id="check-the-config-file-path" tabindex="-1">Check the config file path <a class="header-anchor ml-1 text-gray-300 hover:text-black" href="#check-the-config-file-path">#</a></h2>
<p>Make sure you’re running PHPStan <code>analyse</code> command with the same <a href="/config-reference">configuration file</a>. If you’re passing a custom path with <code>-c</code>, make sure it’s the same one as locally.</p>
<p>If you’re letting PHPStan autodiscover <code>phpstan.neon</code> or <code>phpstan.neon.dist</code> file in the current working directory, it outputs this note at the beginning of the analysis:</p>
<pre class="font-mono text-sm">
Note: Using configuration file /var/www/project/phpstan.neon.
</pre>
<p>Make sure your local machine uses the same file as the CI environment.</p>
<h2 id="get-rid-of-duplicate-symbols" tabindex="-1">Get rid of duplicate symbols <a class="header-anchor ml-1 text-gray-300 hover:text-black" href="#get-rid-of-duplicate-symbols">#</a></h2>
<p>If you have two or more different class or function definitions with the exact same name, PHPStan does not guarantee which one it’s going to give priority to. Your local machine <a href="/user-guide/discovering-symbols">might discover</a> one definition first and your CI might discover a different definition first, leading to different analysis results.</p>
<p>Rename your classes and functions so that they are always unique to get rid of this problem.</p>
<h2 id="fix-your-custom-non-deterministic-autoloader" tabindex="-1">Fix your custom non-deterministic autoloader <a class="header-anchor ml-1 text-gray-300 hover:text-black" href="#fix-your-custom-non-deterministic-autoloader">#</a></h2>
<p>Sometimes the difference in results comes down to the order of files during the analysis. Because the CI environment might have a different number of CPU cores, the files are going to be analysed in different groups and in different order than on your local machine.</p>
<p>If the difference is about what classes are known and unknown to PHPStan, it’s possible you’re using a <a href="/user-guide/discovering-symbols#custom-autoloader">custom autoloader</a> to discover them.</p>
<p>A following scenario can occur:</p>
<ul>
<li>You’re referencing a class name in your code with wrong case. For example. Your class name is <code>AdminUser</code> but you have <code>adminUser</code> somewhere in your code when referencing the same class.</li>
<li>On your local machine the first file that’s analysed refers to the class with the correct name - <code>AdminUser</code>. That makes other occurrences even with the wrong case let the class to be successfully found by PHPStan.</li>
<li>In CI the first file that’s analysed refers to the class with an incorrect name - <code>adminUser</code>. Your custom autoloader should still find the class successfully but maybe has a bug. This makes PHPStan report “unknown class” error and it also makes it memoize that this class does not exist.</li>
<li>When the second file that’s analysed in CI refers correctly to <code>AdminUser</code>, it still makes PHPStan report an error about unknown class which is a different behaviour than you’re experiencing locally.</li>
</ul>
<p>To fix this problem, make your autoloader discover classes even with wrong case.</p>
<h2 id="open-a-discussion-on-phpstan%E2%80%99s-github" tabindex="-1">Open a discussion on PHPStan’s GitHub <a class="header-anchor ml-1 text-gray-300 hover:text-black" href="#open-a-discussion-on-phpstan%E2%80%99s-github">#</a></h2>
<p>If none of the steps above helped you to get consistent results between running PHPStan locally and in CI, please <a href="https://github.com/phpstan/phpstan/discussions/new?category=support">open a discussion on GitHub,</a> so we can help you figure out this problem.</p>
<hr>
<p>Do you like PHPStan and use it every day? <a href="/sponsor"><strong>Consider sponsoring</strong> further development of PHPStan on GitHub Sponsors and also <strong>subscribe to PHPStan Pro</strong></a>! I’d really appreciate it!</p>
Debugging PHPStan Performance: Identify Slow Files2024-07-05T00:00:00Zhttps://phpstan.org/blog/debugging-performance-identify-slow-files<p>If you feel like PHPStan could be faster when analysing your project, you can debug what’s actually making it slow. Usually the cause of slow analysis can be pinpointed to a single file or a handful of files in a project that make PHPStan’s analysis crawl to a halt.</p>
<p>Before you start debugging, try enabling <a href="/blog/what-is-bleeding-edge">Bleeding Edge</a>. It makes analysis in huge codebases run much faster. It comes with a <a href="/blog/phpstan-1-6-0-with-conditional-return-types#lower-memory-consumption">slight backward compatibility break</a> with the advantage of breaking bidirectional memory references, making the job of garbage collector easier.</p>
<p>If that didn’t help, run PHPStan again but add <code>-vvv --debug</code> <a href="/user-guide/command-line-usage">command line options</a>. PHPStan will run in a single thread instead of in multiple processes at once, and it will print the elapsed time and consumed memory during the analysis of each file:</p>
<pre><code>/var/www/src/Database/Eloquent/Relations/MorphToMany.php
--- consumed 6 MB, total 200 MB, took 0.11 s
/var/www/src/Database/Eloquent/Relations/HasOneThrough.php
--- consumed 0 B, total 200 MB, took 0.06 s
/var/www/src/Database/Eloquent/Relations/MorphPivot.php
--- consumed 0 B, total 200 MB, took 0.06 s
/var/www/src/Database/Eloquent/Relations/Pivot.php
--- consumed 2 MB, total 202 MB, took 0.09 s
/var/www/src/Database/Eloquent/Relations/HasOneOrMany.php
--- consumed 0 B, total 202 MB, took 0.15 s
/var/www/src/Database/Eloquent/Relations/BelongsTo.php
--- consumed 0 B, total 202 MB, took 0.15 s
</code></pre>
<p>You can either watch this log go by in realtime with your own eyes and spot files that took too much time or memory. Or you can redirect the output to a file:</p>
<pre><code>vendor/bin/phpstan -vvv --debug > phpstan.log
</code></pre>
<p>And parse this file with a <a href="https://gist.github.com/ruudk/41897eb59ff497b271fc9fa3c7d5fb27">simple script</a> by <a href="https://github.com/ruudk">Ruud Kamphuis</a> that sorts the file list and puts the files that took the most time on top:</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token php language-php"><span class="token delimiter important"><?php</span> <span class="token keyword">declare</span><span class="token punctuation">(</span>strict_types<span class="token operator">=</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token variable">$log</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">SplFileObject</span><span class="token punctuation">(</span><span class="token string double-quoted-string">"phpstan.log"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token variable">$logs</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token variable">$file</span> <span class="token operator">=</span> <span class="token constant">null</span><span class="token punctuation">;</span>
<span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token variable">$log</span><span class="token operator">-></span><span class="token function">eof</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token variable">$line</span> <span class="token operator">=</span> <span class="token function">trim</span><span class="token punctuation">(</span><span class="token variable">$log</span><span class="token operator">-></span><span class="token function">fgets</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token variable">$line</span> <span class="token operator">===</span> <span class="token string single-quoted-string">''</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">continue</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token variable">$file</span> <span class="token operator">===</span> <span class="token constant">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token variable">$file</span> <span class="token operator">=</span> <span class="token variable">$line</span><span class="token punctuation">;</span>
<span class="token keyword">continue</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">preg_match</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'/took (?<seconds>[\d.]+) s/'</span><span class="token punctuation">,</span> <span class="token variable">$line</span><span class="token punctuation">,</span> <span class="token variable">$matches</span><span class="token punctuation">)</span> <span class="token operator">!==</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">continue</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token variable">$logs</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">(</span><span class="token keyword type-casting">float</span><span class="token punctuation">)</span> <span class="token variable">$matches</span><span class="token punctuation">[</span><span class="token string single-quoted-string">'seconds'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token variable">$file</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token variable">$file</span> <span class="token operator">=</span> <span class="token constant">null</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token function">usort</span><span class="token punctuation">(</span><span class="token variable">$logs</span><span class="token punctuation">,</span> <span class="token keyword">fn</span><span class="token punctuation">(</span><span class="token keyword type-hint">array</span> <span class="token variable">$left</span><span class="token punctuation">,</span> <span class="token keyword type-hint">array</span> <span class="token variable">$right</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token variable">$right</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator"><=></span> <span class="token variable">$left</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token variable">$logs</span> <span class="token operator">=</span> <span class="token function">array_slice</span><span class="token punctuation">(</span><span class="token variable">$logs</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">20</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">echo</span> <span class="token string double-quoted-string">"Slowest files"</span> <span class="token operator">.</span> <span class="token constant">PHP_EOL</span><span class="token punctuation">;</span>
<span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token variable">$logs</span> <span class="token keyword">as</span> <span class="token variable">$log</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">echo</span> <span class="token function">sprintf</span><span class="token punctuation">(</span><span class="token string double-quoted-string">"%.2f seconds: %s"</span><span class="token punctuation">,</span> <span class="token variable">$log</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token variable">$log</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">.</span> <span class="token constant">PHP_EOL</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</span></code></pre>
<p>Save this file into <code>parse.php</code> in the same directory where <code>phpstan.log</code> is and run it:</p>
<pre class="language-bash"><code class="language-diff-bash diff-highlight">php parse.php</code></pre>
<p>It will output 20 files that took the most time:</p>
<pre><code>Slowest files
4.46 seconds: /var/www/src/Collections/LazyCollection.php
2.71 seconds: /var/www/src/Support/Str.php
2.56 seconds: /var/www/src/Console/Command.php
2.45 seconds: /var/www/src/Validation/Validator.php
2.44 seconds: /var/www/src/Database/Query/Builder.php
2.41 seconds: /var/www/src/Collections/Collection.php
2.12 seconds: /var/www/src/Database/Eloquent/Builder.php
2.10 seconds: /var/www/src/Foundation/Testing/TestCase.php
2.01 seconds: /var/www/src/Database/Eloquent/Model.php
...
</code></pre>
<p>What to do with this list? It’s up to you to decide. Maybe the slowest file is really large and maybe even auto-generated. Analysing it with PHPStan doesn’t probably bring too much value, so you can <a href="/user-guide/ignoring-errors#excluding-whole-files">exclude it from the analysis</a> and make PHPStan immediately faster.</p>
<p>But if the slowest file isn’t that large, you’ve just found a real bottleneck which is an opportunity to make PHPStan faster. Take this file and <a href="https://github.com/phpstan/phpstan/issues/new?template=Bug_report.yaml">open a new bug report</a> in PHPStan’s GitHub repository so we can take a look and use it to make PHPStan faster.</p>
<p>Happy debugging!</p>
<hr>
<p>Do you like PHPStan and use it every day? <a href="/sponsor"><strong>Consider sponsoring</strong> further development of PHPStan on GitHub Sponsors and also <strong>subscribe to PHPStan Pro</strong></a>! I’d really appreciate it!</p>
PHPStan 1.11 With Error Identifiers, PHPStan Pro Reboot and Much More2024-05-13T00:00:00Zhttps://phpstan.org/blog/phpstan-1-11-errors-identifiers-phpstan-pro-reboot<p>This release has been in the works for a year. But it doesn’t mean there hasn’t been anything else released for a year. After starting the work on PHPStan 1.11 I continued working on 1.10.x series in parallel, bringing 54 releases into the world between April 2023 and 2024. But at some point earlier this year I said “enough!” and couldn’t resist finally bringing the <a href="https://github.com/phpstan/phpstan/releases/tag/1.11.0">awesome improvements in 1.11</a> over the finish line and into the world.</p>
<h2 id="error-identifiers" tabindex="-1">Error Identifiers <a class="header-anchor ml-1 text-gray-300 hover:text-black" href="#error-identifiers">#</a></h2>
<p>When I decided to add error identifiers I knew it was going to be a lot of work. They’re a way of labeling and categorizing errors reported by PHPStan useful for ignoring errors of a certain category among other things.</p>
<p>I had to go through all PHPStan rules and decide how the identifier should look like for all of them. I’ve settled on two-part format <code>category.subtype</code> pretty early on as being easy to read and memorize. Other tools like TypeScript have settled on simple numbers like <code>TS2322</code>. In my opinion that’s as human-friendly as IP addresses. There are reasons why we need DNS to translate that to actual names.</p>
<p>Another easy way out would be to mark the rule’s implementation class name as its identifier. But I’ve never considered these to be user-facing and stable. They’re implementation details. To give them public meaning would really tie our hands when evolving PHPStan. Because some rule classes report very different issues resulting in multiple identifiers, and at the same time, different rules report very similar issues in PHP code resulting in a single identifier.</p>
<p>So a more flexible approach was needed. You can see for yourself how it turned out. There’s a catalogue on PHPStan’s website with all the identifiers. You can <a href="/error-identifiers#gbi-">group them by identifier</a> (and see all the rules where the same identifier is reported), or <a href="/error-identifiers#gbr-">group them by the rule class</a> (and see all the identifiers reported by the same class). This page is generated by analysing PHPStan sources with PHPStan to be used on PHPStan’s website. There’s probably an Inception or <a href="https://knowyourmeme.com/memes/xzibit-yo-dawg">Yo Dawg</a> joke somewhere in there.</p>
<p>So let’s say we have an error and we want to find out its identifier in order to ignore it. If we’re using the default <code>table</code> output formatter, we can run PHPStan with <code>-v</code> and see the identifiers right in the output:</p>
<p class="border border-gray-200 rounded-lg p-2"><img src="/images/identifiers-table-v.png" alt="Table error formatter running with -v showing error identifiers"></p>
<p>Or we can run <a href="/blog/introducing-phpstan-pro" class="phpstan-pro-label">PHPStan Pro</a> by launching PHPStan with <code>--pro</code> CLI option and see the identifier in the UI next to the reported error:</p>
<p class="border-2 border-purple-600 rounded-lg overflow-hidden"><img src="/images/identifiers-pro-hover.png" alt="PHPStan Pro showing error identifiers"></p>
<p>Once we know the identifier we can use it in the new <code>@phpstan-ignore</code> comment annotation that replaces the current pair of <code>@phpstan-ignore-line</code> and <code>@phpstan-ignore-next-line</code> annotations which ignore all errors on a certain line. With <code>@phpstan-ignore</code> PHPStan figures out if there’s some code besides the comment on the current line (and ignores an error in that code), or if we want to ignore an error in the code on the next line:</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token comment">// Both variants work:</span>
<span class="token comment">// @phpstan-ignore echo.nonString</span>
<span class="token keyword">echo</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token keyword">echo</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token comment">// @phpstan-ignore echo.nonString</span></code></pre>
<p>In <code>@phpstan-ignore</code> specifying an identifier is always required.</p>
<p>If we want to ignore multiple errors on the same line, we can place multiple identifiers separated by a comma in the <code>@phpstan-ignore</code> annotation.</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token keyword">echo</span> <span class="token variable">$foo</span><span class="token punctuation">,</span> <span class="token variable">$bar</span><span class="token punctuation">;</span> <span class="token comment">// @phpstan-ignore variable.undefined, variable.undefined</span></code></pre>
<p>Yes, we need to repeat the same identifier twice if we want to ignore two errors of the same type on the same line. Better safe than sorry!</p>
<p>We might also want to explain why an error is ignored. The description has to be put in parentheses after the identifier:</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token comment">// @phpstan-ignore offsetAccess.notFound (exists, set by a reference)</span>
<span class="token function">data_set</span><span class="token punctuation">(</span><span class="token variable">$target</span><span class="token punctuation">[</span><span class="token variable">$segment</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token variable">$segments</span><span class="token punctuation">,</span> <span class="token variable">$value</span><span class="token punctuation">,</span> <span class="token variable">$overwrite</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>We can also use identifiers in our configuration’s <code>ignoreErrors</code> section. This ignores all errors that match both the message and the identifier:</p>
<pre class="language-yaml"><code class="language-diff-yaml diff-highlight"><span class="token key atrule">parameters</span><span class="token punctuation">:</span>
<span class="token key atrule">ignoreErrors</span><span class="token punctuation">:</span>
<span class="token punctuation">-</span>
<span class="token key atrule">message</span><span class="token punctuation">:</span> <span class="token string">'#Access to an undefined property Foo::\$[a-zA-Z0-9\\_]#'</span>
<span class="token key atrule">identifier</span><span class="token punctuation">:</span> property.notFound</code></pre>
<p>Or we can ignore all errors of a specific identifier:</p>
<pre class="language-yaml"><code class="language-diff-yaml diff-highlight"><span class="token key atrule">parameters</span><span class="token punctuation">:</span>
<span class="token key atrule">ignoreErrors</span><span class="token punctuation">:</span>
<span class="token punctuation">-</span>
<span class="token key atrule">identifier</span><span class="token punctuation">:</span> property.notFound</code></pre>
<p>Across PHPStan core and 1<sup>st</sup> extensions there’s a total of 728 identifiers in 364 rule classes. <sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup> I think I managed to strike a good middle ground with the granularity between being too coarse and being too fine.</p>
<h2 id="phpstan-pro-wizard" tabindex="-1">PHPStan Pro Wizard <a class="header-anchor ml-1 text-gray-300 hover:text-black" href="#phpstan-pro-wizard">#</a></h2>
<p>I <a href="/blog/introducing-phpstan-pro">introduced</a> PHPStan Pro in September 2020 as a paid addon for PHPStan that doesn’t remove anything from the beloved open-source version, but adds extra features that make PHPStan easier to use. Since that PHPStan Pro grew steadily and contributes about one third to my income from PHPStan. That made my open-source work sustainable and I’m really grateful for that.</p>
<p>I’ve had many ideas how to make PHPStan Pro more useful and interesting but I mostly sat on them for the first three years since the initial release, choosing to work on open-source PHPStan instead.</p>
<p>Error identifiers brought the perfect opportunity to turn to PHPStan Pro again. Right now your codebase is probably full of <code>@phpstan-ignore-line</code> and <code>@phpstan-ignore-next-line</code> comments. They ignore all errors on specific lines. All future errors introduced from the point you type out these comments are silently ignored without you ever seeing them. Which is dangerous.</p>
<p>What if you could snap your fingers and all of these comments magically turned into the new <code>@phpstan-ignore</code> with the right identifiers filled out?</p>
<pre class="language-diff-php"><code class="language-diff-diff-php diff-highlight"><span class="token deleted-sign deleted language-php"><span class="token prefix deleted">-</span> <span class="token comment">// @phpstan-ignore-next-line</span>
</span><span class="token inserted-sign inserted language-php"><span class="token prefix inserted">+</span> <span class="token comment">// @phpstan-ignore argument.type</span>
</span><span class="token unchanged language-php"><span class="token prefix unchanged"> </span> <span class="token variable">$this</span><span class="token operator">-></span><span class="token function">requireInt</span><span class="token punctuation">(</span><span class="token variable">$string</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span></code></pre>
<p>PHPStan Pro introduces a <strong>migration wizard</strong> that’s going to do this for you! Let’s run PHPStan with <code>--pro</code> CLI option and see it in action:</p>
<video class="w-full aspect-[1680/1080] mb-8 border border-gray-200 rounded-lg overflow-hidden" autoplay muted loop playsinline poster="/images/ignore-line-wizard-poster.jpg">
<source src="/images/ignore-line-wizard.mp4" type="video/mp4">
</video>
<p>With just a few clicks your codebase can be modernized and made safer thanks to this wizard.</p>
<p>PHPStan Pro now ships with this single wizard, but I have a lot more ideas how wizards could be used to update various aspects of your codebases related to static analysis like typehints and PHPDocs and leave them in a better state. My plan is to do regular “wizard drops” (like feature drops). As the foundation has been laid out, it shouldn’t take long for more awesome wizards to appear!</p>
<h2 id="phpstan-pro-ui-revamp" tabindex="-1">PHPStan Pro UI Revamp <a class="header-anchor ml-1 text-gray-300 hover:text-black" href="#phpstan-pro-ui-revamp">#</a></h2>
<p>But wizards are not the only thing that has changed about PHPStan Pro. Since the beginning PHPStan Pro has been about browsing errors in a web UI instead of a terminal.</p>
<p>You’re probably familiar with this saying:</p>
<blockquote>
<p>If you’re not embarrassed by the first version of your product, you’ve launched too late.</p>
</blockquote>
<p>Well I’m definitely not guilty of that 🤣 PHPStan Pro up until today did not have a good UI. Each error was rendered in a separate box so if you had multiple errors around neighbouring lines, you could quickly lose context. They weren’t presented in a clear and understandable way. Additionally, you could lose the focus on currently showing file if you reloaded the webpage. The sidebar panel wasn’t sticky so it shifted away if you scrolled a long file. It was not possible to remap Docker path to the host’s filesystem.</p>
<p>I could continue down this unending embarrassing list of shortcomings of the previous version, but let me put an end to that.</p>
<p>The new version you’ll get once you update to PHPStan 1.11 and launch Pro with <code>--pro</code> is much better. Let’s have a look:</p>
<video class="w-full aspect-[1652/1080] mb-8 border border-gray-200 rounded-lg overflow-hidden" autoplay muted loop playsinline poster="/images/phpstan-pro-browsing-poster.jpg">
<source src="/images/phpstan-pro-browsing.mp4" type="video/mp4">
</video>
<p>The layout is more natural and resembles an IDE. Each file is rendered just once and errors are attached to lines where they are reported. You can expand hidden lines to see more context around an error. Docker paths can be remapped so that editor links lead to the correct file.</p>
<p>PHPStan lets you see ignored errors too. If you’ve inherited a project and want to see what errors the previous team ignored, or if you want to check the errors lurking in your huge baseline, PHPStan Pro lets you do that with ease. By default it will show a small pill button you can use to see ignored errors near normally reported errors, but you can also change the setting to show and browse all ignored errors:</p>
<video class="w-full aspect-[1656/1080] mb-8 border border-gray-200 rounded-lg overflow-hidden" autoplay muted loop playsinline poster="/images/phpstan-pro-ignored-errors-poster.jpg">
<source src="/images/phpstan-pro-ignored-errors.mp4" type="video/mp4">
</video>
<p>PHPStan Pro analyzes your codebase in the background and refreshes the UI with the latest errors automatically. If you don’t want to keep your hands warm while your laptop struggles to keep up with too-frequent analysis, you can choose to run it only when the PHPStan Pro window is in focus, or pause it altogether:</p>
<video class="w-1/2 mx-auto aspect-[3624/1080] mb-8 border border-gray-200 rounded-lg overflow-hidden" autoplay muted loop playsinline>
<source src="/images/play-window-pause.mp4" type="video/mp4">
</video>
<p>PHPStan Pro costs <strong>€7/month</strong> for individuals and <strong>€70/month</strong> for teams. If you decide to pay annually, you’ll get PHPStan Pro for 12 months for the price of 10 months – that’s <strong>€70/year</strong> for individual developers, <strong>€700/year</strong> for teams.</p>
<p>There’s a 30-day free trial period for all the plans. And there’s no limit on the number of projects - you can run PHPStan Pro on any code you have on your computer.</p>
<p>Start by running PHPStan with <code>--pro</code> or by going to <a href="https://account.phpstan.com/">account.phpstan.com</a> and creating an account.</p>
<h2 id="is-this-function-really-pure%3F" tabindex="-1">Is this function really pure? <a class="header-anchor ml-1 text-gray-300 hover:text-black" href="#is-this-function-really-pure%3F">#</a></h2>
<p>We’re not even close to the end of the list of what’s new in PHPStan 1.11.</p>
<p>The <code>@phpstan-pure</code> annotation has been supported for a long time to mark functions that don’t have any side effects. That’s useful for <a href="/blog/remembering-and-forgetting-returned-values">remembering and forgetting returned values</a> among other things.</p>
<p>But PHPStan didn’t enforce the truthiness of this annotation. It always obeyed it. This release changes that. I went through the trouble of deciding what language construct is pure or not for all statement and expression types. PHPStan now understands for example that <code>$a + $b</code> is always pure but calling <code>sleep(5)</code> or assigning a property outside of constructor is impure.</p>
<p>Once we have that information we can use it for multiple opportunities as marking some pieces code as wrong. Pure expressions always have to be used so it doesn’t make sense to have these on a separate line without using the results:</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token variable">$a</span> <span class="token operator">+</span> <span class="token variable">$b</span><span class="token punctuation">;</span>
<span class="token keyword">new</span> <span class="token class-name">ClassWithNoConstructor</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token variable">$cb</span> <span class="token operator">=</span> <span class="token keyword">static</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token number">1</span> <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token variable">$cb</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// does not do anything</span></code></pre>
<p>Besides reporting functions annotated with <code>@phpstan-pure</code> but having side effects as wrong, we can now also afford marking functions with <code>@phpstan-impure</code> without any side effects also as wrong. And finally, <code>void</code>-returning functions (which are understood as impure implicitly) that do not have any side effects are also reported as wrong. What would be a point of calling a function that doesn’t have a side effect and doesn’t return any value?</p>
<p>Clamping this problem space from all sides allowed us to weed out some bugs by testing development versions on real-world projects.</p>
<p>For all of this to be applicable to real-world situations, we also added new <code>pure-callable</code> and <code>pure-Closure</code> PHPDoc types that can be called even in pure functions.</p>
<p>Make sure to enable <a href="/blog/what-is-bleeding-edge">bleeding edge</a> to take advantage of these new rules.</p>
<h2 id="when-is-a-passed-callable-called%3F" tabindex="-1">When is a passed callable called? <a class="header-anchor ml-1 text-gray-300 hover:text-black" href="#when-is-a-passed-callable-called%3F">#</a></h2>
<p>Callables are severely underdocumented in PHP projects. You could already type and enforce the signature of a callable to make it more useful for static analysis:</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token comment">/**
* @param callable(int, int): string $cb
*/</span></code></pre>
<p>But other aspects of callables remained mysterious for PHPStan. What happens to a callback when it’s passed to another function? Is it called immediately or later? This information can become handy for purity checks described in previous section, and also for <a href="/blog/bring-your-exceptions-under-control">bringing exceptions under control</a>.</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token variable">$this</span><span class="token operator">-></span><span class="token function">doFoo</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">rand</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">MyException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>It would be useful for PHPStan to know if this call to method <code>doFoo</code> should be surrounded with a try-catch or if the thrown <code>MyException</code> should be documented in <code>@throws</code> PHPDoc tag. But it needs to know if this callback is called immediately, or saved for later invocation.</p>
<p>PHPStan 1.11 introduces a new pair of PHPDoc tags for this purpose:</p>
<ul>
<li><code>@param-immediately-invoked-callable</code></li>
<li><code>@param-later-invoked-callable</code></li>
</ul>
<p>The default reasonable convention if these tags are not present is that callables passed to functions are invoked immediately, and callables passed to class methods are invoked later.</p>
<p>You need these PHPDoc tags only to override the defaults, so you’ll use <code>@param-immediately-invoked-callable</code> above methods and <code>@param-later-invoked-callable</code> above functions.</p>
<h2 id="what-is-a-passed-closure-bound-to%3F" tabindex="-1">What is a passed closure bound to? <a class="header-anchor ml-1 text-gray-300 hover:text-black" href="#what-is-a-passed-closure-bound-to%3F">#</a></h2>
<p>PHP’s methods <a href="https://www.php.net/manual/en/closure.bind.php"><code>Closure::bind</code></a> and <a href="https://www.php.net/manual/en/closure.bindto.php"><code>Closure::bindTo</code></a> are used to change what <code>$this</code> refers to in a non-static closure. Doing that can lead to confusing PHPStan:</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token comment">// we're in class Foo</span>
<span class="token comment">// $this is Foo here</span>
<span class="token variable">$this</span><span class="token operator">-></span><span class="token function">doSomething</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">// PHPStan still thinks $this is Foo here</span>
<span class="token comment">// but it could be something different</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>If your function binds passed closure to some other object, you can now use new PHPDoc tag <code>@param-closure-this</code> to inform PHPStan about it:</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token comment">/**
* @param-closure-this \stdClass $cb
*/</span>
<span class="token keyword">function</span> <span class="token function-definition function">doFoo</span><span class="token punctuation">(</span><span class="token keyword type-hint">callable</span> <span class="token variable">$cb</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">void</span>
<span class="token punctuation">{</span>
<span class="token variable">$cb</span><span class="token operator">-></span><span class="token function">bindTo</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name class-name-fully-qualified"><span class="token punctuation">\</span>stdClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// ...</span>
<span class="token punctuation">}</span></code></pre>
<p>It fully works with <a href="/blog/generics-in-php-using-phpdocs">generics</a> and <a href="/writing-php-code/phpdoc-types#conditional-return-types">conditional types</a> so you can really go crazy in there!</p>
<h2 id="new-options-for-possibly-nonexistent-offsets-in-arrays" tabindex="-1">New options for possibly nonexistent offsets in arrays <a class="header-anchor ml-1 text-gray-300 hover:text-black" href="#new-options-for-possibly-nonexistent-offsets-in-arrays">#</a></h2>
<p>PHPStan by default tries not to complain too much about code that might be <em>totally fine</em>. One example is accessing offsets of arrays without checking that these offsets actually exist first.</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token comment">/**
* @param array<string, Item> $items
*/</span>
<span class="token keyword">function</span> <span class="token function-definition function">doFoo</span><span class="token punctuation">(</span><span class="token keyword type-hint">array</span> <span class="token variable">$items</span><span class="token punctuation">,</span> <span class="token keyword type-hint">string</span> <span class="token variable">$key</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">void</span>
<span class="token punctuation">{</span>
<span class="token comment">// this might exist but might not</span>
<span class="token variable">$selectedItem</span> <span class="token operator">=</span> <span class="token variable">$items</span><span class="token punctuation">[</span><span class="token variable">$key</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre>
<p><a href="https://github.com/phpstan/phpstan-src/pull/3028">Tom De Wit kindly contributed</a> a pair of new configuration options to have potential these issues reported.</p>
<p>To have the error reported in the example above, turn on <a href="/config-reference#reportpossiblynonexistentgeneralarrayoffset"><code>reportPossiblyNonexistentGeneralArrayOffset</code></a>.</p>
<p>To have the same error reported for constant arrays, also known as <a href="/writing-php-code/phpdoc-types#array-shapes">array shapes</a>, turn on <a href="/config-reference#reportpossiblynonexistentconstantarrayoffset"><code>reportPossiblyNonexistentConstantArrayOffset</code></a>.</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function-definition function">doFoo</span><span class="token punctuation">(</span><span class="token keyword type-hint">string</span> <span class="token variable">$s</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">void</span>
<span class="token punctuation">{</span>
<span class="token variable">$a</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string single-quoted-string">'foo'</span> <span class="token operator">=></span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token keyword">echo</span> <span class="token variable">$a</span><span class="token punctuation">[</span><span class="token variable">$s</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre>
<p>My personal preference is to only turn on the latter option, but your experience may vary.</p>
<hr>
<p>Me and PHPStan contributors put a lot of hard work into this release. I hope that you’ll really enjoy it and take advantage of these new features! We’re going to be <a href="https://github.com/phpstan/phpstan/discussions">waiting for everyone’s feedback on GitHub</a>.</p>
<hr>
<p>Do you like PHPStan and use it every day? <a href="/sponsor"><strong>Consider sponsoring</strong> further development of PHPStan on GitHub Sponsors and also <strong>subscribe to PHPStan Pro</strong></a>! I’d really appreciate it!</p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>The fact there are exactly two identifiers per rule class on average is a total coincidence. It made me re-check my algorithm that counts them. <a href="#fnref1" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>
Enhancements in Handling Parameters Passed by Reference in PHPStan 1.10.602024-03-07T00:00:00Zhttps://phpstan.org/blog/enhancements-in-handling-parameters-passed-by-reference<p><a href="https://github.com/phpstan/phpstan/releases/tag/1.10.60">PHPStan 1.10.60</a> introduces enhancements in handling parameters passed by reference. These parameters, besides typical returned values, also serve as a form of output that a called function can produce:</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token keyword">function</span> <span class="token function-definition function">foo</span><span class="token punctuation">(</span><span class="token operator">&</span><span class="token variable">$p</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">void</span>
<span class="token punctuation">{</span>
<span class="token variable">$p</span> <span class="token operator">=</span> <span class="token constant boolean">true</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token variable">$v</span> <span class="token operator">=</span> <span class="token constant boolean">false</span><span class="token punctuation">;</span>
<span class="token function">foo</span><span class="token punctuation">(</span><span class="token variable">$v</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">var_dump</span><span class="token punctuation">(</span><span class="token variable">$v</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// bool(true)</span></code></pre>
<p>You might be surprised to learn that you can set a type for a parameter passed by reference, but it doesn’t limit the output type:</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token keyword">function</span> <span class="token function-definition function">foo</span><span class="token punctuation">(</span><span class="token keyword type-declaration">string</span> <span class="token operator">&</span><span class="token variable">$p</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">void</span>
<span class="token punctuation">{</span>
<span class="token variable">$p</span> <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token variable">$v</span> <span class="token operator">=</span> <span class="token string single-quoted-string">'foo'</span><span class="token punctuation">;</span>
<span class="token function">foo</span><span class="token punctuation">(</span><span class="token variable">$v</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">var_dump</span><span class="token punctuation">(</span><span class="token variable">$v</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// int(1)</span></code></pre>
<p>Previously, PHPStan’s type inference discarded the type of a variable passed into such a parameter:</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token keyword">function</span> <span class="token function-definition function">foo</span><span class="token punctuation">(</span><span class="token keyword type-declaration">string</span> <span class="token operator">&</span><span class="token variable">$p</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">void</span>
<span class="token punctuation">{</span>
<span class="token variable">$p</span> <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token variable">$v</span> <span class="token operator">=</span> <span class="token string single-quoted-string">'foo'</span><span class="token punctuation">;</span>
<span class="token function">foo</span><span class="token punctuation">(</span><span class="token variable">$v</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function"><span class="token punctuation">\</span>PHPStan<span class="token punctuation">\</span>dumpType</span><span class="token punctuation">(</span><span class="token variable">$v</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// mixed</span></code></pre>
<p>When analysing code that calls a function, PHPStan relies on function’s signature and PHPDoc to understand what’s going to happen. It does not dive into the function implementation because it’d hurt the performance of the analyser. So changing the type of the variable to <code>mixed</code> was a sensible solution - we didn’t know what’s going on inside the function so we could not assume anything.</p>
<p>In the latest release, this behavior has changed.</p>
<p>All of the following changes are part of <a href="/blog/what-is-bleeding-edge">Bleeding Edge</a> because we intend to keep our generous <a href="/user-guide/backward-compatibility-promise">backward compatibility promise</a>. You can get them in PHPStan 1.10.60 today if you include the Bleeding Edge config. Everybody else will get them as part of the next major release, PHPStan 2.0.</p>
<h2 id="checking-the-type-of-argument-passed-into-a-parameter-by-reference" tabindex="-1">Checking the Type of Argument Passed into a Parameter by Reference <a class="header-anchor ml-1 text-gray-300 hover:text-black" href="#checking-the-type-of-argument-passed-into-a-parameter-by-reference">#</a></h2>
<p>The recent <a href="https://github.com/phpstan/phpstan-src/pull/2941">pull request</a> by Lincoln Maskey initiated the development of these enhancements.</p>
<p>Previously, PHPStan skipped type checking for all parameters passed by reference. Consequently, code like the following did not trigger any warnings:</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token keyword">function</span> <span class="token function-definition function">foo</span><span class="token punctuation">(</span><span class="token keyword type-declaration">string</span> <span class="token operator">&</span><span class="token variable">$p</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">void</span>
<span class="token punctuation">{</span>
<span class="token comment">// ...</span>
<span class="token punctuation">}</span>
<span class="token variable">$v</span> <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>
<span class="token function">foo</span><span class="token punctuation">(</span><span class="token variable">$v</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>This was because some internal built-in PHP functions have parameters passed by reference in their signature, but the type enforcement didn’t match that of userland functions.</p>
<p>However, PHPStan is now equipped to check the type in such cases, ensuring more comprehensive type validation moving forward.</p>
<h2 id="assumption-of-output-type" tabindex="-1">Assumption of Output Type <a class="header-anchor ml-1 text-gray-300 hover:text-black" href="#assumption-of-output-type">#</a></h2>
<p>Instead of setting the type of the passed variable to <code>mixed</code>, we now assume that the user does not intend to change the type entirely:</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token keyword">function</span> <span class="token function-definition function">foo</span><span class="token punctuation">(</span><span class="token keyword type-declaration">string</span> <span class="token operator">&</span><span class="token variable">$p</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">void</span>
<span class="token punctuation">{</span>
<span class="token comment">// ...</span>
<span class="token punctuation">}</span>
<span class="token variable">$v</span> <span class="token operator">=</span> <span class="token string single-quoted-string">'foo'</span><span class="token punctuation">;</span>
<span class="token function">foo</span><span class="token punctuation">(</span><span class="token variable">$v</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function"><span class="token punctuation">\</span>PHPStan<span class="token punctuation">\</span>dumpType</span><span class="token punctuation">(</span><span class="token variable">$v</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// string</span></code></pre>
<p>However, this creates a problem: what if we do want to change the type? You can still achieve this using <a href="/writing-php-code/phpdocs-basics#setting-parameter-type-passed-by-reference"><code>@param-out</code></a> PHPDoc tag:</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token comment">/**
* @param-out int $p
*/</span>
<span class="token keyword">function</span> <span class="token function-definition function">foo</span><span class="token punctuation">(</span><span class="token keyword type-declaration">string</span> <span class="token operator">&</span><span class="token variable">$p</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">void</span>
<span class="token punctuation">{</span>
<span class="token variable">$p</span> <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token variable">$v</span> <span class="token operator">=</span> <span class="token string single-quoted-string">'foo'</span><span class="token punctuation">;</span>
<span class="token function">foo</span><span class="token punctuation">(</span><span class="token variable">$v</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function"><span class="token punctuation">\</span>PHPStan<span class="token punctuation">\</span>dumpType</span><span class="token punctuation">(</span><span class="token variable">$v</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// int</span></code></pre>
<h2 id="enforcing-assigned-type" tabindex="-1">Enforcing Assigned Type <a class="header-anchor ml-1 text-gray-300 hover:text-black" href="#enforcing-assigned-type">#</a></h2>
<p>Psst, I’ll let you in on a little secret. Not all extra PHPDoc features static analysers offer are actually enforced by their rules. <code>@param-out</code> was one example. You could have assigned anything to the variable and PHPStan would not complain.</p>
<p>That changes today. These assignments are now checked by new set of rules:</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token keyword">function</span> <span class="token function-definition function">foo</span><span class="token punctuation">(</span><span class="token keyword type-declaration">string</span> <span class="token operator">&</span><span class="token variable">$p</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">void</span>
<span class="token punctuation">{</span>
<span class="token comment">// Parameter &$p by-ref type of function foo() expects string, int given.</span>
<span class="token comment">// Tip: You can change the parameter out type with @param-out PHPDoc tag.</span>
<span class="token variable">$p</span> <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre>
<p>Yes, PHPStan will even contextually hint to you that you can write a <code>@param-out</code> PHPDoc tag if the assignment to an integer is intentional. These tips can be seen next to a 💡 in the CLI output, and also as <a href="/r/846c85d9-2159-4a18-9118-04eca47e266d">a small grey text on the on-line playground</a>.</p>
<p>If <code>@param-out</code> is already involved, the message is a little bit different:</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token comment">/**
* @param-out string $p
*/</span>
<span class="token keyword">function</span> <span class="token function-definition function">foo</span><span class="token punctuation">(</span><span class="token keyword type-declaration">string</span> <span class="token operator">&</span><span class="token variable">$p</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">void</span>
<span class="token punctuation">{</span>
<span class="token comment">// Parameter &$p @param-out type of function foo() expects string, int given.</span>
<span class="token variable">$p</span> <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre>
<p>And if the type of <code>@param-out</code> is different to the input type, but we don’t reassign the variable, PHPStan is also able to notice it:</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token comment">/**
* @param-out int $p
*/</span>
<span class="token keyword">function</span> <span class="token function-definition function">foo</span><span class="token punctuation">(</span><span class="token keyword type-declaration">string</span> <span class="token operator">&</span><span class="token variable">$p</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">void</span> <span class="token comment">// Parameter &$p @param-out type of function foo() expects int, string given.</span>
<span class="token punctuation">{</span>
<span class="token punctuation">}</span></code></pre>
<h2 id="detecting-overly-broad-%40param-out-type" tabindex="-1">Detecting Overly Broad <code>@param-out</code> Type <a class="header-anchor ml-1 text-gray-300 hover:text-black" href="#detecting-overly-broad-%40param-out-type">#</a></h2>
<p>Similar to how PHPStan handles return types, it now detects when a union type in the output parameter type includes unused parts:</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token keyword">function</span> <span class="token function-definition function">foo</span><span class="token punctuation">(</span><span class="token operator">?</span><span class="token keyword type-declaration">string</span> <span class="token operator">&</span><span class="token variable">$p</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">void</span>
<span class="token punctuation">{</span>
<span class="token comment">// Function foo() never assigns null to &$p so it can be removed from the by-ref type.</span>
<span class="token comment">// Tip: You can narrow the parameter out type with @param-out PHPDoc tag.</span>
<span class="token variable">$p</span> <span class="token operator">=</span> <span class="token string single-quoted-string">'foo'</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre>
<p>If your function accepts <code>null</code> but the variable never leaves the function as <code>null</code> anymore, you should inform the caller with the <code>@param-out</code> tag as well:</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token comment">/**
* @param-out string $p
*/</span>
<span class="token keyword">function</span> <span class="token function-definition function">foo</span><span class="token punctuation">(</span><span class="token operator">?</span><span class="token keyword type-declaration">string</span> <span class="token operator">&</span><span class="token variable">$p</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">void</span>
<span class="token punctuation">{</span>
<span class="token comment">// No errors</span>
<span class="token variable">$p</span> <span class="token operator">=</span> <span class="token string single-quoted-string">'foo'</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre>
<hr>
<p>Do you like PHPStan and use it every day? <a href="/sponsor"><strong>Consider supporting further development of PHPStan</strong></a>. I’d really appreciate it!</p>
A guide to call-site generic variance2023-09-19T00:00:00Zhttps://phpstan.org/blog/guide-to-call-site-generic-variance<p>PHPStan has supported what is called declaration-site variance for a long time. An example you might be familiar with is the infamous <a href="/blog/whats-up-with-template-covariant"><code>@template-covariant</code></a>. And although not as often useful, it also has <a href="https://jiripudil.cz/blog/contravariant-template-types">a contravariant counterpart</a>.</p>
<p>To freshen your memory: you can mark a template type as covariant:</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token comment">/** @template-covariant ItemType */</span>
<span class="token keyword">interface</span> <span class="token class-name-definition class-name">Collection</span>
<span class="token punctuation">{</span>
<span class="token comment">/** @param ItemType $item */</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function-definition function">add</span><span class="token punctuation">(</span><span class="token keyword type-hint">mixed</span> <span class="token variable">$item</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">void</span><span class="token punctuation">;</span>
<span class="token comment">/** @return ItemType|null */</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function-definition function">get</span><span class="token punctuation">(</span><span class="token keyword type-hint">int</span> <span class="token variable">$index</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">mixed</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre>
<p>This allows you to pass <code>Collection<Cat></code> into functions where <code>Collection<Animal></code> is expected. But it comes at a cost:</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token comment">/** @param Collection<Animal> $animals */</span>
<span class="token keyword">function</span> <span class="token function-definition function">foo</span><span class="token punctuation">(</span><span class="token class-name type-declaration">Collection</span> <span class="token variable">$animals</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">void</span>
<span class="token punctuation">{</span>
<span class="token variable">$animals</span><span class="token operator">-></span><span class="token function">add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Dog</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre>
<p>By itself, this is a perfectly valid code. But with <code>Collection</code> being covariant over its template type, we can now mix cats with dogs. That’s why PHPStan prevents us from using the <code>ItemType</code> in <code>Collection::add()</code> method’s parameter:</p>
<blockquote>
<p>Template type ItemType is declared as covariant, but occurs in contravariant position in parameter item of method <code>Collection::add()</code>.</p>
</blockquote>
<p>That’s the trade-off: if you want the <code>Collection</code> to be covariant, it can only have the <code>get</code> method. If you want it to have the <code>add</code> method too, it has to be invariant in its <code>ItemType</code>.</p>
<p>Until now, there has not been an easy way around this. You could split the interface into a read-only one that can safely be covariant, and an invariant one:</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token comment">/** @template-covariant ItemType */</span>
<span class="token keyword">interface</span> <span class="token class-name-definition class-name">ReadonlyCollection</span>
<span class="token punctuation">{</span>
<span class="token comment">/** @return ItemType|null */</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function-definition function">get</span><span class="token punctuation">(</span><span class="token keyword type-hint">int</span> <span class="token variable">$index</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">mixed</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token comment">/**
* @template ItemType
* @extends ReadonlyCollection<ItemType>
*/</span>
<span class="token keyword">interface</span> <span class="token class-name-definition class-name">Collection</span> <span class="token keyword">extends</span> <span class="token class-name">ReadonlyCollection</span>
<span class="token punctuation">{</span>
<span class="token comment">/** @param ItemType $item */</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function-definition function">add</span><span class="token punctuation">(</span><span class="token keyword type-hint">mixed</span> <span class="token variable">$item</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">void</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre>
<p>But that is a lot of work and can quickly get tedious. Call-site variance is a way of having PHPStan do this work for you.</p>
<h2 id="call-site-variance" tabindex="-1">Call-site variance <a class="header-anchor ml-1 text-gray-300 hover:text-black" href="#call-site-variance">#</a></h2>
<p>As the name suggests, call-site variance (or type projections, if you prefer fancier words) moves the variance annotation from the declaration to the call site. This means that the declaration of the interface can remain invariant, and therefore contain both <code>get</code> and <code>add</code> methods:</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token comment">/** @template ItemType */</span>
<span class="token keyword">interface</span> <span class="token class-name-definition class-name">Collection</span>
<span class="token punctuation">{</span>
<span class="token comment">/** @param ItemType $item */</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function-definition function">add</span><span class="token punctuation">(</span><span class="token keyword type-hint">mixed</span> <span class="token variable">$item</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">void</span><span class="token punctuation">;</span>
<span class="token comment">/** @return ItemType|null */</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function-definition function">get</span><span class="token punctuation">(</span><span class="token keyword type-hint">int</span> <span class="token variable">$index</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">mixed</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre>
<p>If you need a specific function to accept <code>Collection<Cat></code> in place of <code>Collection<Animal></code>, you can instruct it so by attaching the <code>covariant</code> keyword to the generic type argument:</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token comment">/** @param Collection<covariant Animal> $animals */</span>
<span class="token keyword">function</span> <span class="token function-definition function">foo</span><span class="token punctuation">(</span><span class="token class-name type-declaration">Collection</span> <span class="token variable">$animals</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">void</span>
<span class="token punctuation">{</span>
<span class="token variable">$animals</span><span class="token operator">-></span><span class="token function">add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Dog</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre>
<p>Correspondingly, the error has moved from the declaration to the call-site: if the implementation does something that would break type safety, like adding a <code>Dog</code> into the collection above, PHPStan will tell us:</p>
<blockquote>
<p>Parameter #1 $item of method <code>Collection<covariant Animal>::add()</code> expects never, Dog given.</p>
</blockquote>
<h2 id="call-site-contravariance" tabindex="-1">Call-site contravariance <a class="header-anchor ml-1 text-gray-300 hover:text-black" href="#call-site-contravariance">#</a></h2>
<p>Although not as useful, it’s worth mentioning that you can also use contravariant type projections. For example, if we wanted a happy little function that fills a collection with dogs, we could make it so that it accepts not only <code>Collection<Dog></code>, but also <code>Collection<Animal></code>, or even <code>Collection<mixed></code>:</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token comment">/** @param Collection<contravariant Dog> $collection */</span>
<span class="token keyword">function</span> <span class="token function-definition function">fill</span><span class="token punctuation">(</span><span class="token class-name type-declaration">Collection</span> <span class="token variable">$collection</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token constant boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token variable">$collection</span><span class="token operator">-></span><span class="token function">add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Dog</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span></code></pre>
<p>This too has a limitation, but at least this time it’s not so drastic: if we wanted to <code>get</code> a value from the collection, we can:</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token comment">/** @param Collection<contravariant Dog> $collection */</span>
<span class="token keyword">function</span> <span class="token function-definition function">fill</span><span class="token punctuation">(</span><span class="token class-name type-declaration">Collection</span> <span class="token variable">$collection</span><span class="token punctuation">)</span>
<span class="token punctuation">{</span>
<span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token variable">$collection</span><span class="token operator">-></span><span class="token function">get</span><span class="token punctuation">(</span><span class="token number">42</span><span class="token punctuation">)</span> <span class="token operator">===</span> <span class="token constant">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token variable">$collection</span><span class="token operator">-></span><span class="token function">add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Dog</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span></code></pre>
<p>But because a contravariant type is not bounded from the top, we cannot make any assumption about the type of the retrieved item, and neither can PHPStan, therefore the type of <code>$collection->get(42)</code> would be <code>mixed</code>.</p>
<h2 id="star-projections" tabindex="-1">Star projections <a class="header-anchor ml-1 text-gray-300 hover:text-black" href="#star-projections">#</a></h2>
<p>Sometimes, you just don’t care about the type of the values you’re working with. Let’s add a <code>count()</code> method to the <code>Collection</code>:</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token comment">/** @template ItemType */</span>
<span class="token keyword">interface</span> <span class="token class-name-definition class-name">Collection</span>
<span class="token punctuation">{</span>
<span class="token comment">/** @param ItemType $item */</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function-definition function">add</span><span class="token punctuation">(</span><span class="token keyword type-hint">mixed</span> <span class="token variable">$item</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">void</span><span class="token punctuation">;</span>
<span class="token comment">/** @return ItemType|null */</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function-definition function">get</span><span class="token punctuation">(</span><span class="token keyword type-hint">int</span> <span class="token variable">$index</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">mixed</span><span class="token punctuation">;</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function-definition function">count</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">int</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre>
<p>This method doesn’t reference the <code>ItemType</code> template type at all. We are using it in a function <code>printSize</code> that prints the size of a collection. In this case, the function can accept a collection of <em>anything</em>. Inspired by other languages such as Kotlin, PHPStan provides an idiomatic way of writing this, using an asterisk:</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token comment">/** @param Collection<*> $collection */</span>
<span class="token keyword">function</span> <span class="token function-definition function">printSize</span><span class="token punctuation">(</span><span class="token class-name type-declaration">Collection</span> <span class="token variable">$collection</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">int</span>
<span class="token punctuation">{</span>
<span class="token keyword">echo</span> <span class="token variable">$collection</span><span class="token operator">-></span><span class="token function">count</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre>
<p>Obviously, we cannot make any assumptions about the collection’s item type <em>whatsoever</em>. In other words, star projections combine the limitations of covariant and contravariant projections. If we were to <code>add()</code> anything into the collection inside this <code>printSize</code> function, we would get a similar error as above:</p>
<blockquote>
<p>Parameter #1 $item of method <code>Collection<*>::add()</code> expects never, Dog given.</p>
</blockquote>
<p>And if we wanted to <code>get</code> a value from the collection, it would be <code>mixed</code>:</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token comment">/** @param Collection<*> $collection */</span>
<span class="token keyword">function</span> <span class="token function-definition function">printSize</span><span class="token punctuation">(</span><span class="token class-name type-declaration">Collection</span> <span class="token variable">$collection</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">int</span>
<span class="token punctuation">{</span>
<span class="token variable">$item</span> <span class="token operator">=</span> <span class="token variable">$collection</span><span class="token operator">-></span><span class="token function">get</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// $item is mixed</span>
<span class="token punctuation">}</span></code></pre>
Using RuleErrorBuilder to enrich reported errors in custom rules2023-07-12T00:00:00Zhttps://phpstan.org/blog/using-rule-error-builder<p>When writing <a href="/developing-extensions/rules">custom rules</a> for PHPStan, developers can either return plain strings or RuleError instances.</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token comment">/**
* @phpstan-param TNodeType $node
* @return (string|RuleError)[] errors
*/</span>
<span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function-definition function">processNode</span><span class="token punctuation">(</span><span class="token class-name type-declaration">Node</span> <span class="token variable">$node</span><span class="token punctuation">,</span> <span class="token class-name type-declaration">Scope</span> <span class="token variable">$scope</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">array</span><span class="token punctuation">;</span></code></pre>
<p>The latter is a way to attach additional information to the reported errors.</p>
<p>RuleError <a href="https://github.com/phpstan/phpstan-src/blob/2.1.x/src/Rules/RuleError.php">is just an interface</a>. The way you create an instance is through <a href="https://apiref.phpstan.org/2.1.x/PHPStan.Rules.RuleErrorBuilder.html">RuleErrorBuilder</a>:</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token keyword">return</span> <span class="token punctuation">[</span>
<span class="token class-name static-context">RuleErrorBuilder</span><span class="token operator">::</span><span class="token function">message</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'This is an error message.'</span><span class="token punctuation">)</span>
<span class="token operator">-></span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">]</span><span class="token punctuation">;</span></code></pre>
<p>Besides setting an error message RuleErrorBuilder offers the following capabilities:</p>
<ul>
<li><code>->line(int $line)</code>: Set a different file line. Useful when you want to report a different line than the node which the rule was called for</li>
<li><code>->file(string $file)</code>: Set a different file path. Useful for <a href="/developing-extensions/collectors">collector rules</a>.</li>
<li><code>->tip(string $tip)</code>: Shows an additional text next to a 💡 emoji on the command line. You can tell the user why the error is reported or how to solve it.</li>
<li><code>->nonIgnorable()</code>: Makes the error non-ignorable by any means.</li>
<li><code>->metadata(array<mixed> $metadata)</code>: Attach additional metadata to the error that can later be read in the <a href="/developing-extensions/error-formatters">error formatter</a>.</li>
<li><code>->identifier(string $identifier)</code>: Sets an error identifier. More about this below.</li>
</ul>
<h2 id="error-identifiers" tabindex="-1">Error identifiers <a class="header-anchor ml-1 text-gray-300 hover:text-black" href="#error-identifiers">#</a></h2>
<p>The flagship feature of the upcoming PHPStan 1.11 release are error identifiers. You will be able to use them to ignore specific errors:</p>
<pre class="language-php"><code class="language-diff-php diff-highlight"><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">// @phpstan-ignore argument.type</span>
<span class="token variable">$this</span><span class="token operator">-></span><span class="token property">foo</span><span class="token operator">-></span><span class="token function">doSomethingWithString</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token variable">$this</span><span class="token operator">-></span><span class="token property">foo</span><span class="token operator">-></span><span class="token function">doSomethingWithString</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// @phpstan-ignore argument.type</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre>
<p>There’s a <a href="/error-identifiers">generated catalogue</a> of all the identifiers in PHPStan itself and 1<sup>st</sup> party extensions. Each identifier links to source code where it’s reported so this serves as a great educational resource about PHPStan internals.</p>
<p>To ensure great experience for all users, custom rules will also be required to provide their own identifiers. At first it will only be soft-enforced in <a href="/blog/what-is-bleeding-edge">Bleeding Edge</a>, and in PHPStan 2.0 it will be hard-enforced with a native return type.</p>
<p>This means that the ability to return plain strings from custom rules <strong>will eventually go away</strong>. I recommend you to switch to RuleErrorBuilder sooner rather than later.</p>
<p>PHPStan 1.11 with Bleeding Edge enabled will require custom rules to return errors with identifiers. As a first step, remove any PHPDoc above your <code>processNode</code> method. If your rule does not yet have <code>@implements Rule<...></code> generic PHPDoc tag, add it:</p>
<pre class="language-diff-php"><code class="language-diff-diff-php diff-highlight"><span class="token inserted-sign inserted language-php"><span class="token prefix inserted">+</span><span class="token comment">/**
<span class="token prefix inserted">+</span> * @implements Rule<StaticCall>
<span class="token prefix inserted">+</span> */</span>
</span><span class="token unchanged language-php"><span class="token prefix unchanged"> </span><span class="token keyword">class</span> <span class="token class-name-definition class-name">MyRule</span> <span class="token keyword">implements</span> <span class="token class-name">Rule</span>
<span class="token prefix unchanged"> </span><span class="token punctuation">{</span>
<span class="token prefix unchanged"> </span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function-definition function">getNodeType</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">string</span>
<span class="token prefix unchanged"> </span> <span class="token punctuation">{</span>
<span class="token prefix unchanged"> </span> <span class="token keyword">return</span> <span class="token class-name static-context">StaticCall</span><span class="token operator">::</span><span class="token keyword">class</span><span class="token punctuation">;</span>
<span class="token prefix unchanged"> </span> <span class="token punctuation">}</span>
</span>
<span class="token deleted-sign deleted language-php"><span class="token prefix deleted">-</span> <span class="token comment">/**
<span class="token prefix deleted">-</span> * @param StaticCall $node
<span class="token prefix deleted">-</span> * @param Scope $scope
<span class="token prefix deleted">-</span> * @return string[]
<span class="token prefix deleted">-</span> */</span>
</span><span class="token unchanged language-php"><span class="token prefix unchanged"> </span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function-definition function">processNode</span><span class="token punctuation">(</span><span class="token class-name type-declaration">Node</span> <span class="token variable">$node</span><span class="token punctuation">,</span> <span class="token class-name type-declaration">Scope</span> <span class="token variable">$scope</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">array</span>
<span class="token prefix unchanged"> </span> <span class="token punctuation">{</span>
<span class="token prefix unchanged"> </span> <span class="token comment">// ...</span>
<span class="token prefix unchanged"> </span> <span class="token punctuation">}</span>
</span></code></pre>
<p>Even without the PHPDoc both PHPStan and PhpStorm will understand that the parameter <code>$node</code> coming into the <code>processNode</code> method is a <code>StaticCall</code>.</p>
<p>After that migrate the plain strings to RuleErrorBuilder, and add error identifiers for each of them:</p>
<pre class="language-diff-php"><code class="language-diff-diff-php diff-highlight"><span class="token unchanged language-php"><span class="token prefix unchanged"> </span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function-definition function">processNode</span><span class="token punctuation">(</span><span class="token class-name type-declaration">Node</span> <span class="token variable">$node</span><span class="token punctuation">,</span> <span class="token class-name type-declaration">Scope</span> <span class="token variable">$scope</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">array</span>
<span class="token prefix unchanged"> </span> <span class="token punctuation">{</span>
<span class="token prefix unchanged"> </span> <span class="token keyword">return</span> <span class="token punctuation">[</span>
</span><span class="token deleted-sign deleted language-php"><span class="token prefix deleted">-</span> <span class="token string single-quoted-string">'This is an error message.'</span><span class="token punctuation">,</span>
</span><span class="token inserted-sign inserted language-php"><span class="token prefix inserted">+</span> <span class="token class-name static-context">RuleErrorBuilder</span><span class="token operator">::</span><span class="token function">message</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'This is an error message.'</span><span class="token punctuation">)</span>
<span class="token prefix inserted">+</span> <span class="token operator">-></span><span class="token function">identifier</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'some.problem'</span><span class="token punctuation">)</span>
<span class="token prefix inserted">+</span> <span class="token operator">-></span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
</span><span class="token unchanged language-php"><span class="token prefix unchanged"> </span> <span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token prefix unchanged"> </span> <span class="token punctuation">}</span>
</span></code></pre>
<p>For an inspiration how the identifiers should look like check out <a href="/error-identifiers">the catalogue</a>.</p>
<p>The identifier must consist of lowercase and uppercase ASCII letters, and optionally can have one or more dots in the middle. <a href="https://github.com/phpstan/phpstan-src/blob/2.1.x/tests/PHPStan/Analyser/ErrorTest.php">See the tests</a> for examples of valid and invalid identifiers.</p>
<hr>
<p>PHPStan 1.11 will be released at some time in the coming months. Don’t worry, your old rules will not break with that release, but it would be great to modernize them to take advantage (and let your users take advantage) of the latest PHPStan features. Thanks!</p>