<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
	<title>PHPStan Blog</title>
	<subtitle>Find Bugs In Your Code Without Writing Tests!</subtitle>
	<link href="https://phpstan.org/rss.xml" rel="self"/>
	<link href="https://phpstan.org/"/>
	<updated>2026-03-26T00:00:00Z</updated>
	<id>https://phpstan.org/</id>
	<author>
		<name>Ondřej Mirtes</name>
		<email>ondrej@mirtes.cz</email>
	</author>
	
		
		<entry>
			<title>Using PHPStan to Extract Data About Your Codebase</title>
			<link href="https://phpstan.org/blog/using-phpstan-to-extract-data-about-your-codebase"/>
			<updated>2026-03-26T00:00:00Z</updated>
			<id>https://phpstan.org/blog/using-phpstan-to-extract-data-about-your-codebase</id>
			<content type="html" xml:base="https://phpstan.org">&lt;p&gt;PHPStan is known for finding bugs in your code. But that’s not all it can do. When PHPStan analyses your codebase, it builds a detailed model of every class, method, property, type, and relationship. All of that knowledge is accessible through &lt;a href=&quot;/developing-extensions/scope&quot;&gt;Scope&lt;/a&gt; and &lt;a href=&quot;/developing-extensions/reflection&quot;&gt;Reflection&lt;/a&gt;. It’d be a shame to only use it for error reporting.&lt;/p&gt;
&lt;p&gt;In this article, I’m going to show you how to use PHPStan as a data extraction tool — to query your codebase and produce machine-readable output you can use for documentation, visualization, or any other purpose.&lt;/p&gt;
&lt;h2 id=&quot;the-general-approach&quot; tabindex=&quot;-1&quot;&gt;The general approach &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#the-general-approach&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The idea relies on three PHPStan extension points working together:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;/developing-extensions/collectors&quot;&gt;Collectors&lt;/a&gt;&lt;/strong&gt; gather data about your codebase during analysis. They visit AST nodes, query the Scope and Reflection, and return structured data.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A &lt;a href=&quot;/developing-extensions/rules&quot;&gt;rule&lt;/a&gt;&lt;/strong&gt; that processes &lt;code&gt;CollectedDataNode&lt;/code&gt; — a special node that gives you access to all collected data after the analysis is complete. Instead of reporting actual errors, it packages the collected data as metadata attached to rule errors.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A custom &lt;a href=&quot;/developing-extensions/error-formatters&quot;&gt;error formatter&lt;/a&gt;&lt;/strong&gt; that reads the metadata from these errors and outputs machine-readable data (like JSON) instead of the usual error table.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is a bit of a creative hack — we’re using PHPStan’s error reporting pipeline as a data transport mechanism. The “errors” aren’t real errors at all, they’re just carriers for the data we extracted.&lt;/p&gt;
&lt;p&gt;All of this is tied together with a &lt;a href=&quot;/config-reference&quot;&gt;configuration file&lt;/a&gt; that registers the collectors, the rule, and the error formatter, and points PHPStan at the code you want to analyse.&lt;/p&gt;
&lt;p&gt;Let’s walk through a real-world example.&lt;/p&gt;
&lt;h2 id=&quot;example%3A-generating-a-call-map&quot; tabindex=&quot;-1&quot;&gt;Example: Generating a call map &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#example%3A-generating-a-call-map&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://gitlab.com/stella-maris/callmap&quot;&gt;stella-maris/callmap&lt;/a&gt; by &lt;a href=&quot;https://github.com/heiglandreas&quot;&gt;Andreas Heigl&lt;/a&gt; is a PHPStan extension that maps every method call in your codebase — which method calls which other method, and in which class. The result is a JSON file you can feed into &lt;a href=&quot;https://gitlab.com/stella-maris/callmapcli&quot;&gt;CallMap CLI&lt;/a&gt; to generate a call graph.&lt;/p&gt;
&lt;h3 id=&quot;the-collector&quot; tabindex=&quot;-1&quot;&gt;The Collector &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#the-collector&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The collector visits every &lt;code&gt;MethodCall&lt;/code&gt; node in the AST and records four things: the calling class, the calling method, the called class, and the called method.&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-diff-php diff-highlight&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;token package&quot;&gt;PhpParser&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;Node&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;token package&quot;&gt;PhpParser&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;Node&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;Expr&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;MethodCall&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;token package&quot;&gt;PHPStan&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;Analyser&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;Scope&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;token package&quot;&gt;PHPStan&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;Collectors&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;Collector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/**
 * @implements Collector&amp;lt;MethodCall, array{callingClass?: string, callingMethod: string, calledClass: string, calledMethod: string}&gt;
 */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name-definition class-name&quot;&gt;MethodCallCollector&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Collector&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;getNodeType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword return-type&quot;&gt;string&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name static-context&quot;&gt;MethodCall&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;processNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name type-declaration&quot;&gt;Node&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$node&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name type-declaration&quot;&gt;Scope&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$scope&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token variable&quot;&gt;$methodName&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$node&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$methodName&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;instanceof&lt;/span&gt; &lt;span class=&quot;token class-name class-name-fully-qualified&quot;&gt;Node&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;Identifier&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// Find the class that declares the called method&lt;/span&gt;
        &lt;span class=&quot;token variable&quot;&gt;$type&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$scope&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$node&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token variable&quot;&gt;$methodReflection&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$scope&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMethodReflection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$methodName&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$methodReflection&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token variable&quot;&gt;$calledClass&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$methodReflection&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getDeclaringClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// Find the calling context&lt;/span&gt;
        &lt;span class=&quot;token variable&quot;&gt;$callingClass&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$scope&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isInClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token variable&quot;&gt;$callingClass&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$scope&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getClassReflection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
            &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;callingClass&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$callingClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;callingMethod&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$scope&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFunction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?-&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;calledClass&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$calledClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;calledMethod&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$methodName&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The key thing here is that we’re using &lt;code&gt;$scope-&amp;gt;getMethodReflection()&lt;/code&gt; to resolve the &lt;em&gt;declaring&lt;/em&gt; class of the called method. This means if you call &lt;code&gt;$user-&amp;gt;save()&lt;/code&gt; and &lt;code&gt;save()&lt;/code&gt; is declared on a parent &lt;code&gt;Model&lt;/code&gt; class, the collector records &lt;code&gt;Model&lt;/code&gt; as the called class, not the concrete type. This is the kind of insight that would be difficult to get from simple text-based analysis.&lt;/p&gt;
&lt;h3 id=&quot;the-rule&quot; tabindex=&quot;-1&quot;&gt;The Rule &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#the-rule&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The rule processes &lt;code&gt;CollectedDataNode&lt;/code&gt; and wraps each row of collected data as a rule error with metadata:&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-diff-php diff-highlight&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;token package&quot;&gt;PHPStan&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;Node&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;CollectedDataNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;token package&quot;&gt;PHPStan&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;Rules&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;RuleErrorBuilder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/**
 * @implements &#92;PHPStan&#92;Rules&#92;Rule&amp;lt;CollectedDataNode&gt;
 */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name-definition class-name&quot;&gt;Rule&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name class-name-fully-qualified&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;PHPStan&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;Rules&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;Rule&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;getNodeType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword return-type&quot;&gt;string&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token class-name static-context&quot;&gt;CollectedDataNode&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;processNode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name type-declaration&quot;&gt;Node&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$node&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name type-declaration&quot;&gt;Scope&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$scope&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword return-type&quot;&gt;array&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token variable&quot;&gt;$errors&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;foreach&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$node&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name static-context&quot;&gt;MethodCallCollector&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$rows&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;foreach&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$rows&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$row&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token variable&quot;&gt;$errors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name static-context&quot;&gt;RuleErrorBuilder&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;Metadata&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;identifier&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;callmapFormatter.data&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$row&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$errors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;$node-&amp;gt;get(MethodCallCollector::class)&lt;/code&gt; call retrieves all data collected by &lt;code&gt;MethodCallCollector&lt;/code&gt; across the entire codebase. Each entry is wrapped as a &lt;code&gt;RuleError&lt;/code&gt; with a specific identifier and the original data stashed in the metadata.&lt;/p&gt;
&lt;h3 id=&quot;the-error-formatter&quot; tabindex=&quot;-1&quot;&gt;The Error Formatter &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#the-error-formatter&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The error formatter reads these metadata-carrying errors and outputs them as JSON:&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-diff-php diff-highlight&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;token package&quot;&gt;PHPStan&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;Command&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;AnalysisResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;token package&quot;&gt;PHPStan&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;Command&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;ErrorFormatter&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;ErrorFormatter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;token package&quot;&gt;PHPStan&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;Command&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;ErrorFormatter&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;TableErrorFormatter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;token package&quot;&gt;PHPStan&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;Command&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;Output&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name-definition class-name&quot;&gt;CallmapFormatter&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ErrorFormatter&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;__construct&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name type-declaration&quot;&gt;TableErrorFormatter&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$tableErrorFormatter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;formatErrors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token class-name type-declaration&quot;&gt;AnalysisResult&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$analysisResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token class-name type-declaration&quot;&gt;Output&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$output&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword return-type&quot;&gt;int&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$analysisResult&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasInternalErrors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// fall back to table output&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;tableErrorFormatter&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;formatErrors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                &lt;span class=&quot;token variable&quot;&gt;$analysisResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token variable&quot;&gt;$output&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token variable&quot;&gt;$json&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;foreach&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$analysisResult&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFileSpecificErrors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$error&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getIdentifier&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;callmapFormatter.data&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token comment&quot;&gt;// An unexpected real error — fall back to table output&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;tableErrorFormatter&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;formatErrors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
                    &lt;span class=&quot;token variable&quot;&gt;$analysisResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;token variable&quot;&gt;$output&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

            &lt;span class=&quot;token variable&quot;&gt;$metadata&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$error&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMetadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token variable&quot;&gt;$json&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
                &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;callingClass&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;callingClass&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;callingMethod&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;callingMethod&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;calledClass&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;calledClass&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;calledMethod&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$metadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;calledMethod&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token function&quot;&gt;file_put_contents&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;callmap.json&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;json_encode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;data&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$json&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The formatter checks for the specific error identifier. If it encounters any unexpected errors (real PHPStan errors from the analysed code), it falls back to the standard table output. This is a good safety measure — you want to know if something went wrong during the analysis instead of silently producing incomplete data.&lt;/p&gt;
&lt;h3 id=&quot;the-configuration&quot; tabindex=&quot;-1&quot;&gt;The Configuration &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#the-configuration&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;All of this is tied together with a NEON configuration file:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-diff-yaml diff-highlight&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;parameters&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;errorFormat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; callmap
    &lt;span class=&quot;token key atrule&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; src

    &lt;span class=&quot;token comment&quot;&gt;# Makes PHPStan not require a `--level` for `analyse` command.&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# Our rule will be the only one that runs&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;customRulesetUsed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;

&lt;span class=&quot;token key atrule&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; StellaMaris&#92;Callmap&#92;Rule

&lt;span class=&quot;token key atrule&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;errorFormatter.callmap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; StellaMaris&#92;Callmap&#92;CallmapFormatter
    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;
        &lt;span class=&quot;token key atrule&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; StellaMaris&#92;Callmap&#92;MethodCallCollector
        &lt;span class=&quot;token key atrule&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; phpstan.collector&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You run it like any other PHPStan analysis:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-diff-bash diff-highlight&quot;&gt;vendor/bin/phpstan analyse &lt;span class=&quot;token parameter variable&quot;&gt;-c&lt;/span&gt; callmap.neon&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And the output is a &lt;code&gt;callmap.json&lt;/code&gt; file with every method call in your codebase:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-diff-json diff-highlight&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;data&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;callingClass&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;App&#92;&#92;Service&#92;&#92;UserService&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;callingMethod&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;getUser&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;calledClass&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;App&#92;&#92;Repository&#92;&#92;UserRepository&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token property&quot;&gt;&quot;calledMethod&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;find&quot;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;a-more-complex-example%3A-extracting-error-identifiers&quot; tabindex=&quot;-1&quot;&gt;A more complex example: Extracting error identifiers &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#a-more-complex-example%3A-extracting-error-identifiers&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I use this same pattern myself to &lt;a href=&quot;/error-identifiers&quot;&gt;generate the error identifier documentation&lt;/a&gt; on this website. Every PHPStan error has a &lt;a href=&quot;/blog/phpstan-1-11-errors-identifiers-phpstan-pro-reboot&quot;&gt;unique identifier&lt;/a&gt; like &lt;code&gt;argument.type&lt;/code&gt; or &lt;code&gt;deadCode.unreachable&lt;/code&gt;. The &lt;a href=&quot;https://github.com/phpstan/phpstan/tree/2.2.x/identifier-extractor&quot;&gt;identifier extractor&lt;/a&gt; scans the PHPStan source code and all 1st-party extensions to find where these identifiers are defined.&lt;/p&gt;
&lt;p&gt;It’s more complex than the callmap example because it uses four different collectors, each targeting a different code pattern:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;RuleErrorBuilderCollector&lt;/code&gt; — finds calls to &lt;code&gt;RuleErrorBuilder::message()-&amp;gt;identifier(&#39;...&#39;)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ErrorWithIdentifierCollector&lt;/code&gt; — finds calls to &lt;code&gt;Error::withIdentifier(&#39;...&#39;)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RestrictedUsageCollector&lt;/code&gt; — finds calls to &lt;code&gt;RestrictedUsage::create()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RuleConstructorParameterCollector&lt;/code&gt; — maps rule-to-rule injection dependencies&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The last one is interesting — some rules delegate error reporting to helper classes. The extractor traces these dependency chains to figure out which top-level rule ultimately emits a given identifier.&lt;/p&gt;
&lt;p&gt;The output is a JSON file that gets &lt;a href=&quot;https://github.com/phpstan/phpstan/tree/2.2.x/identifier-extractor/merge.php&quot;&gt;merged from multiple repositories&lt;/a&gt;, transformed, and used to generate the “Rules that report this error” section you can see at the bottom of each &lt;a href=&quot;/error-identifiers/argument.type&quot;&gt;error identifier detail page&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;build-your-own&quot; tabindex=&quot;-1&quot;&gt;Build your own &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#build-your-own&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you want to extract data from your codebase, the pattern is always the same:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Write one or more &lt;a href=&quot;/developing-extensions/collectors&quot;&gt;collectors&lt;/a&gt; that implement &lt;code&gt;PHPStan&#92;Collectors&#92;Collector&lt;/code&gt; and gather the data you’re interested in.&lt;/li&gt;
&lt;li&gt;Write a &lt;a href=&quot;/developing-extensions/rules&quot;&gt;rule&lt;/a&gt; for &lt;code&gt;CollectedDataNode&lt;/code&gt; that packages collected data as error metadata.&lt;/li&gt;
&lt;li&gt;Write a custom &lt;a href=&quot;/developing-extensions/error-formatters&quot;&gt;error formatter&lt;/a&gt; that reads the metadata and produces the output you need.&lt;/li&gt;
&lt;li&gt;Create a NEON configuration file with &lt;code&gt;customRulesetUsed: true&lt;/code&gt; and your custom &lt;code&gt;errorFormat&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The possibilities are endless. You could generate dependency graphs between namespaces, find all usages of a deprecated API, catalogue all database queries, or map out event listener registrations. Anything PHPStan can see through its type inference and reflection, you can extract.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Do you like PHPStan and use it every day? &lt;a href=&quot;/sponsor&quot;&gt;&lt;strong&gt;Consider supporting further development of PHPStan&lt;/strong&gt;&lt;/a&gt;. I’d really appreciate it!&lt;/p&gt;
</content>
		</entry>
	
		
		<entry>
			<title>Why Array String Keys Are Not Type-Safe in PHP</title>
			<link href="https://phpstan.org/blog/why-array-string-keys-are-not-type-safe"/>
			<updated>2026-03-19T00:00:00Z</updated>
			<id>https://phpstan.org/blog/why-array-string-keys-are-not-type-safe</id>
			<content type="html" xml:base="https://phpstan.org">&lt;p&gt;PHP silently casts array keys. This is one of the oldest and most well-known quirks in the language. But I don’t think its consequences for type safety have been fully appreciated — until now.&lt;/p&gt;
&lt;h2 id=&quot;the-casting-rules&quot; tabindex=&quot;-1&quot;&gt;The casting rules &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#the-casting-rules&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://www.php.net/manual/en/language.types.array.php&quot;&gt;PHP manual&lt;/a&gt; lists the following automatic key casts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Strings&lt;/strong&gt; containing valid decimal integers are cast to &lt;code&gt;int&lt;/code&gt;. The key &lt;code&gt;&amp;quot;8&amp;quot;&lt;/code&gt; is stored as &lt;code&gt;8&lt;/code&gt;. But &lt;code&gt;&amp;quot;08&amp;quot;&lt;/code&gt; stays as a string because it’s not a valid decimal integer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Floats&lt;/strong&gt; are truncated to &lt;code&gt;int&lt;/code&gt;. The key &lt;code&gt;8.7&lt;/code&gt; becomes &lt;code&gt;8&lt;/code&gt;. Deprecated since PHP 8.1.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bools&lt;/strong&gt; are cast to &lt;code&gt;int&lt;/code&gt;. &lt;code&gt;true&lt;/code&gt; becomes &lt;code&gt;1&lt;/code&gt;, &lt;code&gt;false&lt;/code&gt; becomes &lt;code&gt;0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Null&lt;/strong&gt; becomes the empty string &lt;code&gt;&amp;quot;&amp;quot;&lt;/code&gt;. Deprecated since PHP 8.5.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The classic demonstration:&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-diff-php diff-highlight&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$array&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;    &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;a&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;1&#39;&lt;/span&gt;  &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;b&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token number&quot;&gt;1.5&lt;/span&gt;  &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;c&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token constant boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;d&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;var_dump&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$array&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// int(1)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Four entries, four different literal types — but PHP treats them all as the same key &lt;code&gt;1&lt;/code&gt;. Only the last value survives.&lt;/p&gt;
&lt;p&gt;This isn’t just a quirky language feature. It breaks type safety in ways that static analysis has historically been unable to catch.&lt;/p&gt;
&lt;h2 id=&quot;the-strict_types-trap&quot; tabindex=&quot;-1&quot;&gt;The strict_types trap &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#the-strict_types-trap&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Consider this innocent-looking code:&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-diff-php diff-highlight&quot;&gt;&lt;span class=&quot;token php language-php&quot;&gt;&lt;span class=&quot;token delimiter important&quot;&gt;&amp;lt;?php&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;declare&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;strict_types &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;processKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword type-hint&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword return-type&quot;&gt;void&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/** @param array&amp;lt;string, mixed&gt; $data */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;processData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword type-hint&quot;&gt;array&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword return-type&quot;&gt;void&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;foreach&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$data&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$key&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;processKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PHPStan sees &lt;code&gt;$data&lt;/code&gt; typed as &lt;code&gt;array&amp;lt;string, mixed&amp;gt;&lt;/code&gt;, so &lt;code&gt;$key&lt;/code&gt; must be &lt;code&gt;string&lt;/code&gt;. No errors reported. The code looks perfectly safe.&lt;/p&gt;
&lt;p&gt;But at runtime, if &lt;code&gt;$data&lt;/code&gt; was built with a key like &lt;code&gt;&#39;123&#39;&lt;/code&gt;, PHP stored it as &lt;code&gt;int(123)&lt;/code&gt;. When the &lt;code&gt;foreach&lt;/code&gt; reaches that entry, &lt;code&gt;$key&lt;/code&gt; is &lt;code&gt;int&lt;/code&gt;, not &lt;code&gt;string&lt;/code&gt;. With &lt;code&gt;strict_types&lt;/code&gt; enabled, &lt;code&gt;processKey($key)&lt;/code&gt; throws a &lt;code&gt;TypeError&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;PHPStan told you everything was fine. PHP disagrees. The type annotation &lt;code&gt;array&amp;lt;string, mixed&amp;gt;&lt;/code&gt; promises something that PHP arrays simply cannot guarantee.&lt;/p&gt;
&lt;h2 id=&quot;array_merge-re-indexes-your-%E2%80%9Cstring%E2%80%9D-keys&quot; tabindex=&quot;-1&quot;&gt;array_merge re-indexes your “string” keys &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#array_merge-re-indexes-your-%E2%80%9Cstring%E2%80%9D-keys&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;array_merge()&lt;/code&gt; has different behaviour for integer keys and string keys: integer keys get re-indexed starting from &lt;code&gt;0&lt;/code&gt;, while string keys are preserved. When your string keys get silently cast to integers, you get re-indexing you didn’t ask for:&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-diff-php diff-highlight&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$a&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;100&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;foo&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;// Stored as [100 =&gt; &#39;foo&#39;]&lt;/span&gt;
&lt;span class=&quot;token variable&quot;&gt;$b&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;200&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;bar&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;// Stored as [200 =&gt; &#39;bar&#39;]&lt;/span&gt;

&lt;span class=&quot;token variable&quot;&gt;$merged&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;array_merge&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$a&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$b&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// Result: [0 =&gt; &#39;foo&#39;, 1 =&gt; &#39;bar&#39;]&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// Not [&#39;100&#39; =&gt; &#39;foo&#39;, &#39;200&#39; =&gt; &#39;bar&#39;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The developer wrote string keys. They got integer keys. And &lt;code&gt;array_merge&lt;/code&gt; silently re-indexed them.&lt;/p&gt;
&lt;h2 id=&quot;strict-key-lookups-fail&quot; tabindex=&quot;-1&quot;&gt;Strict key lookups fail &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#strict-key-lookups-fail&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you use &lt;code&gt;array_keys()&lt;/code&gt; and then search with strict comparison, you’ll be surprised:&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-diff-php diff-highlight&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$data&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;123&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;value&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token variable&quot;&gt;$keys&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;array_keys&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// $keys is [123] — an integer!&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;in_array&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;123&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$keys&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;// false!&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// Strict comparison: &#39;123&#39; !== 123&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You put in &lt;code&gt;&#39;123&#39;&lt;/code&gt; as a string key, you search for &lt;code&gt;&#39;123&#39;&lt;/code&gt; as a string — and it’s not found. Because PHP silently changed the key’s type behind your back.&lt;/p&gt;
&lt;h2 id=&quot;a-real-world-example&quot; tabindex=&quot;-1&quot;&gt;A real-world example &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#a-real-world-example&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Imagine working with postal codes:&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-diff-php diff-highlight&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/** @return array{&#39;1234aa&#39;, &#39;2345bb&#39;} */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;getPostals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;1234aa&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;2345bb&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token variable&quot;&gt;$pc4Set&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;foreach&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getPostals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$postal&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token variable&quot;&gt;$pc4Set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;substr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$postal&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token variable&quot;&gt;$pc4&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;array_keys&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$pc4Set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;PHPStan&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;dumpType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$pc4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 1234|2345 (int, not string!)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The developer extracted the first 4 characters of each postal code using &lt;code&gt;substr()&lt;/code&gt; — a function that returns a &lt;code&gt;string&lt;/code&gt;. But because those strings happen to be &lt;code&gt;&#39;1234&#39;&lt;/code&gt; and &lt;code&gt;&#39;2345&#39;&lt;/code&gt;, PHP casts them to integers when they’re used as array keys. The resulting &lt;code&gt;array_keys()&lt;/code&gt; returns integers, not strings.&lt;/p&gt;
&lt;h2 id=&quot;what-phpstan-already-does&quot; tabindex=&quot;-1&quot;&gt;What PHPStan already does &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#what-phpstan-already-does&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I’ve been laying the groundwork for solving this problem.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;/try&quot;&gt;online playground&lt;/a&gt; already notifies you about key casts. When you write a literal array or access an array with a key that will be cast, PHPStan tells you:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Key ‘1234’ (string) will be cast to 1234 (int) in the array access.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&quot;/r/188f1e7b-a708-4f8f-b483-7b812d4ab09c&quot;&gt;See this example in the playground »&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Additionally, the &lt;a href=&quot;/config-reference#reportnonintstringarraykey&quot;&gt;&lt;code&gt;reportNonIntStringArrayKey&lt;/code&gt;&lt;/a&gt; option (available since PHPStan 2.1.39) can report when non-int/non-string types like booleans, floats, or &lt;code&gt;null&lt;/code&gt; are used as array keys:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-diff-yaml diff-highlight&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;parameters&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;reportNonIntStringArrayKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-diff-php diff-highlight&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$array&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token variable&quot;&gt;$array&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token constant boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;value&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;// Reported: Invalid array key type true.&lt;/span&gt;
&lt;span class=&quot;token variable&quot;&gt;$array&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;4.5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;value&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;   &lt;span class=&quot;token comment&quot;&gt;// Reported: Invalid array key type float.&lt;/span&gt;
&lt;span class=&quot;token variable&quot;&gt;$array&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;value&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;// Reported: Invalid array key type null.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These features catch explicit misuses of non-int/non-string types as keys. But they don’t solve the fundamental problem: any &lt;code&gt;string&lt;/code&gt; might be a decimal integer string, and once it’s used as an array key, it silently becomes an &lt;code&gt;int&lt;/code&gt;. PHPStan couldn’t do anything about this — until now.&lt;/p&gt;
&lt;h2 id=&quot;phpstan-2.2.0%3A-non-decimal-int-string&quot; tabindex=&quot;-1&quot;&gt;PHPStan 2.2.0: non-decimal-int-string &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#phpstan-2.2.0%3A-non-decimal-int-string&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;PHPStan 2.2.0 introduces two new types: &lt;code&gt;non-decimal-int-string&lt;/code&gt; and &lt;code&gt;decimal-int-string&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A &lt;em&gt;decimal integer string&lt;/em&gt; is a string that PHP considers a valid decimal integer and would cast to &lt;code&gt;int&lt;/code&gt; when used as an array key — like &lt;code&gt;&#39;0&#39;&lt;/code&gt;, &lt;code&gt;&#39;123&#39;&lt;/code&gt;, or &lt;code&gt;&#39;42&#39;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A &lt;code&gt;non-decimal-int-string&lt;/code&gt; is any string that is &lt;em&gt;not&lt;/em&gt; one of these — like &lt;code&gt;&#39;hello&#39;&lt;/code&gt;, &lt;code&gt;&#39;08&#39;&lt;/code&gt;, &lt;code&gt;&#39;+1&#39;&lt;/code&gt;, or &lt;code&gt;&#39;1.5&#39;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The key insight: if your array keys are &lt;code&gt;non-decimal-int-string&lt;/code&gt;, PHP will never cast them. They will always remain strings. This means &lt;code&gt;array&amp;lt;non-decimal-int-string, mixed&amp;gt;&lt;/code&gt; is genuinely type-safe:&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-diff-php diff-highlight&quot;&gt;&lt;span class=&quot;token php language-php&quot;&gt;&lt;span class=&quot;token delimiter important&quot;&gt;&amp;lt;?php&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;declare&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;strict_types &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;processKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword type-hint&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword return-type&quot;&gt;void&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/** @param array&amp;lt;non-decimal-int-string, mixed&gt; $data */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;processData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword type-hint&quot;&gt;array&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword return-type&quot;&gt;void&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;foreach&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$data&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$key&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;processKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Always safe — $key is guaranteed to be string&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No more &lt;code&gt;TypeError&lt;/code&gt; at runtime. No more surprise re-indexing by &lt;code&gt;array_merge&lt;/code&gt;. No more failed strict key lookups. The type system finally tells the truth about what your array keys actually are.&lt;/p&gt;
&lt;p&gt;You can start using &lt;code&gt;non-decimal-int-string&lt;/code&gt; in your type annotations today. It makes sense anywhere you need array keys that are guaranteed to stay as strings.&lt;/p&gt;
&lt;h2 id=&quot;reportunsafearraystringkeycasting&quot; tabindex=&quot;-1&quot;&gt;reportUnsafeArrayStringKeyCasting &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#reportunsafearraystringkeycasting&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;But you shouldn’t have to rewrite all your &lt;code&gt;array&amp;lt;string, mixed&amp;gt;&lt;/code&gt; annotations by hand. That’s where the new &lt;a href=&quot;/config-reference#reportunsafearraystringkeycasting&quot;&gt;&lt;code&gt;reportUnsafeArrayStringKeyCasting&lt;/code&gt;&lt;/a&gt; config parameter comes in. It has three values:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;null&lt;/code&gt;&lt;/strong&gt; (default) — behaviour as it is today. No additional checks. &lt;a href=&quot;/r/dcbe6433-b2bb-4c1e-b378-2d61190ff253&quot;&gt;Playground example »&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;&#39;detect&#39;&lt;/code&gt;&lt;/strong&gt; — typehinted &lt;code&gt;array&amp;lt;string, mixed&amp;gt;&lt;/code&gt; accepts other &lt;code&gt;array&amp;lt;string, mixed&amp;gt;&lt;/code&gt;, but when you get a key out of it, the key type will be &lt;code&gt;int|non-decimal-int-string&lt;/code&gt; instead of &lt;code&gt;string&lt;/code&gt;. This reveals the potential issues in your code without requiring you to change any type annotations. &lt;a href=&quot;/r/170d5184-42cd-4ad1-8cc7-c10d83d0f967&quot;&gt;Playground example »&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;&#39;prevent&#39;&lt;/code&gt;&lt;/strong&gt; — typehinted &lt;code&gt;array&amp;lt;string, mixed&amp;gt;&lt;/code&gt; is essentially treated as &lt;code&gt;array&amp;lt;non-decimal-int-string, mixed&amp;gt;&lt;/code&gt;. It will only accept another array with safe string keys. Additionally, any &lt;code&gt;string&lt;/code&gt; being used as an array key is correctly narrowed to &lt;code&gt;int|non-decimal-int-string&lt;/code&gt;. This is the strictest mode and gives you the most protection. &lt;a href=&quot;/r/c05a336c-727e-44ae-81d8-1e63bfaac563&quot;&gt;Playground example »&lt;/a&gt;&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-diff-yaml diff-highlight&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;parameters&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;reportUnsafeArrayStringKeyCasting&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; detect &lt;span class=&quot;token comment&quot;&gt;# or: prevent&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I’d recommend starting with &lt;code&gt;detect&lt;/code&gt; to see how your codebase is affected, and then moving to &lt;code&gt;prevent&lt;/code&gt; once you’ve addressed the findings.&lt;/p&gt;
&lt;p&gt;Please note this is highly experimental and things can change as I adjust and make the types more practical and useful.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Do you like PHPStan and use it every day? &lt;a href=&quot;/sponsor&quot;&gt;&lt;strong&gt;Consider supporting further development of PHPStan&lt;/strong&gt;&lt;/a&gt;. I’d really appreciate it!&lt;/p&gt;
</content>
		</entry>
	
		
		<entry>
			<title>PHPStan fully supports PHP 8.5!</title>
			<link href="https://phpstan.org/blog/phpstan-fully-supports-php-8-5"/>
			<updated>2026-02-13T00:00:00Z</updated>
			<id>https://phpstan.org/blog/phpstan-fully-supports-php-8-5</id>
			<content type="html" xml:base="https://phpstan.org">&lt;p&gt;PHP 8.5 has been out for a while now, and so has PHPStan’s support for it. &lt;a href=&quot;https://github.com/phpstan/phpstan/releases/tag/2.1.32&quot;&gt;PHPStan 2.1.32&lt;/a&gt;, released back in November 2025, shipped with over 17 PHP 8.5-related changes. It was a massive community effort with many contributors. But I haven’t had the chance to sit down and write about all the interesting implementation details until now. It’s bigger than how it might seem on the first sight.&lt;/p&gt;
&lt;h2 id=&quot;%23%5Bnodiscard%5D-attribute&quot; tabindex=&quot;-1&quot;&gt;&lt;code&gt;#[NoDiscard]&lt;/code&gt; attribute &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#%23%5Bnodiscard%5D-attribute&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This is the feature I’m most excited about. It feels like it was made for static analysis, even though it’s really a runtime feature.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.php.net/rfc/marking_return_value_as_important&quot;&gt;&lt;code&gt;#[NoDiscard]&lt;/code&gt;&lt;/a&gt; lets you mark functions where ignoring the return value is a likely bug. Call such a function without doing anything with the result and PHP gives you a warning.&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-diff-php diff-highlight&quot;&gt;&lt;span class=&quot;token attribute&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;#[&lt;/span&gt;&lt;span class=&quot;token attribute-content&quot;&gt;&#92;&lt;span class=&quot;token attribute-class-name class-name&quot;&gt;NoDiscard&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;createDatabaseBackup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword return-type&quot;&gt;string&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$backupPath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// You probably wanted to do something with the path!&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;createDatabaseBackup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PHPStan reports: &lt;code&gt;Call to function createDatabaseBackup() on a separate line discards return value.&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This is great for API design. Think about immutable objects where methods like &lt;code&gt;withHeader()&lt;/code&gt; or &lt;code&gt;withStatus()&lt;/code&gt; return a new instance — calling them without using the result is always a mistake. I’ve seen this bug in real codebases way more times than I’d like to admit.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;#[NoDiscard]&lt;/code&gt; errors are &lt;strong&gt;non-ignorable&lt;/strong&gt; in PHPStan. The attribute is something you put in your code on purpose, so it doesn’t really make sense to then ignore the errors it produces. If you don’t want the errors, just remove the attribute.&lt;/p&gt;
&lt;h3 id=&quot;the-(void)-cast&quot; tabindex=&quot;-1&quot;&gt;The &lt;code&gt;(void)&lt;/code&gt; cast &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#the-(void)-cast&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;PHP 8.5 also introduces a &lt;code&gt;(void)&lt;/code&gt; cast to explicitly say “I know this returns something, and I don’t care”:&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-diff-php diff-highlight&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword type-declaration&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createDatabaseBackup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// No warning&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But here’s a gotcha: you can’t use &lt;code&gt;(void)&lt;/code&gt; inside other expressions:&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-diff-php diff-highlight&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$a&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword type-declaration&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;someFunction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Error!&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;var_dump&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword type-declaration&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;someFunction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Error!&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PHPStan reports: &lt;code&gt;The (void) cast cannot be used within an expression.&lt;/code&gt;&lt;/p&gt;
&lt;h3 id=&quot;phpstan-goes-a-step-further&quot; tabindex=&quot;-1&quot;&gt;PHPStan goes a step further &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#phpstan-goes-a-step-further&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;PHPStan doesn’t just warn about discarding &lt;code&gt;#[NoDiscard]&lt;/code&gt; return values — it also reports &lt;strong&gt;unnecessary&lt;/strong&gt; &lt;code&gt;(void)&lt;/code&gt; casts:&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-diff-php diff-highlight&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;canDiscard&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword return-type&quot;&gt;int&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword type-declaration&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;canDiscard&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// PHPStan reports this!&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PHPStan reports: &lt;code&gt;Call to function canDiscard() in (void) cast but function allows discarding return value.&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;I always try to clamp new language features from both sides — if you forget to use the return value, that’s an error. But if you use &lt;code&gt;(void)&lt;/code&gt; where there’s nothing to suppress, that’s also an error. This way you can’t go wrong either way.&lt;/p&gt;
&lt;h3 id=&quot;interaction-with-the-pipe-operator&quot; tabindex=&quot;-1&quot;&gt;Interaction with the pipe operator &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#interaction-with-the-pipe-operator&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;NoDiscard&lt;/code&gt; rules also handle pipe operator expressions. When they encounter a &lt;code&gt;Node&#92;Expr&#92;BinaryOp&#92;Pipe&lt;/code&gt;, they unwrap the right-hand side to get at the actual function being called:&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-diff-php diff-highlight&quot;&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;withSideEffects&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Error: return value discarded&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Big thanks to &lt;a href=&quot;https://github.com/DanielEScherzer&quot;&gt;Daniel Scherzer&lt;/a&gt; for the &lt;a href=&quot;https://github.com/phpstan/phpstan-src/pull/4253&quot;&gt;initial &lt;code&gt;#[NoDiscard]&lt;/code&gt; implementation&lt;/a&gt;!&lt;/p&gt;
&lt;h2 id=&quot;pipe-operator&quot; tabindex=&quot;-1&quot;&gt;Pipe operator &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#pipe-operator&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The pipe operator &lt;code&gt;|&amp;gt;&lt;/code&gt; passes the left-hand side as the sole argument to a callable on the right-hand side:&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-diff-php diff-highlight&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$result&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string double-quoted-string&quot;&gt;&quot;Hello World&quot;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;trim&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;strtolower&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;strlen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Instead of: strlen(strtolower(trim(&quot;Hello World&quot;)))&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What’s interesting is that most of PHPStan doesn’t actually see the pipe operator at all. The AST gets rewritten on-the-fly to traditional function calls. This is pretty neat because it means all existing rules — type checking, argument validation, return type inference — just work with pipe expressions for free. No need to go and teach every single rule about a new syntax. The transformation handles &lt;code&gt;FuncCall&lt;/code&gt;, &lt;code&gt;MethodCall&lt;/code&gt;, and &lt;code&gt;StaticCall&lt;/code&gt; with first-class callables, so &lt;code&gt;$x |&amp;gt; $obj-&amp;gt;method(...)&lt;/code&gt; simply becomes &lt;code&gt;$obj-&amp;gt;method($x)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The only rule that actually operates on the &lt;code&gt;Pipe&lt;/code&gt; node directly is &lt;a href=&quot;https://github.com/phpstan/phpstan-src/blob/2.2.x/src/Rules/Operators/PipeOperatorRule.php&quot;&gt;&lt;code&gt;PipeOperatorRule&lt;/code&gt;&lt;/a&gt;, which checks that the callable on the right side doesn’t accept its parameter by reference:&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-diff-php diff-highlight&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// PHPStan reports:&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// Parameter #1 $value of callable on the right side of pipe operator&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// is passed by reference.&lt;/span&gt;
&lt;span class=&quot;token variable&quot;&gt;$x&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One syntax gotcha: &lt;strong&gt;arrow functions need parentheses&lt;/strong&gt; when used in a pipe chain.&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-diff-php diff-highlight&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// This doesn&#39;t work:&lt;/span&gt;
&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$x&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$x&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// You need parentheses:&lt;/span&gt;
&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$x&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$x&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;clone-with&quot; tabindex=&quot;-1&quot;&gt;Clone with &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#clone-with&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We’ve all been writing &lt;code&gt;withFoo()&lt;/code&gt; methods on immutable value objects for years. The new &lt;code&gt;clone()&lt;/code&gt; with property overrides makes this pattern way cleaner:&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-diff-php diff-highlight&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;readonly&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name-definition class-name&quot;&gt;Response&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;__construct&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword type-declaration&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$statusCode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword type-declaration&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token variable&quot;&gt;$response&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Response&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;OK&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token variable&quot;&gt;$error&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;clone&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$response&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;statusCode&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;body&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;Internal Server Error&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PHPStan enforces the same rules for property assignments during cloning as it does for regular assignments — you can’t set &lt;code&gt;private&lt;/code&gt; properties from outside the class, you can’t assign &lt;code&gt;&#39;wrong&#39;&lt;/code&gt; to an &lt;code&gt;int&lt;/code&gt; property, and readonly properties can only be set from the appropriate scope.&lt;/p&gt;
&lt;p&gt;It also infers types from clone expressions. If you clone a generic &lt;code&gt;object&lt;/code&gt; and pass property overrides, PHPStan knows the result has those properties:&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-diff-php diff-highlight&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;doBar&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword type-hint&quot;&gt;object&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword return-type&quot;&gt;void&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token variable&quot;&gt;$cloned&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;clone&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;foo&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;bar&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// PHPStan knows: object&amp;amp;hasProperty(bar)&amp;amp;hasProperty(foo)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;new-functions%3A-array_first()-and-array_last()&quot; tabindex=&quot;-1&quot;&gt;New functions: &lt;code&gt;array_first()&lt;/code&gt; and &lt;code&gt;array_last()&lt;/code&gt; &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#new-functions%3A-array_first()-and-array_last()&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Finally, PHP gets proper functions for getting the first and last element of an array. Unlike &lt;code&gt;reset()&lt;/code&gt; and &lt;code&gt;end()&lt;/code&gt;, these don’t mess with the array’s internal pointer, so they’re truly pure functions.&lt;/p&gt;
&lt;p&gt;PHPStan’s &lt;a href=&quot;https://github.com/phpstan/phpstan-src/blob/2.2.x/src/Type/Php/ArrayFirstLastDynamicReturnTypeExtension.php&quot;&gt;&lt;code&gt;ArrayFirstLastDynamicReturnTypeExtension&lt;/code&gt;&lt;/a&gt; (&lt;a href=&quot;https://github.com/phpstan/phpstan-src/pull/4499&quot;&gt;#4499&lt;/a&gt;, thanks &lt;a href=&quot;https://github.com/canvural&quot;&gt;@canvural&lt;/a&gt;!) understands them precisely:&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-diff-php diff-highlight&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/** @param non-empty-array&amp;lt;int, string&gt; $nonEmpty */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;doFoo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword type-hint&quot;&gt;array&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$strings&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword type-hint&quot;&gt;array&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$nonEmpty&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword return-type&quot;&gt;void&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;array_first&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$strings&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;// string|null&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;array_first&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$nonEmpty&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;// string (no null - guaranteed non-empty!)&lt;/span&gt;

    &lt;span class=&quot;token function&quot;&gt;array_first&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;a&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;b&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;c&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// &#39;a&#39;|&#39;b&#39;|&#39;c&#39;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice how PHPStan narrows the return type based on whether the array can be empty — pass a &lt;code&gt;non-empty-array&lt;/code&gt; and you’ll never get &lt;code&gt;null&lt;/code&gt; back. We liked these functions so much we started using &lt;code&gt;array_last()&lt;/code&gt; inside PHPStan itself right away (&lt;a href=&quot;https://github.com/phpstan/phpstan-src/pull/4504&quot;&gt;#4504&lt;/a&gt;, thanks &lt;a href=&quot;https://github.com/staabm&quot;&gt;@staabm&lt;/a&gt;!).&lt;/p&gt;
&lt;h2 id=&quot;deprecated-casts&quot; tabindex=&quot;-1&quot;&gt;Deprecated casts &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#deprecated-casts&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;PHP 8.5 deprecates the non-canonical cast names:&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-diff-php diff-highlight&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword type-casting&quot;&gt;integer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$m&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Deprecated! Use (int)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword type-casting&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$m&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Deprecated! Use (bool)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;double&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$m&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;// Deprecated! Use (float)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;binary&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$m&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;// Deprecated! Use (string)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/phpstan/phpstan-src/blob/2.2.x/src/Rules/Cast/DeprecatedCastRule.php&quot;&gt;&lt;code&gt;DeprecatedCastRule&lt;/code&gt;&lt;/a&gt; reports these so you can clean them up before upgrading.&lt;/p&gt;
&lt;h2 id=&quot;deprecated-backtick-operator&quot; tabindex=&quot;-1&quot;&gt;Deprecated backtick operator &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#deprecated-backtick-operator&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The backtick operator for &lt;code&gt;shell_exec()&lt;/code&gt; is deprecated in PHP 8.5:&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-diff-php diff-highlight&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$output&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string backtick-quoted-string&quot;&gt;`ls -la`&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Deprecated!&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pssst, I have a secret for you. If you still have backticks in your source code, and PHPStan reports them as deprecated, try re-running your analysis but add &lt;code&gt;--fix&lt;/code&gt; to the CLI options. You’ll be surprised what happens. I’ll have more to say about this later.&lt;/p&gt;
&lt;h2 id=&quot;more-php-8.5-goodies&quot; tabindex=&quot;-1&quot;&gt;More PHP 8.5 goodies &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#more-php-8.5-goodies&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here are some other notable changes PHPStan now supports:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;#[Override]&lt;/code&gt; on properties&lt;/strong&gt; — You can now use the &lt;code&gt;#[Override]&lt;/code&gt; attribute on properties to assert that the property overrides a parent class property. There’s a new configuration option &lt;code&gt;checkMissingOverridePropertyAttribute: true&lt;/code&gt; which can enforce them, similar to &lt;a href=&quot;/config-reference#checkmissingoverridemethodattribute&quot;&gt;the one for methods&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Asymmetric visibility for static properties&lt;/strong&gt; — Previously only instance properties supported different read/write visibility. Now static properties can too.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Closure::getCurrent()&lt;/code&gt;&lt;/strong&gt; — Returns the current closure, enabling recursion without assigning to a variable first. PHPStan will know the correct signature of the closure if you fetch it with this method.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Closures and first-class callables in constant expressions&lt;/strong&gt; — Static closures can now be used in constant expressions like property defaults, constants, and attribute parameters. They must be static and can’t capture variables.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Casts in constant expressions&lt;/strong&gt; — You can now use type casts in constant initializers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Support for deprecated traits&lt;/strong&gt; — The &lt;code&gt;#[Deprecated]&lt;/code&gt; attribute now works on traits.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;FILTER_THROW_ON_FAILURE&lt;/code&gt;&lt;/strong&gt; — New flag for &lt;code&gt;filter_var()&lt;/code&gt; (&lt;a href=&quot;https://github.com/phpstan/phpstan-src/pull/4495&quot;&gt;#4495&lt;/a&gt;, thanks &lt;a href=&quot;https://github.com/canvural&quot;&gt;@canvural&lt;/a&gt;!).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Global constants support attributes&lt;/strong&gt; — Constants can now have attributes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;PHP_BUILD_DATE&lt;/code&gt; type&lt;/strong&gt; (&lt;a href=&quot;https://github.com/phpstan/phpstan-src/pull/4468&quot;&gt;#4468&lt;/a&gt;, thanks &lt;a href=&quot;https://github.com/staabm&quot;&gt;@staabm&lt;/a&gt;!).&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;Do you like PHPStan and use it every day? &lt;a href=&quot;/sponsor&quot;&gt;&lt;strong&gt;Consider supporting further development of PHPStan&lt;/strong&gt;&lt;/a&gt;. I’d really appreciate it!&lt;/p&gt;
</content>
		</entry>
	
		
		<entry>
			<title>Restricted Usage Extensions - You Don&#39;t Always Need a Custom Rule</title>
			<link href="https://phpstan.org/blog/restricted-usage-extensions-you-dont-always-need-custom-rule"/>
			<updated>2025-04-27T00:00:00Z</updated>
			<id>https://phpstan.org/blog/restricted-usage-extensions-you-dont-always-need-custom-rule</id>
			<content type="html" xml:base="https://phpstan.org">&lt;p&gt;Recently I’ve wanted to implement support for the &lt;code&gt;@internal&lt;/code&gt; PHPDoc tag. The good news was there was already a similar set of rules in &lt;a href=&quot;https://github.com/phpstan/phpstan-deprecation-rules&quot;&gt;phpstan-deprecation-rules&lt;/a&gt; for the &lt;code&gt;@deprecated&lt;/code&gt; tag. The bad news was the logic around &lt;code&gt;@deprecated&lt;/code&gt; was hardcoded there, meaning I’d have to copy and adjust all the rules.&lt;/p&gt;
&lt;p&gt;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 &lt;a href=&quot;https://github.com/DaveLiddament/php-language-extensions#namespacevisibility&quot;&gt;&lt;code&gt;#[NamespaceVisibility]&lt;/code&gt;&lt;/a&gt; or &lt;a href=&quot;https://github.com/DaveLiddament/php-language-extensions#friend&quot;&gt;&lt;code&gt;#[Friend]&lt;/code&gt;&lt;/a&gt;, it should not be their job to figure out all the rules needed for a comprehensive coverage of these restrictions.&lt;/p&gt;
&lt;p&gt;Take methods for example: Everyone would implement a rule for a traditional method call like &lt;code&gt;$foo-&amp;gt;doBar(1, 2, 3)&lt;/code&gt;, but only a few would remember that &lt;a href=&quot;https://wiki.php.net/rfc/first_class_callable_syntax&quot;&gt;first-class callables&lt;/a&gt; like &lt;code&gt;$foo-&amp;gt;doBar(...)&lt;/code&gt; should be covered as well.&lt;/p&gt;
&lt;p&gt;Or class names. The new extension currently &lt;a href=&quot;https://github.com/phpstan/phpstan-src/blob/ecfc5417bb2bd2f9f478f0c005b10ab1937cf149/src/Rules/ClassNameUsageLocation.php#L18-L43&quot;&gt;covers 26 locations&lt;/a&gt; where a class name can occur in PHP code or &lt;a href=&quot;https://phpstan.org/writing-php-code/phpdocs-basics&quot;&gt;PHPDocs&lt;/a&gt;. 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.&lt;/p&gt;
&lt;p&gt;Besides taking advantage of the new extensions to implement the &lt;a href=&quot;https://github.com/phpstan/phpstan-src/tree/2.2.x/src/Rules/InternalTag&quot;&gt;&lt;code&gt;@internal&lt;/code&gt; tag rules&lt;/a&gt;, I went back to phpstan-deprecation-rules and &lt;a href=&quot;https://github.com/phpstan/phpstan-deprecation-rules/compare/96f93574dd20a293df14700e84502123103178d7...9d8e7d4e32711715ad78a1fb6ec368df9af01fdf&quot;&gt;refactored&lt;/a&gt; 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 &lt;a href=&quot;/blog/what-is-bleeding-edge&quot;&gt;bleeding edge&lt;/a&gt; enabled, it will report deprecated class names in more places.&lt;/p&gt;
&lt;p&gt;The Restricted Usage Extensions were released in &lt;a href=&quot;https://github.com/phpstan/phpstan/releases/tag/2.1.13&quot;&gt;PHPStan 2.1.13&lt;/a&gt;. Head over to the &lt;a href=&quot;/developing-extensions/restricted-usage-extensions&quot;&gt;developer guide&lt;/a&gt; to learn how to use them!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Do you like PHPStan and use it every day? &lt;a href=&quot;/sponsor&quot;&gt;&lt;strong&gt;Consider supporting further development of PHPStan&lt;/strong&gt;&lt;/a&gt;. I’d really appreciate it!&lt;/p&gt;
</content>
		</entry>
	
		
		<entry>
			<title>PHPStan 2.1: Support For PHP 8.4&#39;s Property Hooks, and More!</title>
			<link href="https://phpstan.org/blog/phpstan-2-1-support-for-php-8-4-property-hooks-more"/>
			<updated>2024-12-31T00:00:00Z</updated>
			<id>https://phpstan.org/blog/phpstan-2-1-support-for-php-8-4-property-hooks-more</id>
			<content type="html" xml:base="https://phpstan.org">&lt;p&gt;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 &lt;a href=&quot;https://github.com/nikic/PHP-Parser/releases/tag/v5.0.0&quot;&gt;PHP-Parser v5&lt;/a&gt;. In order to upgrade to PHP-Parser v5, I had to release PHPStan 2.0. &lt;a href=&quot;/blog/phpstan-2-0-released-level-10-elephpants&quot;&gt;Which I did on November 11th&lt;/a&gt;, 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.&lt;/p&gt;
&lt;p&gt;Today’s &lt;a href=&quot;https://github.com/phpstan/phpstan/releases/tag/2.1.0&quot;&gt;PHPStan 2.1&lt;/a&gt; brings full understanding of flagship PHP 8.4 features like &lt;a href=&quot;https://wiki.php.net/rfc/property-hooks&quot;&gt;property hooks&lt;/a&gt;, &lt;a href=&quot;https://wiki.php.net/rfc/asymmetric-visibility-v2&quot;&gt;asymmetric visibility&lt;/a&gt;, and the &lt;a href=&quot;https://wiki.php.net/rfc/deprecated_attribute&quot;&gt;&lt;code&gt;#[Deprecated]&lt;/code&gt; attribute&lt;/a&gt;. As usual I’m going to describe what considerations went into supporting these language features in PHPStan.&lt;/p&gt;
&lt;h2 id=&quot;property-hooks&quot; tabindex=&quot;-1&quot;&gt;Property hooks &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#property-hooks&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This is a massive and complex new language feature which is obvious just by looking at the length of &lt;a href=&quot;https://wiki.php.net/rfc/property-hooks&quot;&gt;the RFC&lt;/a&gt;. 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 &lt;a href=&quot;https://github.com/phpstan/phpstan/issues/12336&quot;&gt;around 32 todos left&lt;/a&gt;. They are not necessary enough to be included in this initial release, but it’d be nice to have them.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/r/041c903d-87c4-4de1-97aa-12e5c80975e5&quot;&gt;Missing return&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/r/1d82e9b9-0e10-4b3b-a444-f5ad65523b48&quot;&gt;Returned type&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/r/e5d7d8c8-4133-4f3a-b003-7c877931d152&quot;&gt;Existence of parameter types&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/r/0f0103a7-6226-4dbb-894b-3462b831ab8c&quot;&gt;Compatibility of PHPDocs with parameter types&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/r/611182a8-ac7f-4155-ab1a-d7553511282e&quot;&gt;Syntax errors in PHPDocs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/r/02f64b10-6579-4cbc-b1b8-939a32d89f13&quot;&gt;Unknown &lt;code&gt;@phpstan-&lt;/code&gt; tags in PHPDocs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/blog/bring-your-exceptions-under-control&quot;&gt;Checked exceptions thrown in the body must either be handled or documented with &lt;code&gt;@throws&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/r/b721ee1f-d2df-4d28-b84c-fb072f19b97a&quot;&gt;Rule for unused private properties must ignore property access inside hooks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/r/7083a3bb-fc8b-4e2b-aa68-722a87e3a6e6&quot;&gt;Attributes above property hooks&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Additionally, there are new special rules for property hooks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Backing value of non-virtual property &lt;a href=&quot;/r/386f6fa6-2c6e-4e22-818f-47d35fa1ee28&quot;&gt;must be read&lt;/a&gt; in get hook&lt;/li&gt;
&lt;li&gt;Backing value of non-virtual property &lt;a href=&quot;/r/ce9480ce-fdf1-4ff7-9377-105ca7fa4313&quot;&gt;must be always assigned&lt;/a&gt; in set hook&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Set hook parameter type can be wider than the actual property type so PHPStan also has to be aware of the types it &lt;a href=&quot;/r/5b12238d-8206-4194-a30c-19ad4071bbf9&quot;&gt;allows to assign to properties in and out of hooks&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-diff-php diff-highlight&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name-definition class-name&quot;&gt;Foo&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword type-declaration&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$i&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		get &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword type-declaration&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token keyword type-declaration&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token variable&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// string not allowed&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token variable&quot;&gt;$f&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Foo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token variable&quot;&gt;$f&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;rand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;10&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;foo&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// string allowed&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Property hooks also bring properties that &lt;a href=&quot;/r/2494f530-17c0-4da7-8d91-263d171766a9&quot;&gt;can only be read, or can only be written&lt;/a&gt;, to the language:&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-diff-php diff-highlight&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name-definition class-name&quot;&gt;Foo&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword type-declaration&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$i&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; get&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword type-declaration&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$j&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; set&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name type-declaration&quot;&gt;Foo&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$f&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword return-type&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token variable&quot;&gt;$f&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;42&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// invalid&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$f&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// invalid	&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And my final remark is that access to properties can now be &lt;a href=&quot;/r/39b8d6a8-99db-4bfc-a99b-9af4b214abee&quot;&gt;expected to throw any exception&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-diff-php diff-highlight&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name-definition class-name&quot;&gt;Foo&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword type-declaration&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$i&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; get&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name type-declaration&quot;&gt;Foo&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$f&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword return-type&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$f&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;MyCustomException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// dead catch on PHP 8.3-, but not on 8.4+&lt;/span&gt;
		
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;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 &lt;code&gt;private&lt;/code&gt; or &lt;code&gt;final&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Hooks can be documented with &lt;code&gt;@throws&lt;/code&gt; PHPDoc tag which &lt;a href=&quot;/blog/precise-try-catch-finally-analysis&quot;&gt;improves analysis of try-catch blocks&lt;/a&gt;, same as with functions and methods.&lt;/p&gt;
&lt;p&gt;I also posted a &lt;a href=&quot;https://github.com/phpstan/phpstan/discussions/12337&quot;&gt;community update&lt;/a&gt; which goes into depth what you should do if you want to support property hooks in your custom rules.&lt;/p&gt;
&lt;p&gt;A huge thanks to &lt;a href=&quot;https://github.com/kukulich&quot;&gt;Jarda Hanslík&lt;/a&gt; for &lt;a href=&quot;https://github.com/Roave/BetterReflection/pull/1462&quot;&gt;initial property hooks support&lt;/a&gt; in BetterReflection, and for &lt;a href=&quot;https://github.com/Roave/BetterReflection/pull/1465&quot;&gt;many&lt;/a&gt; &lt;a href=&quot;https://github.com/Roave/BetterReflection/pull/1466&quot;&gt;many&lt;/a&gt; &lt;a href=&quot;https://github.com/Roave/BetterReflection/pull/1470&quot;&gt;subsequent&lt;/a&gt; &lt;a href=&quot;https://github.com/Roave/BetterReflection/pull/1471&quot;&gt;fixes&lt;/a&gt; for bugs I found 😅&lt;/p&gt;
&lt;p&gt;And also a huge thanks to &lt;a href=&quot;https://github.com/nikic&quot;&gt;Nikita Popov&lt;/a&gt;, not just for his work on PHP-Parser in general, but also for merging a &lt;a href=&quot;https://github.com/nikic/PHP-Parser/pull/1049&quot;&gt;couple&lt;/a&gt; of &lt;a href=&quot;https://github.com/nikic/PHP-Parser/pull/1051&quot;&gt;fixes&lt;/a&gt; I made, and also for fixing a &lt;a href=&quot;https://github.com/nikic/PHP-Parser/issues/1053&quot;&gt;couple&lt;/a&gt; of other &lt;a href=&quot;https://github.com/nikic/PHP-Parser/issues/1050&quot;&gt;issues&lt;/a&gt; I reported.&lt;/p&gt;
&lt;h2 id=&quot;asymmetric-visibility&quot; tabindex=&quot;-1&quot;&gt;Asymmetric visibility &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#asymmetric-visibility&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;This feature is much &lt;a href=&quot;https://wiki.php.net/rfc/asymmetric-visibility-v2&quot;&gt;easier to grasp&lt;/a&gt; than property hooks, but also comes with a few gotchas.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Property that’s public-readable or protected-readable, but only privately writable, is &lt;a href=&quot;/r/eb32ab7b-1f3d-4abf-898a-b22523e88e8c&quot;&gt;implicitly final and cannot be overridden&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Acquiring reference to a property follows write visibility, not read visibility, so I added &lt;a href=&quot;/r/add82dc4-8cd2-4d5a-bed4-05f8d51e8cfa&quot;&gt;a new rule for that&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Similarly to property hooks, I also have a few nice-to-have todos &lt;a href=&quot;https://github.com/phpstan/phpstan/issues/12347&quot;&gt;left for later&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;%23%5Bdeprecated%5D-attribute&quot; tabindex=&quot;-1&quot;&gt;&lt;code&gt;#[Deprecated]&lt;/code&gt; attribute &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#%23%5Bdeprecated%5D-attribute&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.php.net/rfc/deprecated_attribute&quot;&gt;This attribute&lt;/a&gt; allows the PHP engine &lt;a href=&quot;https://3v4l.org/MEJTq&quot;&gt;trigger deprecated warnings&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;Nevertheless, you can use it in PHPStan 2.1 in tandem with &lt;a href=&quot;https://github.com/phpstan/phpstan-deprecation-rules&quot;&gt;phpstan-deprecation-rules&lt;/a&gt; to mark deprecated code and have it reported when used.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Do you like PHPStan and use it every day? &lt;a href=&quot;/sponsor&quot;&gt;&lt;strong&gt;Consider sponsoring&lt;/strong&gt; further development of PHPStan on GitHub Sponsors and also &lt;strong&gt;subscribe to PHPStan Pro&lt;/strong&gt;&lt;/a&gt;! I’d really appreciate it!&lt;/p&gt;
</content>
		</entry>
	
		
		<entry>
			<title>Upgrading from PHPStan 1.x to 2.0</title>
			<link href="https://phpstan.org/blog/upgrading-from-phpstan-1-to-2"/>
			<updated>2024-11-11T00:00:00Z</updated>
			<id>https://phpstan.org/blog/upgrading-from-phpstan-1-to-2</id>
			<content type="html" xml:base="https://phpstan.org">&lt;p&gt;This is the upgrading guide for &lt;a href=&quot;/blog/phpstan-2-0-released-level-10-elephpants&quot;&gt;PHPStan 2.0&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;php-version-requirements&quot; tabindex=&quot;-1&quot;&gt;PHP version requirements &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#php-version-requirements&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;PHPStan now requires PHP 7.4 or newer to run.&lt;/p&gt;
&lt;h2 id=&quot;upgrading-guide-for-end-users&quot; tabindex=&quot;-1&quot;&gt;Upgrading guide for end users &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#upgrading-guide-for-end-users&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The best way to get ready for upgrade to PHPStan 2.0 is to update to the &lt;strong&gt;latest PHPStan 1.12 release&lt;/strong&gt;
and enable &lt;a href=&quot;/blog/what-is-bleeding-edge&quot;&gt;&lt;strong&gt;Bleeding Edge&lt;/strong&gt;&lt;/a&gt;. This will enable the new rules and behaviours that 2.0 turns on for all users.&lt;/p&gt;
&lt;p&gt;Also make sure to install and enable &lt;a href=&quot;https://github.com/phpstan/phpstan-deprecation-rules&quot;&gt;&lt;code&gt;phpstan/phpstan-deprecation-rules&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Once you get to a green build with no deprecations showed on latest PHPStan 1.12.x with Bleeding Edge enabled, you can update all your related PHPStan dependencies to 2.0 in &lt;code&gt;composer.json&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-diff-json diff-highlight&quot;&gt;&lt;span class=&quot;token property&quot;&gt;&quot;require-dev&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;phpstan/phpstan&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;^2.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;phpstan/phpstan-deprecation-rules&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;^2.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;phpstan/phpstan-doctrine&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;^2.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;phpstan/phpstan-nette&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;^2.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;phpstan/phpstan-phpunit&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;^2.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;phpstan/phpstan-strict-rules&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;^2.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;phpstan/phpstan-symfony&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;^2.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;phpstan/phpstan-webmozart-assert&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;^2.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    ...
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Don’t forget to update &lt;a href=&quot;/user-guide/extension-library&quot;&gt;3rd party PHPStan extensions&lt;/a&gt; as well.&lt;/p&gt;
&lt;p&gt;After changing your &lt;code&gt;composer.json&lt;/code&gt;, run &lt;code&gt;composer update &#39;phpstan/*&#39; -W&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It’s up to you whether you go through the new reported errors or if you just put them all to the &lt;a href=&quot;/user-guide/baseline&quot;&gt;baseline&lt;/a&gt; ;) Everyone who’s on PHPStan 1.12 should be able to upgrade to PHPStan 2.0.&lt;/p&gt;
&lt;h3 id=&quot;noteworthy-changes-to-code-analysis&quot; tabindex=&quot;-1&quot;&gt;Noteworthy changes to code analysis &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#noteworthy-changes-to-code-analysis&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/blog/enhancements-in-handling-parameters-passed-by-reference&quot;&gt;&lt;strong&gt;Enhancements in handling parameters passed by reference&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/blog/phpstan-1-10-comes-with-lie-detector#validate-inline-phpdoc-%40var-tag-type&quot;&gt;&lt;strong&gt;Validate inline PHPDoc &lt;code&gt;@var&lt;/code&gt; tag type&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/blog/phpstan-1-9-0-with-phpdoc-asserts-list-type#list-type&quot;&gt;&lt;strong&gt;List type enforced&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Always &lt;code&gt;true&lt;/code&gt; conditions always reported&lt;/strong&gt;: previously reported only with phpstan-strict-rules, this is now always reported.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;removed-option-checkmissingiterablevaluetype&quot; tabindex=&quot;-1&quot;&gt;Removed option &lt;code&gt;checkMissingIterableValueType&lt;/code&gt; &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#removed-option-checkmissingiterablevaluetype&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;It’s strongly recommended to add the missing array typehints.&lt;/p&gt;
&lt;p&gt;If you want to continue ignoring missing typehints from arrays, add &lt;code&gt;missingType.iterableValue&lt;/code&gt; error identifier to your &lt;code&gt;ignoreErrors&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-neon&quot;&gt;&lt;code class=&quot;language-diff-neon diff-highlight&quot;&gt;&lt;span class=&quot;token key property&quot;&gt;parameters&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
	&lt;span class=&quot;token key property&quot;&gt;ignoreErrors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;
			&lt;span class=&quot;token key property&quot;&gt;identifier&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token literal string&quot;&gt;missingType.iterableValue&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;removed-option-checkgenericclassinnongenericobjecttype&quot; tabindex=&quot;-1&quot;&gt;Removed option &lt;code&gt;checkGenericClassInNonGenericObjectType&lt;/code&gt; &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#removed-option-checkgenericclassinnongenericobjecttype&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;It’s strongly recommended to add the missing generic typehints.&lt;/p&gt;
&lt;p&gt;If you want to continue ignoring missing typehints from generics, add &lt;code&gt;missingType.generics&lt;/code&gt; error identifier to your &lt;code&gt;ignoreErrors&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-neon&quot;&gt;&lt;code class=&quot;language-diff-neon diff-highlight&quot;&gt;&lt;span class=&quot;token key property&quot;&gt;parameters&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
	&lt;span class=&quot;token key property&quot;&gt;ignoreErrors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;
			&lt;span class=&quot;token key property&quot;&gt;identifier&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token literal string&quot;&gt;missingType.generics&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;removed-checkalwaystrue*-options&quot; tabindex=&quot;-1&quot;&gt;Removed &lt;code&gt;checkAlwaysTrue*&lt;/code&gt; options &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#removed-checkalwaystrue*-options&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;These options have been removed because PHPStan now always behaves as if these were set to &lt;code&gt;true&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;checkAlwaysTrueCheckTypeFunctionCall&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;checkAlwaysTrueInstanceof&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;checkAlwaysTrueStrictComparison&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;checkAlwaysTrueLooseComparison&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;removed-option-excludes_analyse&quot; tabindex=&quot;-1&quot;&gt;Removed option &lt;code&gt;excludes_analyse&lt;/code&gt; &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#removed-option-excludes_analyse&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;It has been replaced with &lt;a href=&quot;/user-guide/ignoring-errors#excluding-whole-files&quot;&gt;&lt;code&gt;excludePaths&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;paths-in-excludepaths-and-ignoreerrors-have-to-be-a-valid-file-path-or-a-fnmatch-pattern&quot; tabindex=&quot;-1&quot;&gt;Paths in &lt;code&gt;excludePaths&lt;/code&gt; and &lt;code&gt;ignoreErrors&lt;/code&gt; have to be a valid file path or a fnmatch pattern &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#paths-in-excludepaths-and-ignoreerrors-have-to-be-a-valid-file-path-or-a-fnmatch-pattern&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If you are excluding a file path that might not exist but you still want to have it in &lt;code&gt;excludePaths&lt;/code&gt;, append &lt;code&gt;(?)&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-neon&quot;&gt;&lt;code class=&quot;language-diff-neon diff-highlight&quot;&gt;&lt;span class=&quot;token key property&quot;&gt;parameters&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
	&lt;span class=&quot;token key property&quot;&gt;excludePaths&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token literal string&quot;&gt;tests/*/data/*&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token literal string&quot;&gt;src/broken&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token literal string&quot;&gt;node_modules&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token literal string&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# optional path, might not exist&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you have the same situation in &lt;code&gt;ignoreErrors&lt;/code&gt; (ignoring an error in a path that might not exist), use &lt;code&gt;reportUnmatchedIgnoredErrors: false&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-neon&quot;&gt;&lt;code class=&quot;language-diff-neon diff-highlight&quot;&gt;&lt;span class=&quot;token key property&quot;&gt;parameters&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
	&lt;span class=&quot;token key property&quot;&gt;reportUnmatchedIgnoredErrors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Appending &lt;code&gt;(?)&lt;/code&gt; in &lt;code&gt;ignoreErrors&lt;/code&gt; is not supported.&lt;/p&gt;
&lt;h3 id=&quot;changes-in-1st-party-phpstan-extensions&quot; tabindex=&quot;-1&quot;&gt;Changes in 1st party PHPStan extensions &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#changes-in-1st-party-phpstan-extensions&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/phpstan/phpstan-doctrine&quot;&gt;phpstan-doctrine&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;Removed config parameter &lt;code&gt;searchOtherMethodsForQueryBuilderBeginning&lt;/code&gt; (extension now behaves as when this was set to &lt;code&gt;true&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Removed config parameter &lt;code&gt;queryBuilderFastAlgorithm&lt;/code&gt; (extension now behaves as when this was set to &lt;code&gt;false&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/phpstan/phpstan-symfony&quot;&gt;phpstan-symfony&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;Removed legacy options with &lt;code&gt;_&lt;/code&gt; in the name&lt;/li&gt;
&lt;li&gt;&lt;code&gt;container_xml_path&lt;/code&gt; -&amp;gt; use &lt;code&gt;containerXmlPath&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;constant_hassers&lt;/code&gt; -&amp;gt; use &lt;code&gt;constantHassers&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;console_application_loader&lt;/code&gt; -&amp;gt; use &lt;code&gt;consoleApplicationLoader&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;minor-backward-compatibility-breaks&quot; tabindex=&quot;-1&quot;&gt;Minor backward compatibility breaks &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#minor-backward-compatibility-breaks&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Removed unused config parameter &lt;code&gt;cache.nodesByFileCountMax&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Removed unused config parameter &lt;code&gt;memoryLimitFile&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Removed unused feature toggle &lt;code&gt;disableRuntimeReflectionProvider&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Removed unused config parameter &lt;code&gt;staticReflectionClassNamePatterns&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;fixerTmpDir&lt;/code&gt; config parameter, use &lt;code&gt;pro.tmpDir&lt;/code&gt; instead&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;tempResultCachePath&lt;/code&gt; config parameter, use &lt;code&gt;resultCachePath&lt;/code&gt; instead&lt;/li&gt;
&lt;li&gt;&lt;code&gt;additionalConfigFiles&lt;/code&gt; config parameter must be a list&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;upgrading-guide-for-extension-developers&quot; tabindex=&quot;-1&quot;&gt;Upgrading guide for extension developers &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#upgrading-guide-for-extension-developers&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Please switch to PHPStan 2.0 in a new major version of your extension. It’s not feasible to try to support both PHPStan 1.x and PHPStan 2.x with the same extension code.&lt;/p&gt;
&lt;p&gt;You can definitely get closer to supporting PHPStan 2.0 without increasing major version by solving reported deprecations and other issues by analysing your extension code with PHPStan &amp;amp; phpstan-deprecation-rules &amp;amp; Bleeding Edge, but the final leap and solving backward incompatibilities should be done by requiring &lt;code&gt;&amp;quot;phpstan/phpstan&amp;quot;: &amp;quot;^2.0&amp;quot;&lt;/code&gt; in your &lt;code&gt;composer.json&lt;/code&gt;, and releasing a new major version.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;phpstan-now-uses-nikic%2Fphp-parser-v5&quot; tabindex=&quot;-1&quot;&gt;PHPStan now uses nikic/php-parser v5 &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#phpstan-now-uses-nikic%2Fphp-parser-v5&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;See &lt;a href=&quot;https://github.com/nikic/PHP-Parser/blob/master/UPGRADE-5.0.md&quot;&gt;UPGRADING&lt;/a&gt; guide for PHP-Parser.&lt;/p&gt;
&lt;p&gt;The most notable change is how &lt;code&gt;throw&lt;/code&gt; statement is represented. Previously, &lt;code&gt;throw&lt;/code&gt; statements like &lt;code&gt;throw $e;&lt;/code&gt; were represented using the &lt;code&gt;Stmt&#92;Throw_&lt;/code&gt; class, while uses inside other expressions (such as &lt;code&gt;$x ?? throw $e&lt;/code&gt;) used the &lt;code&gt;Expr&#92;Throw_&lt;/code&gt; class.&lt;/p&gt;
&lt;p&gt;Now, &lt;code&gt;throw $e;&lt;/code&gt; is represented as a &lt;code&gt;Stmt&#92;Expression&lt;/code&gt; that contains an &lt;code&gt;Expr&#92;Throw_&lt;/code&gt;. The
&lt;code&gt;Stmt&#92;Throw_&lt;/code&gt; class has been removed.&lt;/p&gt;
&lt;h3 id=&quot;phpstan-now-uses-phpstan%2Fphpdoc-parser-v2&quot; tabindex=&quot;-1&quot;&gt;PHPStan now uses phpstan/phpdoc-parser v2 &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#phpstan-now-uses-phpstan%2Fphpdoc-parser-v2&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;See &lt;a href=&quot;https://github.com/phpstan/phpdoc-parser/blob/2.0.x/UPGRADING.md&quot;&gt;UPGRADING&lt;/a&gt; guide for phpstan/phpdoc-parser.&lt;/p&gt;
&lt;h3 id=&quot;returning-plain-strings-as-errors-no-longer-supported%2C-use-ruleerrorbuilder&quot; tabindex=&quot;-1&quot;&gt;Returning plain strings as errors no longer supported, use RuleErrorBuilder &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#returning-plain-strings-as-errors-no-longer-supported%2C-use-ruleerrorbuilder&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Identifiers are also required in custom rules.&lt;/p&gt;
&lt;p&gt;Learn more: &lt;a href=&quot;/blog/using-rule-error-builder&quot;&gt;Using RuleErrorBuilder to enrich reported errors in custom rules&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Before&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-diff-php diff-highlight&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;My error&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;After&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-diff-php diff-highlight&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token class-name static-context&quot;&gt;RuleErrorBuilder&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;My error&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;identifier&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;my.error&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;deprecate-various-instanceof-*type-in-favour-of-new-methods-on-type-interface&quot; tabindex=&quot;-1&quot;&gt;Deprecate various &lt;code&gt;instanceof *Type&lt;/code&gt; in favour of new methods on &lt;code&gt;Type&lt;/code&gt; interface &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#deprecate-various-instanceof-*type-in-favour-of-new-methods-on-type-interface&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Learn more: &lt;a href=&quot;/blog/why-is-instanceof-type-wrong-and-getting-deprecated&quot;&gt;Why Is instanceof *Type Wrong and Getting Deprecated?&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;removed-deprecated-parametersacceptorselector%3A%3Aselectsingle()&quot; tabindex=&quot;-1&quot;&gt;Removed deprecated &lt;code&gt;ParametersAcceptorSelector::selectSingle()&lt;/code&gt; &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#removed-deprecated-parametersacceptorselector%3A%3Aselectsingle()&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Use &lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Reflection.ParametersAcceptorSelector.html#_selectFromArgs&quot;&gt;&lt;code&gt;ParametersAcceptorSelector::selectFromArgs()&lt;/code&gt;&lt;/a&gt; instead. It should be used in most places where &lt;code&gt;selectSingle()&lt;/code&gt; was previously used, like dynamic return type extensions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Before&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-diff-php diff-highlight&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$defaultReturnType&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name static-context&quot;&gt;ParametersAcceptorSelector&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;selectSingle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$functionReflection&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getVariants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getReturnType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;After&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-diff-php diff-highlight&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$defaultReturnType&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name static-context&quot;&gt;ParametersAcceptorSelector&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;selectFromArgs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token variable&quot;&gt;$scope&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token variable&quot;&gt;$functionCall&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getArgs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token variable&quot;&gt;$functionReflection&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getVariants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getReturnType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you’re analysing function or method body itself and you’re using one of the following methods, ask for &lt;code&gt;getParameters()&lt;/code&gt; and &lt;code&gt;getReturnType()&lt;/code&gt; directly on the reflection object:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Node.InClassMethodNode.html&quot;&gt;InClassMethodNode::getMethodReflection()&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Node.InFunctionNode.html&quot;&gt;InFunctionNode::getFunctionReflection()&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Node.FunctionReturnStatementsNode.html&quot;&gt;FunctionReturnStatementsNode::getFunctionReflection()&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Node.MethodReturnStatementsNode.html&quot;&gt;MethodReturnStatementsNode::getMethodReflection()&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.Scope.html#_getFunction&quot;&gt;Scope::getFunction()&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Before&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-diff-php diff-highlight&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$function&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$node&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFunctionReflection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token variable&quot;&gt;$returnType&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name static-context&quot;&gt;ParametersAcceptorSelector&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;selectSingle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$function&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getVariants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getReturnType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;After&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-diff-php diff-highlight&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$returnType&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$node&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFunctionReflection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getReturnType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;changed-typespecifier%3A%3Acreate()-and-specifiedtypes-constructor-parameters&quot; tabindex=&quot;-1&quot;&gt;Changed &lt;code&gt;TypeSpecifier::create()&lt;/code&gt; and &lt;code&gt;SpecifiedTypes&lt;/code&gt; constructor parameters &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#changed-typespecifier%3A%3Acreate()-and-specifiedtypes-constructor-parameters&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.TypeSpecifier.html#_create&quot;&gt;&lt;code&gt;PHPStan&#92;Analyser&#92;TypeSpecifier::create()&lt;/code&gt;&lt;/a&gt; now accepts (all parameters are required):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Expr $expr&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Type $type&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TypeSpecifierContext $context&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Scope $scope&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you want to change &lt;code&gt;$overwrite&lt;/code&gt; or &lt;code&gt;$rootExpr&lt;/code&gt; (previous parameters also used to be accepted by this method), call &lt;code&gt;setAlwaysOverwriteTypes()&lt;/code&gt; and &lt;code&gt;setRootExpr()&lt;/code&gt; on &lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.SpecifiedTypes.html&quot;&gt;&lt;code&gt;SpecifiedTypes&lt;/code&gt;&lt;/a&gt; (object returned by &lt;code&gt;TypeSpecifier::create()&lt;/code&gt;). These methods return a new object (SpecifiedTypes is immutable).&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.SpecifiedTypes.html&quot;&gt;&lt;code&gt;SpecifiedTypes&lt;/code&gt;&lt;/a&gt; constructor now accepts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;array $sureTypes&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;array $sureNotTypes&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you want to change &lt;code&gt;$overwrite&lt;/code&gt; or &lt;code&gt;$rootExpr&lt;/code&gt; (previous parameters also used to be accepted by the constructor), call &lt;code&gt;setAlwaysOverwriteTypes()&lt;/code&gt; and &lt;code&gt;setRootExpr()&lt;/code&gt;. These methods return a new object (SpecifiedTypes is immutable).&lt;/p&gt;
&lt;h3 id=&quot;constantarraytype-no-longer-extends-arraytype&quot; tabindex=&quot;-1&quot;&gt;&lt;code&gt;ConstantArrayType&lt;/code&gt; no longer extends &lt;code&gt;ArrayType&lt;/code&gt; &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#constantarraytype-no-longer-extends-arraytype&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Type::getArrays()&lt;/code&gt; now returns &lt;code&gt;list&amp;lt;ArrayType|ConstantArrayType&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Using &lt;code&gt;$type instanceof ArrayType&lt;/code&gt; is &lt;a href=&quot;/blog/why-is-instanceof-type-wrong-and-getting-deprecated&quot;&gt;being deprecated anyway&lt;/a&gt; so the impact of this change should be minimal.&lt;/p&gt;
&lt;h3 id=&quot;changed-typespecifier%3A%3Aspecifytypesincondition()&quot; tabindex=&quot;-1&quot;&gt;Changed &lt;code&gt;TypeSpecifier::specifyTypesInCondition()&lt;/code&gt; &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#changed-typespecifier%3A%3Aspecifytypesincondition()&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This method now longer accepts &lt;code&gt;Expr $rootExpr&lt;/code&gt;. If you want to change it, call &lt;code&gt;setRootExpr()&lt;/code&gt; on &lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.SpecifiedTypes.html&quot;&gt;&lt;code&gt;SpecifiedTypes&lt;/code&gt;&lt;/a&gt; (object returned by &lt;code&gt;TypeSpecifier::specifyTypesInCondition()&lt;/code&gt;). &lt;code&gt;setRootExpr()&lt;/code&gt; method returns a new object (SpecifiedTypes is immutable).&lt;/p&gt;
&lt;h3 id=&quot;node-attributes-parent%2C-previous%2C-next-are-no-longer-available&quot; tabindex=&quot;-1&quot;&gt;Node attributes &lt;code&gt;parent&lt;/code&gt;, &lt;code&gt;previous&lt;/code&gt;, &lt;code&gt;next&lt;/code&gt; are no longer available &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#node-attributes-parent%2C-previous%2C-next-are-no-longer-available&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Learn more: &lt;a href=&quot;/blog/preprocessing-ast-for-custom-rules&quot;&gt;Preprocessing AST for custom rules&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;removed-config-parameter-scopeclass&quot; tabindex=&quot;-1&quot;&gt;Removed config parameter &lt;code&gt;scopeClass&lt;/code&gt; &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#removed-config-parameter-scopeclass&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;As a replacement you can implement &lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Type.ExpressionTypeResolverExtension.html&quot;&gt;&lt;code&gt;PHPStan&#92;Type&#92;ExpressionTypeResolverExtension&lt;/code&gt;&lt;/a&gt; interface instead and register it as a service.&lt;/p&gt;
&lt;h3 id=&quot;removed-phpstan%5Cbroker%5Cbroker&quot; tabindex=&quot;-1&quot;&gt;Removed &lt;code&gt;PHPStan&#92;Broker&#92;Broker&lt;/code&gt; &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#removed-phpstan%5Cbroker%5Cbroker&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Use &lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Reflection.ReflectionProvider.html&quot;&gt;&lt;code&gt;PHPStan&#92;Reflection&#92;ReflectionProvider&lt;/code&gt;&lt;/a&gt; instead.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;BrokerAwareExtension&lt;/code&gt; was also removed. Ask for &lt;code&gt;ReflectionProvider&lt;/code&gt; in the extension constructor instead.&lt;/p&gt;
&lt;p&gt;Instead of &lt;code&gt;PHPStanTestCase::createBroker()&lt;/code&gt;, call &lt;code&gt;PHPStanTestCase::createReflectionProvider()&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;list-type-is-enabled-for-everyone&quot; tabindex=&quot;-1&quot;&gt;List type is enabled for everyone &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#list-type-is-enabled-for-everyone&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Removed static methods from &lt;code&gt;AccessoryArrayListType&lt;/code&gt; class:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;isListTypeEnabled()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setListTypeEnabled()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;intersectWith()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Instead of &lt;code&gt;AccessoryArrayListType::intersectWith($type)&lt;/code&gt;, do &lt;code&gt;TypeCombinator::intersect($type, new AccessoryArrayListType())&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;minor-backward-compatibility-breaks-1&quot; tabindex=&quot;-1&quot;&gt;Minor backward compatibility breaks &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#minor-backward-compatibility-breaks-1&quot;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Classes that were previously &lt;code&gt;@final&lt;/code&gt; were made &lt;code&gt;final&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Parameter &lt;code&gt;$callableParameters&lt;/code&gt; of &lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.MutatingScope.html#_enterAnonymousFunction&quot;&gt;&lt;code&gt;MutatingScope::enterAnonymousFunction()&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.MutatingScope.html#_enterArrowFunction&quot;&gt;&lt;code&gt;enterArrowFunction()&lt;/code&gt;&lt;/a&gt; made required&lt;/li&gt;
&lt;li&gt;Parameter &lt;code&gt;StatementContext $context&lt;/code&gt; of &lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Analyser.NodeScopeResolver.html#_processStmtNodes&quot;&gt;&lt;code&gt;NodeScopeResolver::processStmtNodes()&lt;/code&gt;&lt;/a&gt; made required&lt;/li&gt;
&lt;li&gt;ClassPropertiesNode - remove &lt;code&gt;$extensions&lt;/code&gt; parameter from &lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Node.ClassPropertiesNode.html#_getUninitializedProperties&quot;&gt;&lt;code&gt;getUninitializedProperties()&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Type::getSmallerType()&lt;/code&gt;, &lt;code&gt;Type::getSmallerOrEqualType()&lt;/code&gt;, &lt;code&gt;Type::getGreaterType()&lt;/code&gt;, &lt;code&gt;Type::getGreaterOrEqualType()&lt;/code&gt;, &lt;code&gt;Type::isSmallerThan()&lt;/code&gt;, &lt;code&gt;Type::isSmallerThanOrEqual()&lt;/code&gt; now require &lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Php.PhpVersion.html&quot;&gt;&lt;code&gt;PhpVersion&lt;/code&gt;&lt;/a&gt; as argument.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CompoundType::isGreaterThan()&lt;/code&gt;, &lt;code&gt;CompoundType::isGreaterThanOrEqual()&lt;/code&gt; now require &lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Php.PhpVersion.html&quot;&gt;&lt;code&gt;PhpVersion&lt;/code&gt;&lt;/a&gt; as argument.&lt;/li&gt;
&lt;li&gt;Removed &lt;code&gt;ReflectionProvider::supportsAnonymousClasses()&lt;/code&gt; (all reflection providers support anonymous classes)&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;ArrayType::generalizeKeys()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;ArrayType::count()&lt;/code&gt;, use &lt;code&gt;Type::getArraySize()&lt;/code&gt; instead&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;ArrayType::castToArrayKeyType()&lt;/code&gt;, &lt;code&gt;Type::toArrayKey()&lt;/code&gt; instead&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;UnionType::pickTypes()&lt;/code&gt;, use &lt;code&gt;pickFromTypes()&lt;/code&gt; instead&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;RegexArrayShapeMatcher::matchType()&lt;/code&gt;, use &lt;code&gt;matchExpr()&lt;/code&gt; instead&lt;/li&gt;
&lt;li&gt;Remove unused &lt;code&gt;PHPStanTestCase::$useStaticReflectionProvider&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;PHPStanTestCase::getReflectors()&lt;/code&gt;, use &lt;code&gt;getReflector()&lt;/code&gt; instead&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;ClassReflection::getFileNameWithPhpDocs()&lt;/code&gt;, use &lt;code&gt;getFileName()&lt;/code&gt; instead&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;AnalysisResult::getInternalErrors()&lt;/code&gt;, use &lt;code&gt;getInternalErrorObjects()&lt;/code&gt; instead&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;ConstantReflection::getValue()&lt;/code&gt;, use &lt;code&gt;getValueExpr()&lt;/code&gt; instead. To get &lt;code&gt;Type&lt;/code&gt; from &lt;code&gt;Expr&lt;/code&gt;, use &lt;code&gt;Scope::getType()&lt;/code&gt; or &lt;code&gt;InitializerExprTypeResolver::getType()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;PropertyTag::getType()&lt;/code&gt;, use &lt;code&gt;getReadableType()&lt;/code&gt; / &lt;code&gt;getWritableType()&lt;/code&gt; instead&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;GenericTypeVariableResolver&lt;/code&gt;, use &lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getTemplateType&quot;&gt;&lt;code&gt;Type::getTemplateType()&lt;/code&gt;&lt;/a&gt; instead&lt;/li&gt;
&lt;li&gt;Rename &lt;code&gt;Type::isClassStringType()&lt;/code&gt; to &lt;code&gt;Type::isClassString()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;Scope::isSpecified()&lt;/code&gt;, use &lt;code&gt;hasExpressionType()&lt;/code&gt; instead&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;ConstantArrayType::isEmpty()&lt;/code&gt;, use &lt;code&gt;isIterableAtLeastOnce()-&amp;gt;no()&lt;/code&gt; instead&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;ConstantArrayType::getNextAutoIndex()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Removed methods from &lt;code&gt;ConstantArrayType&lt;/code&gt; - &lt;code&gt;getFirst*Type&lt;/code&gt; and &lt;code&gt;getLast*Type&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;getFirstIterable*Type&lt;/code&gt; and &lt;code&gt;getLastIterable*Type&lt;/code&gt; instead&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;ConstantArrayType::generalizeToArray()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;ConstantArrayType::findTypeAndMethodName()&lt;/code&gt;, use &lt;code&gt;findTypeAndMethodNames()&lt;/code&gt; instead&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;ConstantArrayType::removeLast()&lt;/code&gt;, use &lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_popArray&quot;&gt;&lt;code&gt;Type::popArray()&lt;/code&gt;&lt;/a&gt; instead&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;ConstantArrayType::removeFirst()&lt;/code&gt;, use &lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_shiftArray&quot;&gt;&lt;code&gt;Type::shiftArray()&lt;/code&gt;&lt;/a&gt; instead&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;ConstantArrayType::reverse()&lt;/code&gt;, use &lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_reverseArray&quot;&gt;&lt;code&gt;Type::reverseArray()&lt;/code&gt;&lt;/a&gt; instead&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;ConstantArrayType::chunk()&lt;/code&gt;, use &lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_chunkArray&quot;&gt;&lt;code&gt;Type::chunkArray()&lt;/code&gt;&lt;/a&gt; instead&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;ConstantArrayType::slice()&lt;/code&gt;, use &lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_sliceArray&quot;&gt;&lt;code&gt;Type::sliceArray()&lt;/code&gt;&lt;/a&gt; instead&lt;/li&gt;
&lt;li&gt;Made &lt;code&gt;TypeUtils&lt;/code&gt; thinner by removing methods:
&lt;ul&gt;
&lt;li&gt;Remove &lt;code&gt;TypeUtils::getArrays()&lt;/code&gt; and &lt;code&gt;getAnyArrays()&lt;/code&gt;, use &lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getArrays&quot;&gt;&lt;code&gt;Type::getArrays()&lt;/code&gt;&lt;/a&gt; instead&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;TypeUtils::getConstantArrays()&lt;/code&gt; and &lt;code&gt;getOldConstantArrays()&lt;/code&gt;, use &lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantArrays&quot;&gt;&lt;code&gt;Type::getConstantArrays()&lt;/code&gt;&lt;/a&gt; instead&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;TypeUtils::getConstantStrings()&lt;/code&gt;, use &lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantStrings&quot;&gt;&lt;code&gt;Type::getConstantStrings()&lt;/code&gt;&lt;/a&gt; instead&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;TypeUtils::getConstantTypes()&lt;/code&gt; and &lt;code&gt;getAnyConstantTypes()&lt;/code&gt;, use &lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isConstantValue&quot;&gt;&lt;code&gt;Type::isConstantValue()&lt;/code&gt;&lt;/a&gt; or &lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_generalize&quot;&gt;&lt;code&gt;Type::generalize()&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;TypeUtils::generalizeType()&lt;/code&gt;, use &lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_generalize&quot;&gt;&lt;code&gt;Type::generalize()&lt;/code&gt;&lt;/a&gt; instead&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;TypeUtils::getDirectClassNames()&lt;/code&gt;, use &lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getObjectClassNames&quot;&gt;&lt;code&gt;Type::getObjectClassNames()&lt;/code&gt;&lt;/a&gt; instead&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;TypeUtils::getConstantScalars()&lt;/code&gt;, use &lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isConstantScalarValue&quot;&gt;&lt;code&gt;Type::isConstantScalarValue()&lt;/code&gt;&lt;/a&gt; or &lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getConstantScalarTypes&quot;&gt;&lt;code&gt;Type::getConstantScalarTypes()&lt;/code&gt;&lt;/a&gt; instead&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;TypeUtils::getEnumCaseObjects()&lt;/code&gt;, use &lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getEnumCases&quot;&gt;&lt;code&gt;Type::getEnumCases()&lt;/code&gt;&lt;/a&gt; instead&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;TypeUtils::containsCallable()&lt;/code&gt;, use &lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isCallable&quot;&gt;&lt;code&gt;Type::isCallable()&lt;/code&gt;&lt;/a&gt; instead&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Removed &lt;code&gt;Scope::doNotTreatPhpDocTypesAsCertain()&lt;/code&gt;, use &lt;code&gt;getNativeType()&lt;/code&gt; instead&lt;/li&gt;
&lt;li&gt;Parameter &lt;code&gt;$isList&lt;/code&gt; in &lt;code&gt;ConstantArrayType&lt;/code&gt; constructor can only be &lt;code&gt;TrinaryLogic&lt;/code&gt;, no longer &lt;code&gt;bool&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Parameter &lt;code&gt;$nextAutoIndexes&lt;/code&gt; in &lt;code&gt;ConstantArrayType&lt;/code&gt; constructor can only be &lt;code&gt;non-empty-list&amp;lt;int&amp;gt;&lt;/code&gt;, no longer &lt;code&gt;int&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;ConstantType&lt;/code&gt; interface, use &lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_isConstantValue&quot;&gt;&lt;code&gt;Type::isConstantValue()&lt;/code&gt;&lt;/a&gt; instead&lt;/li&gt;
&lt;li&gt;&lt;code&gt;acceptsNamedArguments()&lt;/code&gt; in &lt;code&gt;FunctionReflection&lt;/code&gt;, &lt;code&gt;ExtendedMethodReflection&lt;/code&gt; and &lt;code&gt;CallableParametersAcceptor&lt;/code&gt; interfaces returns &lt;code&gt;TrinaryLogic&lt;/code&gt; instead of &lt;code&gt;bool&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;FunctionReflection::isFinal()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Type.Type.html#_getProperty&quot;&gt;&lt;code&gt;Type::getProperty()&lt;/code&gt;&lt;/a&gt; now returns &lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Reflection.ExtendedPropertyReflection.html&quot;&gt;&lt;code&gt;ExtendedPropertyReflection&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;__set_state()&lt;/code&gt; on objects that should not be serialized in cache&lt;/li&gt;
&lt;li&gt;Parameter &lt;code&gt;$selfClass&lt;/code&gt; of &lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Type.TypehintHelper.html#_decideTypeFromReflection&quot;&gt;&lt;code&gt;TypehintHelper::decideTypeFromReflection()&lt;/code&gt;&lt;/a&gt; no longer accepts &lt;code&gt;string&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;LevelsTestCase::dataTopics()&lt;/code&gt; data provider made static&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PHPStan&#92;Node&#92;Printer&#92;Printer&lt;/code&gt; no longer autowired as &lt;code&gt;PhpParser&#92;PrettyPrinter&#92;Standard&lt;/code&gt;, use &lt;code&gt;PHPStan&#92;Node&#92;Printer&#92;Printer&lt;/code&gt; in the typehint&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;Type::acceptsWithReason()&lt;/code&gt;, &lt;code&gt;Type:accepts()&lt;/code&gt; return type changed from &lt;code&gt;TrinaryLogic&lt;/code&gt; to &lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Type.AcceptsResult.html&quot;&gt;&lt;code&gt;AcceptsResult&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;CompoundType::isAcceptedWithReasonBy()&lt;/code&gt;, &lt;code&gt;CompoundType::isAcceptedBy()&lt;/code&gt; return type changed from &lt;code&gt;TrinaryLogic&lt;/code&gt; to &lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Type.AcceptsResult.html&quot;&gt;&lt;code&gt;AcceptsResult&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;Type::isSuperTypeOfWithReason()&lt;/code&gt;, &lt;code&gt;Type:isSuperTypeOf()&lt;/code&gt; return type changed from &lt;code&gt;TrinaryLogic&lt;/code&gt; to &lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Type.IsSuperTypeOfResult.html&quot;&gt;&lt;code&gt;IsSuperTypeOfResult&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;CompoundType::isSubTypeOfWithReasonBy()&lt;/code&gt;, &lt;code&gt;CompoundType::isSubTypeOf()&lt;/code&gt; return type changed from &lt;code&gt;TrinaryLogic&lt;/code&gt; to &lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Type.IsSuperTypeOfResult.html&quot;&gt;&lt;code&gt;IsSuperTypeOfResult&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Remove &lt;code&gt;TemplateType::isValidVarianceWithReason()&lt;/code&gt;, changed &lt;code&gt;TemplateType::isValidVariance()&lt;/code&gt; return type to &lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Type.IsSuperTypeOfResult.html&quot;&gt;&lt;code&gt;IsSuperTypeOfResult&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RuleLevelHelper::accepts()&lt;/code&gt; return type changed from &lt;code&gt;bool&lt;/code&gt; to &lt;a href=&quot;https://apiref.phpstan.org/2.0.x/PHPStan.Type.AcceptsResult.html&quot;&gt;&lt;code&gt;RuleLevelHelperAcceptsResult&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Changes around &lt;code&gt;ClassConstantReflection&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Class &lt;code&gt;ClassConstantReflection&lt;/code&gt; removed from BC promise, renamed to &lt;code&gt;RealClassConstantReflection&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Interface &lt;code&gt;ConstantReflection&lt;/code&gt; renamed to &lt;code&gt;ClassConstantReflection&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Added more methods around PHPDoc types and native types to the (new) &lt;code&gt;ClassConstantReflection&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Interface &lt;code&gt;GlobalConstantReflection&lt;/code&gt; renamed to &lt;code&gt;ConstantReflection&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Renamed interfaces and classes from &lt;code&gt;*WithPhpDocs&lt;/code&gt; to &lt;code&gt;Extended*&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ParametersAcceptorWithPhpDocs&lt;/code&gt; -&amp;gt; &lt;code&gt;ExtendedParametersAcceptor&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ParameterReflectionWithPhpDocs&lt;/code&gt; -&amp;gt; &lt;code&gt;ExtendedParameterReflection&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FunctionVariantWithPhpDocs&lt;/code&gt; -&amp;gt; &lt;code&gt;ExtendedFunctionVariant&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ClassPropertyNode::getNativeType()&lt;/code&gt; return type changed from AST node to &lt;code&gt;Type|null&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Class &lt;code&gt;PHPStan&#92;Node&#92;ClassMethod&lt;/code&gt; (accessible from &lt;code&gt;ClassMethodsNode&lt;/code&gt;) is no longer an AST node
&lt;ul&gt;
&lt;li&gt;Call &lt;code&gt;PHPStan&#92;Node&#92;ClassMethod::getNode()&lt;/code&gt; to access the original AST node&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content>
		</entry>
	
		
		<entry>
			<title>PHPStan 2.0 Released With Level 10 and Elephpants!</title>
			<link href="https://phpstan.org/blog/phpstan-2-0-released-level-10-elephpants"/>
			<updated>2024-11-11T00:00:00Z</updated>
			<id>https://phpstan.org/blog/phpstan-2-0-released-level-10-elephpants</id>
			<content type="html" xml:base="https://phpstan.org">&lt;img src=&quot;/images/phpstan-2-0.jpg&quot; alt=&quot;PHPStan 2.0&quot; class=&quot;rounded-lg mb-8&quot;&gt;
&lt;p&gt;PHPStan 1.0 was released a little over &lt;a href=&quot;/blog/phpstan-1-0-released&quot;&gt;three years ago&lt;/a&gt;. 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.&lt;/p&gt;
&lt;p&gt;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 &lt;a href=&quot;/blog/what-is-bleeding-edge&quot;&gt;early adopters&lt;/a&gt; for more than two years.&lt;/p&gt;
&lt;p&gt;But code and analysis changes are not the only things being released today! PHPStan joins &lt;a href=&quot;https://elephpant.me/&quot;&gt;the family of elephpants&lt;/a&gt; with its own take on the legendary PHP mascot.&lt;/p&gt;
&lt;p class=&quot;mt-4 rounded-lg mb-8 border border-blue-500 p-4 hover:border-blue-400&quot;&gt;&lt;a href=&quot;/merch&quot;&gt;&lt;img src=&quot;/images/elephpant-trio.png&quot; alt=&quot;PHPStan Elephpant&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You can also order PHPStan T-shirts again, in both blue and white, and straight/fitted cut:&lt;/p&gt;
&lt;a href=&quot;/merch&quot; class=&quot;flex w-full justify-center mt-4 rounded-lg mb-8 border border-blue-500 py-2 hover:border-blue-400&quot;&gt;
	&lt;img class=&quot;aspect-[1610/913] max-w-xs w-1/2&quot; src=&quot;/images/tshirt-2024-blue-straight.jpg&quot;&gt;
	&lt;img class=&quot;aspect-[1610/913] max-w-xs w-1/2&quot; src=&quot;/images/tshirt-2024-white-fitted.jpg&quot;&gt;
&lt;/a&gt;
&lt;p&gt;We’re &lt;a href=&quot;/merch&quot;&gt;accepting orders&lt;/a&gt; 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.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;And now to the code part. The &lt;a href=&quot;https://github.com/phpstan/phpstan/releases/tag/2.0.0&quot;&gt;comprehensive release notes&lt;/a&gt; are massive, consisting of over 180 items. That’s why, for the first time ever, PHPStan 2.0 also comes with easy-to-follow &lt;a href=&quot;/blog/upgrading-from-phpstan-1-to-2&quot;&gt;upgrading guide&lt;/a&gt;, for both end users and extension developers.&lt;/p&gt;
&lt;p&gt;It’s really hard to pick and choose my favourite features and changes from 2.0, but I’ll try. Here we go:&lt;/p&gt;
&lt;h2 id=&quot;level-10&quot; tabindex=&quot;-1&quot;&gt;Level 10 &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#level-10&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;New challenge for the level max enthusiasts! Previously added level 9 acknowledges using &lt;code&gt;mixed&lt;/code&gt; 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 &lt;code&gt;checkExplicitMixed&lt;/code&gt;. Meaning it will report errors for explicitly-typed &lt;code&gt;mixed&lt;/code&gt; values in your code.&lt;/p&gt;
&lt;p&gt;Level 6 forces you to add missing types. If you manage to skip that with the help of &lt;del&gt;cheating&lt;/del&gt;, ahem, &lt;a href=&quot;/user-guide/baseline&quot;&gt;the baseline&lt;/a&gt;, 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 &lt;code&gt;mixed&lt;/code&gt;, and they will be picked up by level 10&lt;/p&gt;
&lt;h2 id=&quot;list-type&quot; tabindex=&quot;-1&quot;&gt;List type &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#list-type&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/List_(abstract_data_type)&quot;&gt;List&lt;/a&gt; in PHPStan is an array with sequential integer keys starting at 0 and with no gaps. It joins &lt;a href=&quot;/writing-php-code/phpdoc-types&quot;&gt;many other advanced types expressible in PHPDocs&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-diff-php diff-highlight&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/** @param list&amp;lt;int&gt; $listOfIntegers */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;doFoo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword type-hint&quot;&gt;array&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$listOfIntegers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword return-type&quot;&gt;void&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;lower-memory-consumption-leads-to-faster-performance&quot; tabindex=&quot;-1&quot;&gt;Lower memory consumption leads to faster performance &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#lower-memory-consumption-leads-to-faster-performance&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;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 &lt;code&gt;memory_limit&lt;/code&gt; in php.ini, but also all the memory assigned to the runner hardware.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;To debug memory leaks, I use the &lt;a href=&quot;https://github.com/BitOne/php-meminfo&quot;&gt;&lt;code&gt;php-meminfo&lt;/code&gt;&lt;/a&gt; extension. I quickly realized that most of the memory is occupied by &lt;a href=&quot;/developing-extensions/abstract-syntax-tree&quot;&gt;AST&lt;/a&gt; nodes. PHP frees the memory occupied by an object when there are no more references to it &lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;. It didn’t work in case of AST nodes because they kept pointing at each other:&lt;/p&gt;
&lt;img class=&quot;mb-8&quot; src=&quot;/images/mermaid-6d0ce15eb0039ff974c3884feda067b5eec0efe304ee41e31933d7bf3ac748a0.svg&quot; /&gt;
&lt;p&gt;Getting rid of &lt;code&gt;parent&lt;/code&gt;/&lt;code&gt;previous&lt;/code&gt;/&lt;code&gt;next&lt;/code&gt; node attributes is a backward compatibility break for custom rules that read them. I’ve written &lt;a href=&quot;/blog/preprocessing-ast-for-custom-rules&quot;&gt;an article on how to make these rules work again&lt;/a&gt; even without those memory-consuming references.&lt;/p&gt;
&lt;p&gt;The object graph is now obviously cleaner and with no reference cycles:&lt;/p&gt;
&lt;img class=&quot;mb-8&quot; src=&quot;/images/mermaid-46470766f874d0ff9817a88d3b95b15dfaed05f2782e7cb6db9ff8e2cf7879fb.svg&quot; /&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2 id=&quot;validate-inline-phpdoc-%40var-tag-type&quot; tabindex=&quot;-1&quot;&gt;Validate inline PHPDoc &lt;code&gt;@var&lt;/code&gt; tag type &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#validate-inline-phpdoc-%40var-tag-type&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There are multiple problems with inline &lt;code&gt;@var&lt;/code&gt; PHPDoc tag. PHP developers use it for two main reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;To fix wrong 3rd party PHPDocs. A dependency &lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt; might have &lt;code&gt;@return string&lt;/code&gt; in a PHPDoc but in reality can return &lt;code&gt;null&lt;/code&gt; as well.&lt;/li&gt;
&lt;li&gt;To narrow down the returned type. When a function returns &lt;code&gt;string|null&lt;/code&gt; but we know that in this case it can only return &lt;code&gt;string&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-diff-php diff-highlight&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/** @var Something $a */&lt;/span&gt;
&lt;span class=&quot;token variable&quot;&gt;$a&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;makeSomething&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By looking at the analysed code we can’t really tell which scenario it is. That’s why PHPStan always trusted the type in &lt;code&gt;@var&lt;/code&gt; and didn’t report any possible mistakes. Obviously that’s dangerous because the type in &lt;code&gt;@var&lt;/code&gt; 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.&lt;/p&gt;
&lt;p&gt;PHPStan 2.0 validates the inline &lt;code&gt;@var&lt;/code&gt; tag type against the native type of the assigned expression. It finds the lies spread around in &lt;code&gt;@var&lt;/code&gt; tags:&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-diff-php diff-highlight&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;doFoo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword return-type&quot;&gt;string&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/** @var string|null $a */&lt;/span&gt;
&lt;span class=&quot;token variable&quot;&gt;$a&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doFoo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// PHPDoc tag @var with type string|null is not subtype of native type string.&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It doesn’t make sense to allow &lt;code&gt;string|null&lt;/code&gt;, because the type can never be &lt;code&gt;null&lt;/code&gt;. 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 &lt;code&gt;string&lt;/code&gt; or &lt;code&gt;non-empty-string&lt;/code&gt; would be okay.&lt;/p&gt;
&lt;p&gt;By default PHPStan isn’t going to report anything about the following code:&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-diff-php diff-highlight&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/** @return string */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;doFoo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/** @var string|null $a */&lt;/span&gt;
&lt;span class=&quot;token variable&quot;&gt;$a&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doFoo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because the &lt;code&gt;@return&lt;/code&gt; PHPDoc might be wrong and that’s what the &lt;code&gt;@var&lt;/code&gt; tag might be trying to fix. If you want this scenario to be reported too, enable &lt;a href=&quot;/config-reference#reportwrongphpdoctypeinvartag&quot;&gt;&lt;code&gt;reportWrongPhpDocTypeInVarTag&lt;/code&gt;&lt;/a&gt;, or install &lt;a href=&quot;https://github.com/phpstan/phpstan-strict-rules&quot;&gt;phpstan-strict-rules&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I’d like the PHP community to use inline &lt;code&gt;@var&lt;/code&gt; tags less and less over time. There are many great alternatives that promote good practices and code deduplication: &lt;a href=&quot;/writing-php-code/phpdoc-types#conditional-return-types&quot;&gt;Conditional return types&lt;/a&gt;, &lt;a href=&quot;/writing-php-code/narrowing-types#custom-type-checking-functions-and-methods&quot;&gt;&lt;code&gt;@phpstan-assert&lt;/code&gt;&lt;/a&gt;, &lt;a href=&quot;/blog/generics-in-php-using-phpdocs&quot;&gt;generics&lt;/a&gt;, &lt;a href=&quot;/user-guide/stub-files&quot;&gt;stub files&lt;/a&gt; for overriding 3rd party PHPDocs, or &lt;a href=&quot;/developing-extensions/dynamic-return-type-extensions&quot;&gt;dynamic return type extensions&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;checking-truthiness-of-%40phpstan-pure-with-impure-points&quot; tabindex=&quot;-1&quot;&gt;Checking truthiness of &lt;code&gt;@phpstan-pure&lt;/code&gt; with impure points &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#checking-truthiness-of-%40phpstan-pure-with-impure-points&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;It’s useful to mark functions and methods with &lt;code&gt;@phpstan-pure&lt;/code&gt; if their logic should be pure. PHPStan 2.0 now enforces this annotation, meaning it will report any impure code inside as an error.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-diff-php diff-highlight&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Call to method Foo::pureMethod() on a separate line has no effect.&lt;/span&gt;
&lt;span class=&quot;token variable&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pureMethod&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Also: if a &lt;code&gt;void&lt;/code&gt; method does not have any impure points, it doesn’t have to exist:&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-diff-php diff-highlight&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Method Foo::add() returns void but does not have any side effects.&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword type-hint&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$a&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword type-hint&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$b&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword return-type&quot;&gt;void&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token variable&quot;&gt;$c&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$a&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$b&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;less-caching%2C-disk-space-cleanup&quot; tabindex=&quot;-1&quot;&gt;Less caching, disk space cleanup &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#less-caching%2C-disk-space-cleanup&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;PHPStan used to cache information whether a function is variadic because of &lt;a href=&quot;https://www.php.net/manual/en/function.func-get-args.php&quot;&gt;&lt;code&gt;func_get_args()&lt;/code&gt;&lt;/a&gt; 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.&lt;/p&gt;
&lt;p&gt;PHPStan now solely relies on the &lt;a href=&quot;https://phpstan.org/user-guide/result-cache&quot;&gt;result cache&lt;/a&gt; to speed up the analysis.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;the-future&quot; tabindex=&quot;-1&quot;&gt;The future &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#the-future&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I said it &lt;a href=&quot;/blog/phpstan-1-0-released#a-bright-future&quot;&gt;three years ago&lt;/a&gt;, and I’m saying it again. PHPStan’s future is bright. It’s my full-time job thanks to &lt;a href=&quot;https://phpstan.org/blog/introducing-phpstan-pro&quot;&gt;PHPStan Pro&lt;/a&gt; and &lt;a href=&quot;/sponsor&quot;&gt;GitHub Sponsors&lt;/a&gt;, splitting the revenue roughly 50-50 between them.&lt;/p&gt;
&lt;p&gt;I love my job, I love PHP and I don’t plan to stop. Contributors are &lt;a href=&quot;https://github.com/phpstan/phpstan-src/graphs/contributors?from=11.+11.+2023&quot;&gt;very active&lt;/a&gt; and indispensable. PHPStan has a thriving &lt;a href=&quot;/user-guide/extension-library&quot;&gt;ecosystem of extensions&lt;/a&gt; as well.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Do you like PHPStan and use it every day? &lt;a href=&quot;/sponsor&quot;&gt;&lt;strong&gt;Consider sponsoring&lt;/strong&gt; further development of PHPStan on GitHub Sponsors and also &lt;strong&gt;subscribe to PHPStan Pro&lt;/strong&gt;&lt;/a&gt;! I’d really appreciate it!&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot;&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;That’s called reference counting. &lt;a href=&quot;#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;That probably doesn’t use static analysis. &lt;a href=&quot;#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
		</entry>
	
		
		<entry>
			<title>PHPStan 1.12: Road to PHPStan 2.0</title>
			<link href="https://phpstan.org/blog/phpstan-1-12-road-to-phpstan-2-0"/>
			<updated>2024-08-27T00:00:00Z</updated>
			<id>https://phpstan.org/blog/phpstan-1-12-road-to-phpstan-2-0</id>
			<content type="html" xml:base="https://phpstan.org">&lt;p&gt;After three years since the initial &lt;a href=&quot;/blog/phpstan-1-0-released&quot;&gt;PHPStan 1.0 release&lt;/a&gt;, 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 &lt;a href=&quot;/blog/what-is-bleeding-edge&quot;&gt;Bleeding Edge config toggle&lt;/a&gt;, so they can be enjoyed by PHPStan users sooner.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;Here’s an equation:&lt;/p&gt;
&lt;p class=&quot;text-center font-bold text-lg&quot;&gt;PHPStan 2.0 = PHPStan 1.12 + Bleeding Edge + BC breaks&lt;/p&gt;
&lt;p&gt;When you upgrade to PHPStan 1.12 and enable Bleeding Edge, you can get mostly ready for PHPStan 2.0 today.&lt;/p&gt;
&lt;p&gt;But enough about the future. Here’s what’s new in &lt;a href=&quot;https://github.com/phpstan/phpstan/releases/tag/1.12.0&quot;&gt;today’s 1.12 release&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;general-availability-of-precise-type-inference-for-regular-expressions&quot; tabindex=&quot;-1&quot;&gt;General availability of precise type inference for regular expressions &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#general-availability-of-precise-type-inference-for-regular-expressions&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;First &lt;a href=&quot;https://github.com/phpstan/phpstan/releases/tag/1.11.6&quot;&gt;introduced in 1.11.6&lt;/a&gt;, and improved by dozens of pull requests since then, this feature is about figuring out the precise type for &lt;code&gt;$matches&lt;/code&gt; by-ref argument coming from &lt;code&gt;preg_match()&lt;/code&gt; and other related functions:&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-diff-php diff-highlight&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;preg_match&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;/Price: (?&amp;lt;currency&gt;£|€)&#92;d+/&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$s&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$matches&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;PREG_UNMATCHED_AS_NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;// array{0: string, currency: non-empty-string, 1: non-empty-string}&lt;/span&gt;
	&lt;span class=&quot;token function&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;PHPStan&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;dumpType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$matches&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Markus Staab and Jordi Boggiano of Composer fame &lt;a href=&quot;https://staabm.github.io/2024/07/05/array-shapes-for-preg-match-matches.html&quot;&gt;worked really hard on this&lt;/a&gt;. It brings a whole new level of precision to PHPStan’s type inference.&lt;/p&gt;
&lt;p&gt;From what I gathered, this ability is pretty unique and not many programming languages offer it. And now we have it in PHP!&lt;/p&gt;
&lt;h2 id=&quot;fix-blind-spots-in-phpdoc-tags-type-checks&quot; tabindex=&quot;-1&quot;&gt;Fix blind spots in PHPDoc tags type checks &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#fix-blind-spots-in-phpdoc-tags-type-checks&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;PHPStan performs four categories of sanity checks on &lt;a href=&quot;/writing-php-code/phpdoc-types&quot;&gt;types in PHPDocs&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Missing types: performed on level 6 and up, it enforces specifying array value types, generic types, and &lt;a href=&quot;/config-reference#vague-typehints&quot;&gt;optionally also callable signatures&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Existing classes: looks for nonexistent classes and also trait references&lt;/li&gt;
&lt;li&gt;Unresolvable types: looks for &lt;code&gt;never&lt;/code&gt; bottom type that resulted from impossible intersections like &lt;code&gt;string&amp;amp;int&lt;/code&gt;, referencing undefined constants like &lt;code&gt;Foo::BAR&lt;/code&gt;, etc.&lt;/li&gt;
&lt;li&gt;Generics type checks: checks sanity of generic types. Compares the number of type variables in a type against the number of &lt;code&gt;@template&lt;/code&gt; above the class declaration, checks the subtyping of bounds, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These type checks weren’t performed consistently for all &lt;a href=&quot;/writing-php-code/phpdocs-basics&quot;&gt;supported PHPDocs tags&lt;/a&gt;. We were missing most or all of these checks for these tags:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@param-out&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@param-closure-this&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@param-immediately-invoked-callable&lt;/code&gt; and &lt;code&gt;@param-later-invoked-callable&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@phpstan-self-out&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@mixin&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@property&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@method&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@extends&lt;/code&gt;, &lt;code&gt;@implements&lt;/code&gt;, &lt;code&gt;@use&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2 id=&quot;too-wide-private-property-type&quot; tabindex=&quot;-1&quot;&gt;Too wide private property type &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#too-wide-private-property-type&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Another addition to Bleeding Edge checks if a type could be removed from a private property union type, without affecting anything.&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-diff-php diff-highlight&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// if string is never assigned to `$this-&gt;a`, we can remove it from the type&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword type-declaration&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token keyword type-declaration&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$a&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PHPStan already had this rule for function and method return types, and now we can enjoy it for properties too.&lt;/p&gt;
&lt;h2 id=&quot;php-8.4-runtime-support&quot; tabindex=&quot;-1&quot;&gt;PHP 8.4 runtime support &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#php-8.4-runtime-support&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Implementing support for new PHP version in PHPStan comes in four phases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Make sure PHPStan runs on the new PHP version and all tests pass.&lt;/li&gt;
&lt;li&gt;Understand added functions and changed signatures of existing functions in the new PHP version.&lt;/li&gt;
&lt;li&gt;Understand new syntax and features. This removes false errors when analysing code written against the new PHP version.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;PHPStan 1.12 implements the first two phases. It runs on PHP 8.4 without any deprecation notices, and new functions like &lt;a href=&quot;/r/15c8954d-d090-4c01-8c24-46ef5fd93541&quot;&gt;&lt;code&gt;array_find()&lt;/code&gt;&lt;/a&gt; are already known.&lt;/p&gt;
&lt;p&gt;Because of new syntax, the rest is dependent on upgrading to &lt;a href=&quot;https://github.com/nikic/PHP-Parser&quot;&gt;nikic/PHP-Parser&lt;/a&gt; v5 which has to be done in a major version.&lt;/p&gt;
&lt;p&gt;My plan for the upcoming months is straightforward: finish and release PHPStan 2.0, and then work on PHP 8.4-specific features.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;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 &lt;a href=&quot;https://github.com/phpstan/phpstan/discussions&quot;&gt;waiting for everyone’s feedback on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Do you like PHPStan and use it every day? &lt;a href=&quot;/sponsor&quot;&gt;&lt;strong&gt;Consider sponsoring&lt;/strong&gt; further development of PHPStan on GitHub Sponsors and also &lt;strong&gt;subscribe to PHPStan Pro&lt;/strong&gt;&lt;/a&gt;! I’d really appreciate it!&lt;/p&gt;
</content>
		</entry>
	
		
		<entry>
			<title>PHPStan Reports Different Errors Locally &amp; in CI. What Should I Do?</title>
			<link href="https://phpstan.org/blog/phpstan-reports-different-errors-locally-ci-what-should-i-do"/>
			<updated>2024-08-20T00:00:00Z</updated>
			<id>https://phpstan.org/blog/phpstan-reports-different-errors-locally-ci-what-should-i-do</id>
			<content type="html" xml:base="https://phpstan.org">&lt;p&gt;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.&lt;/p&gt;
&lt;h2 id=&quot;is-it-analysing-the-same-code%3F&quot; tabindex=&quot;-1&quot;&gt;Is it analysing the same code? &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#is-it-analysing-the-same-code%3F&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;Run &lt;code&gt;git rev-parse HEAD&lt;/code&gt; or look at &lt;code&gt;git log&lt;/code&gt; to get the Git commit hash. CI systems usually print the current Git commit hash somewhere early in the build log.&lt;/p&gt;
&lt;p&gt;Run &lt;code&gt;git status&lt;/code&gt; to make sure there are no uncommitted changes you’re running the analysis against.&lt;/p&gt;
&lt;h2 id=&quot;is-it-running-the-same-php-version%3F&quot; tabindex=&quot;-1&quot;&gt;Is it running the same PHP version? &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#is-it-running-the-same-php-version%3F&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Make sure to check that you’re running the same PHP version locally and in CI. You can check that with &lt;code&gt;php -v&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;require-phpstan-in-your-composer-project&quot; tabindex=&quot;-1&quot;&gt;Require PHPStan in your Composer project &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#require-phpstan-in-your-composer-project&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Do not run PHPStan installed somewhere globally in your operating system, nor locally, neither in CI. Install PHPStan in &lt;code&gt;composer.json&lt;/code&gt; with your project dependencies with this command:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-diff-bash diff-highlight&quot;&gt;&lt;span class=&quot;token function&quot;&gt;composer&lt;/span&gt; require &lt;span class=&quot;token parameter variable&quot;&gt;--dev&lt;/span&gt; phpstan/phpstan&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This way you will make sure everyone and everything is on the same PHPStan version - your machine, your teammates and most importantly, CI.&lt;/p&gt;
&lt;h2 id=&quot;make-sure-your-composer.lock-is-committed-in-the-git-repository&quot; tabindex=&quot;-1&quot;&gt;Make sure your &lt;code&gt;composer.lock&lt;/code&gt; is committed in the Git repository &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#make-sure-your-composer.lock-is-committed-in-the-git-repository&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Commit and version your &lt;code&gt;composer.lock&lt;/code&gt; file in Git to make sure your dependencies are locked and always the same until you update them.&lt;/p&gt;
&lt;h2 id=&quot;run-composer-install%2C-not-composer-update&quot; tabindex=&quot;-1&quot;&gt;Run &lt;code&gt;composer install&lt;/code&gt;, not &lt;code&gt;composer update&lt;/code&gt; &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#run-composer-install%2C-not-composer-update&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Even with &lt;code&gt;composer.lock&lt;/code&gt; committed, make sure you run &lt;code&gt;composer install&lt;/code&gt; to install dependencies in CI. Running &lt;code&gt;composer update&lt;/code&gt; discards the contents of your &lt;code&gt;composer.lock&lt;/code&gt; file and updates all dependencies. Which is something we don’t want.&lt;/p&gt;
&lt;h2 id=&quot;run-phpstan%E2%80%99s-diagnose-command-to-compare-the-environments&quot; tabindex=&quot;-1&quot;&gt;Run PHPStan’s &lt;code&gt;diagnose&lt;/code&gt; command to compare the environments &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#run-phpstan%E2%80%99s-diagnose-command-to-compare-the-environments&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Since &lt;a href=&quot;https://github.com/phpstan/phpstan/releases/tag/1.11.8&quot;&gt;release 1.11.8&lt;/a&gt; PHPStan ships with a &lt;code&gt;diagnose&lt;/code&gt; command. It prints useful information about its configuration and more. Do not forget to pass the correct &lt;a href=&quot;/config-reference&quot;&gt;configuration file&lt;/a&gt; and &lt;a href=&quot;/user-guide/rule-levels&quot;&gt;rule level&lt;/a&gt; when running the command.&lt;/p&gt;
&lt;p&gt;The output looks like this:&lt;/p&gt;
&lt;pre class=&quot;font-mono text-sm&quot;&gt;
&lt;span class=&quot;text-green-500&quot;&gt;PHP runtime version:&lt;/span&gt; 8.3.1
&lt;span class=&quot;text-green-500&quot;&gt;PHP version for analysis:&lt;/span&gt; 8.3.99 (from config.platform.php in composer.json)

&lt;span class=&quot;text-green-500&quot;&gt;PHPStan version:&lt;/span&gt; 1.11.11
&lt;span class=&quot;text-green-500&quot;&gt;PHPStan running from:&lt;/span&gt;
/var/www/project/vendor/phpstan/phpstan

&lt;span class=&quot;text-green-500&quot;&gt;Extension installer:&lt;/span&gt;
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

&lt;span class=&quot;text-green-500&quot;&gt;Discovered Composer project root:&lt;/span&gt;
/var/www/project

&lt;span class=&quot;text-green-500&quot;&gt;Doctrine&#39;s objectManagerLoader:&lt;/span&gt; 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

&lt;span class=&quot;text-green-500&quot;&gt;Symfony&#39;s consoleApplicationLoader:&lt;/span&gt; No
&lt;/pre&gt;
&lt;p&gt;Compare the output between your local machine and CI to find possible differences.&lt;/p&gt;
&lt;h2 id=&quot;check-the-config-file-path&quot; tabindex=&quot;-1&quot;&gt;Check the config file path &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#check-the-config-file-path&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Make sure you’re running PHPStan &lt;code&gt;analyse&lt;/code&gt; command with the same &lt;a href=&quot;/config-reference&quot;&gt;configuration file&lt;/a&gt;. If you’re passing a custom path with &lt;code&gt;-c&lt;/code&gt;, make sure it’s the same one as locally.&lt;/p&gt;
&lt;p&gt;If you’re letting PHPStan autodiscover &lt;code&gt;phpstan.neon&lt;/code&gt; or &lt;code&gt;phpstan.neon.dist&lt;/code&gt; file in the current working directory, it outputs this note at the beginning of the analysis:&lt;/p&gt;
&lt;pre class=&quot;font-mono text-sm&quot;&gt;
Note: Using configuration file /var/www/project/phpstan.neon.
&lt;/pre&gt;
&lt;p&gt;Make sure your local machine uses the same file as the CI environment.&lt;/p&gt;
&lt;h2 id=&quot;get-rid-of-duplicate-symbols&quot; tabindex=&quot;-1&quot;&gt;Get rid of duplicate symbols &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#get-rid-of-duplicate-symbols&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;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 &lt;a href=&quot;/user-guide/discovering-symbols&quot;&gt;might discover&lt;/a&gt; one definition first and your CI might discover a different definition first, leading to different analysis results.&lt;/p&gt;
&lt;p&gt;Rename your classes and functions so that they are always unique to get rid of this problem.&lt;/p&gt;
&lt;h2 id=&quot;fix-your-custom-non-deterministic-autoloader&quot; tabindex=&quot;-1&quot;&gt;Fix your custom non-deterministic autoloader &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#fix-your-custom-non-deterministic-autoloader&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;If the difference is about what classes are known and unknown to PHPStan, it’s possible you’re using a &lt;a href=&quot;/user-guide/discovering-symbols#custom-autoloader&quot;&gt;custom autoloader&lt;/a&gt; to discover them.&lt;/p&gt;
&lt;p&gt;A following scenario can occur:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You’re referencing a class name in your code with wrong case. For example. Your class name is &lt;code&gt;AdminUser&lt;/code&gt; but you have &lt;code&gt;adminUser&lt;/code&gt; somewhere in your code when referencing the same class.&lt;/li&gt;
&lt;li&gt;On your local machine the first file that’s analysed refers to the class with the correct name - &lt;code&gt;AdminUser&lt;/code&gt;. That makes other occurrences even with the wrong case let the class to be successfully found by PHPStan.&lt;/li&gt;
&lt;li&gt;In CI the first file that’s analysed refers to the class with an incorrect name - &lt;code&gt;adminUser&lt;/code&gt;. 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.&lt;/li&gt;
&lt;li&gt;When the second file that’s analysed in CI refers correctly to &lt;code&gt;AdminUser&lt;/code&gt;, it still makes PHPStan report an error about unknown class which is a different behaviour than you’re experiencing locally.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To fix this problem, make your autoloader discover classes even with wrong case.&lt;/p&gt;
&lt;h2 id=&quot;open-a-discussion-on-phpstan%E2%80%99s-github&quot; tabindex=&quot;-1&quot;&gt;Open a discussion on PHPStan’s GitHub &lt;a class=&quot;header-anchor ml-1 text-gray-300 hover:text-black&quot; href=&quot;#open-a-discussion-on-phpstan%E2%80%99s-github&quot;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If none of the steps above helped you to get consistent results between running PHPStan locally and in CI, please &lt;a href=&quot;https://github.com/phpstan/phpstan/discussions/new?category=support&quot;&gt;open a discussion on GitHub,&lt;/a&gt; so we can help you figure out this problem.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Do you like PHPStan and use it every day? &lt;a href=&quot;/sponsor&quot;&gt;&lt;strong&gt;Consider sponsoring&lt;/strong&gt; further development of PHPStan on GitHub Sponsors and also &lt;strong&gt;subscribe to PHPStan Pro&lt;/strong&gt;&lt;/a&gt;! I’d really appreciate it!&lt;/p&gt;
</content>
		</entry>
	
		
		<entry>
			<title>Debugging PHPStan Performance: Identify Slow Files</title>
			<link href="https://phpstan.org/blog/debugging-performance-identify-slow-files"/>
			<updated>2024-07-05T00:00:00Z</updated>
			<id>https://phpstan.org/blog/debugging-performance-identify-slow-files</id>
			<content type="html" xml:base="https://phpstan.org">&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;Before you start debugging, try enabling &lt;a href=&quot;/blog/what-is-bleeding-edge&quot;&gt;Bleeding Edge&lt;/a&gt;. It makes analysis in huge codebases run much faster. It comes with a &lt;a href=&quot;/blog/phpstan-1-6-0-with-conditional-return-types#lower-memory-consumption&quot;&gt;slight backward compatibility break&lt;/a&gt; with the advantage of breaking bidirectional memory references, making the job of garbage collector easier.&lt;/p&gt;
&lt;p&gt;If that didn’t help, run PHPStan again but add &lt;code&gt;-vvv --debug&lt;/code&gt; &lt;a href=&quot;/user-guide/command-line-usage&quot;&gt;command line options&lt;/a&gt;. 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:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/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
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;vendor/bin/phpstan -vvv --debug &amp;gt; phpstan.log
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And parse this file with a &lt;a href=&quot;https://gist.github.com/ruudk/41897eb59ff497b271fc9fa3c7d5fb27&quot;&gt;simple script&lt;/a&gt; by &lt;a href=&quot;https://github.com/ruudk&quot;&gt;Ruud Kamphuis&lt;/a&gt; that sorts the file list and puts the files that took the most time on top:&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-diff-php diff-highlight&quot;&gt;&lt;span class=&quot;token php language-php&quot;&gt;&lt;span class=&quot;token delimiter important&quot;&gt;&amp;lt;?php&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;declare&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;strict_types&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token variable&quot;&gt;$log&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SplFileObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string double-quoted-string&quot;&gt;&quot;phpstan.log&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token variable&quot;&gt;$logs&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token variable&quot;&gt;$file&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$log&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;eof&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token variable&quot;&gt;$line&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;trim&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$log&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fgets&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$line&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;continue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$file&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token variable&quot;&gt;$file&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$line&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;continue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;preg_match&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;/took (?&amp;lt;seconds&gt;[&#92;d.]+) s/&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$line&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$matches&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;continue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token variable&quot;&gt;$logs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword type-casting&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$matches&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;seconds&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token variable&quot;&gt;$file&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;usort&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$logs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword type-hint&quot;&gt;array&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$left&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword type-hint&quot;&gt;array&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$right&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$right&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;=&gt;&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$left&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token variable&quot;&gt;$logs&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;array_slice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$logs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string double-quoted-string&quot;&gt;&quot;Slowest files&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;PHP_EOL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;foreach&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$logs&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sprintf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string double-quoted-string&quot;&gt;&quot;%.2f seconds: %s&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;PHP_EOL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Save this file into &lt;code&gt;parse.php&lt;/code&gt; in the same directory where &lt;code&gt;phpstan.log&lt;/code&gt; is and run it:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-diff-bash diff-highlight&quot;&gt;php parse.php&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It will output 20 files that took the most time:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;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
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;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 &lt;a href=&quot;/user-guide/ignoring-errors#excluding-whole-files&quot;&gt;exclude it from the analysis&lt;/a&gt; and make PHPStan immediately faster.&lt;/p&gt;
&lt;p&gt;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 &lt;a href=&quot;https://github.com/phpstan/phpstan/issues/new?template=Bug_report.yaml&quot;&gt;open a new bug report&lt;/a&gt; in PHPStan’s GitHub repository so we can take a look and use it to make PHPStan faster.&lt;/p&gt;
&lt;p&gt;Happy debugging!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Do you like PHPStan and use it every day? &lt;a href=&quot;/sponsor&quot;&gt;&lt;strong&gt;Consider sponsoring&lt;/strong&gt; further development of PHPStan on GitHub Sponsors and also &lt;strong&gt;subscribe to PHPStan Pro&lt;/strong&gt;&lt;/a&gt;! I’d really appreciate it!&lt;/p&gt;
</content>
		</entry>
	
</feed>
