Over the last few days I am working on a new PHPStan capability, which allows PHPStan to use type information from analyzing a class-constructor with the goal to improve results when later on analyzing instance methods or property hook bodies.

This feature will be available starting with PHPStan 2.1.12. In case you are curious you can play with it in the PHPStan Playground right now. The implementation itself can be inspected in pull request #3930.

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

Remember class_exists(), function_exists()

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

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

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

class User
{
   public function __construct() {
      if (!function_exists('some_unknown_function')) {
         throw new \LogicException();
      }
   }

   public function doFoo(): void
   {
      some_unknown_function();
   }
}

Remember global constants

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

class HelloWorld
{
   public function __construct()
   {
      if (!defined('REMEMBERED_FOO')) {
         throw new LogicException();
      }
      if (!is_string(REMEMBERED_FOO)) {
         throw new LogicException();
      }
   }

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

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

Remember class property types

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

readonly property types

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

class User
{
   public string $name {
      get {
         // previously we only knew $this->type is `int`.
         // new: we know the type of $this->type is `1|2`
         return $this->name . $this->type;
      }
   }

   private readonly int $type;

   public function __construct(string $name) {
      $this->name = $name;
      if (rand(0,1)) {
         $this->type = 1;
      } else {
         $this->type = 2;
      }
   }
}

typed properties and the uninitialized state

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

With this knowledge we are able to tell whether isset(), empty() or ?? is redundant.

class User
{
   private string $string;

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

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

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

Related PHPStan issues:

Found a bug? Please help improve this article.


<
Previous Post
Thank You
>
Blog Archive
Archive of all previous blog posts