PHPStan took an important step in early November 2021 with new features, additional rules and numerous performance optimizations. This means that the main PHP static analyzer is now considered stable and comes with a promise of backward compatibility for future updates.
As this is a major issue, PHPStan 1.0 also makes some disruptive changes that could affect your existing scans. These include additional rules and some replaced or removed configuration settings.
The new level 9
PHPStan scans your PHP source for potential problems without actually executing the code. Along with unit and end-to-end testing, this gives you visibility into the quality of your code by showing issues before users encounter them.
PHPStan levels are used to configure the stringency of the analyzer. The v0.x release series offered eight levels, 0 the most relaxed and 8 the strictest. While it’s best to use the strictest level possible for the best coverage, less stringent levels can help you squeeze PHPStan into an imperfect codebase.
PHPStan 1.0 adds level 9 as a new option. It includes all the rules from level 8 and below, as well as an additional check: strict
mixed type comparisons.
mixed type arrived in PHP 8.0 as a way to explicitly type “any” value. As
mixed is essentially equivalent to an untyped value, it is dangerous to make assumptions about what a
mixed-type value looks like.
The ninth level of PHPStan will strictly enforce this by reporting an error if you try to actively use a
mixed value in your code. Accessing a property or calling a method is not allowed because you cannot tell if they will exist. You can only transmit values through others
mixed-type typehints when this rule is active.
To activate the new behavior, you must modify
level: 8 To
level: 9 in your
phpstan.neon configuration file. PHPStan also supports
level: max as an alias for the top level. If you are already using
max, you will automatically get level 9 when you upgrade to PHPStan 1.0.
Better awareness in Try-Catch-Finally blocks
PHPStan now has better type inference and knowledge of variables for try-catch-finally blocks. He will use the
@throws docblock to check the types of exceptions thrown by each function in your codebase. This understanding is used to inform variable availability checks in try-catch blocks.
Here is an example showing why this is important:
/** * @throws DemoException */ function first() throw DemoException(); /** * @throws OtherException */ function second() throw OtherException(); try $first = first(); $second = second(); catch (DemoException $ex) error_log($second); catch (Exception $ex) error_log("General exception");
The first one
catch blog access
$second but that won’t exist when
DemoException is captured. PHPStan v1.0 uses the
@throws statement to achieve this so that you are informed when your
catch the blocks refer to possibly undefined variables. Works without
@throws annotations will generally behave the same way as before.
As a result of this change, the option
polluteCatchScopeWithTryAssignments parameter has been removed. This allowed you to access the variables defined in a
try block in the following
catch; this is no longer necessary because PHPStan can now determine which variables are available.
Unused code detection
PHPStan has improved to find and report certain forms of unused code in your project. It will highlight private class properties, methods, and constants that are never called or consulted.
Their presence is almost always involuntary. If you want to keep the dead code longer, you can try commenting out or adding the
@phpstan-ignore-next-line comment to bypass the control.
Improvements to stored values
The v1.0 improves the consistency of the memory of PHPStan for the return values of the functions. It is better able to understand when a function is called a second time, providing better anticipation of identical return values.
This leads to better dead code detection when a condition is repeated multiple times. PHPStan will warn when a block becomes redundant because the execution is terminated by an earlier branch with the same condition:
if ($demo -> isActive()) return; if ($demo -> isActive()) recordDemoActivity();
The second block will never execute because the first one always ends. There is an assumption involved however:
isActive() must always return the same value throughout the service life of
$demo. In more concrete terms,
isActive() must be a pure function where repeated inputs always produce the same output.
PHPStan assumes that the functions are pure by default. In cases where they are not, you can add the
@phpstan-impure annotation in a docblock above the function definition. This will disable the return value memory for that function. You will need to use it in the example above if
isActive() might return a different value on each call, which means the first check might equal
false and let the second branch run.
/** @phpstan-impure */ public function isActive() : bool return (date("Y") === "2021");
Other rule improvements
Several new rules have been added to existing levels 1 to 6. These include checks for constants and substitution properties, trying to extend a
final class and detection of always true and always false
while loop conditions.
Types are now recursively checked for missing types. This means that PHPDoc definitions like
array<array> will not be accepted because they do not have an internal type definition. You will also need to enter the expected values of the second-level array elements, such as
In addition to checking the source content, PHPStan 1.0 examines the overall validity of
.php files too. Level 0 checks for whitespace and the nomenclature of the start and end files, as stray characters before or after the
<?php ... ?> The tag pair can cause an unexpected exit during runtime.
Performance and stability
Performance has been improved in several areas by optimizing specific operations and fixing some memory leaks. These should contribute to faster and more reliable analyzes of larger code bases.
As v1.0 is considered a formal stable release, it comes with the assurance that future minor versions (1.1, 1.2, 1.3, etc.) will be backward compatible. While moving from v0.x to v1.0 may require some configuration tweaks, you will have an easier migration path in the future.
Developers of PHPStan extensions and custom rule sets also benefit from a more stable code base that is less susceptible to change. Developer documentation has also been extended, with more accessible coverage of creating custom rules. It should be easier to get started when implementing your team’s own rules into your PHPStan analysis.
While this bodes well for future support, users and extension developers are facing some major changes when upgrading to version 1.0. Several scan configuration parameters have been renamed or changed, with
bootstrap to become
excludes_analyse replaced by
Internally, the Extensions API has been revised extensively, so many classes and methods have been changed or removed. The complete list of backward compatible changes is available in the v1.0 changelog.
PHPStan v1.0 matures the project by optimizing performance, adding new rules and correcting some detection irregularities. The evolution of PHPStan over the past few years has contributed to the larger change in the PHP community to embrace typed language concepts. The addition of features such as typed properties and union types has produced a language capable of supporting advanced external inspection, as well as detailed internal introspection via reflection.
Promotion to a semi-stable version should help encourage adoption of PHPStan across the community. Migration to the new version should be fairly straightforward for modern projects that already use a strict PHPStan rule level.
You can upgrade to PHPStan v1.0 today by running
composer require phpstan/phpstan in your projects. It is recommended that you read the release notes first to identify any breaking changes you need to address. Having additional rules in each existing level can cause your codebase to fail tests even if it passed with PHPStan v0.x. You can temporarily disable new rules by adding them to your baseline so that they are excluded from the scan results.