PHPStan is ready for PHP 8.2!

December 8, 2022 · 5 min read

Today is the day of PHP 8.2 becoming generally available! It’s a pretty quiet year for adjustments needed in PHPStan which is a welcome change after making sure it fully works with PHP 8.1 for about six months up until the end of April of this year.

PHPStan has been ready for PHP 8.2 for over two months already. Last additions were included in PHPStan 1.8.7 on October 4th.

As per usual I’m gonna go through some of the interesting changes related to PHP 8.2 and dive into detail what they mean for our static analysis engine.

Dynamic properties deprecated #

I believe this RFC moves the language in the right direction, because when you access a property that isn’t declared on a class, you can’t be sure it really exists and that your code is going to work. This is how PHPStan treats analysed code since the very beginning.

Since PHP 8.2 the following code is marked as deprecated:

class Foo


$foo = new Foo();
$foo->test = 'oh my';
echo $foo->test;

PHPStan always reported this code as being wrong, but you were able to work around it with isset() and other similar techniques:

function (Foo $foo): void {
    if (isset($foo->test)) {
        // ...

But with PHP 8.2 and onward, PHPStan knows that this isset() is always going to report false.

There are some internal changes in PHPStan to make sure it behaves consistently. For example magic @property PHPDocs are not going to have any effect in classes that cannot have dynamic properties.

Disjunctive Normal Form Types #

This mysteriously-sounding RFC makes it possible to use native intersection types from PHP 8.1 in more scenarios. They can now be used in union types, which for example means they can become nullable: (A&B)|null.

Full credit here goes to Jarda Hanslík who implemented this in Roave/BetterReflection, the library PHPStan relies on for all its reflection needs. Talking of BetterReflection, he actually implemented all the changes needed there for PHP 8.2 to be fully supported, which is awesome!

Updated function signatures #

PHPStan uses several sources about internal PHP functions and methods. There’s functionMap.php, there’s jetbrains/phpstorm-stubs, and there’s phpstan/php-8-stubs.

The last one is extracted from official stubs provided by php-src. And because these change from version to version, there has to be a way to express those differences.

Such stubs now come with special #[Since] and #[Until] attributes and the definitions are cleaned up before being interpreted by PHPStan:

function restore_error_handler() : bool
function restore_error_handler() : true

Readonly classes #

Readonly classes mark all their properties as being readonly. PHPStan applies all the same limitations to readonly classes it already applies to readonly properties:

readonly class Foo

    // Readonly property must have a native type.
    public $foo;

    // Class Foo has an uninitialized readonly property $bar. Assign it in the constructor.
    public int $bar;

    public function setBar(int $bar): void
        // Readonly property Foo::$bar is assigned outside of the constructor.
        $this->bar = $bar;


$foo = new Foo();

// Readonly property Foo::$bar is assigned outside of its declaring class.
$foo->bar = $bar;

Do you like PHPStan and use it every day? Consider supporting further development of PHPStan on GitHub Sponsors. I’d really appreciate it!

© 2016–2024 Ondřej Mirtes