Phpstan Phpstorm



Menu

PHPStan plugin for PhpStorm This plugin adds dedicated functionality for PHPStanto PhpStorm. The plugin is under active development and will be bundled into PhpStorm. PHPStorm will probably complain about file sync being slow when you start it - that's because we did not install inotify yet, which PHPStorm can utilize. To do so, type: sudo apt-get install inotify-tools. PHPstan support. Supports static code analysis with phpstan. ApiGen allows automatically generating documentation from specifically formatted comments. It's easy to use, supports traits, allows fuzzy searching for classes and highlighting docblocks using Markdown. PhpStorm has two types of autocompletion: structural.

December 2, 2019 · 13 min read

Two years ago I wrote an impactful article on union and intersection types. It helped the PHP community to familiarize themselves with these concepts which eventually led to intersection types support in PhpStorm.

I wrote that article because the differences between unions and intersections are useful and important for static analysis, and developers should be aware of them.

Today I have a similar goal. Generics are coming to PHPStan 0.12 later this week, I want to explain what they’re all about, and get everyone excited.

Phpstorm

Infinite number of signatures #

When we’re declaring a function, we’re used to attach a single signature to it. There’s no other option. So we declare that the function accepts an argument of a specific type, and also returns a specific type:

These types are set in stone. If you have a function that returns different types based on argument types passed when calling the function, you’d have to resort to returning a union type, or a more general type like object or mixed:

This is not ideal for static analysis. It’s not enough information to keep the code type-safe. We always want to know the exact type. And that’s what generics are for. They offer generating infinite number of signatures for functions and methods based on rules developers can define themselves.

Type variables #

These rules are defined using type variables. Other languages that have generics also use this term. In PHPDocs we annotate them with the @template tag. Consider a function that returns the same type it accepts:

The type variable name can be anything, as long as you don’t use an existing class name.

You can also limit which types can be used in place of the type variable with an upper bound using the of keyword:

Only objects of classes extending Exception will be accepted and returned by this function.

Class names #

If you want to involve a class name in the type resolution, you can use the class-string pseudotype for that:

If you then call findEntity(Article::class, 1), PHPStan will know that you’re getting an Article object or null!

Phpstan Phpstorm Docker

Marking the return type as T[](for example for a findAll()function) would infer the return type as an array of Articles.

Class-level generics #

Up until this point, I’ve written only about function-level or method-level generics. We can also put @template above a class or an interface:

Phpstan phpstorm docker

And then reference the type variable above properties and methods:

The types of the Collection can be specified when you’re typehinting it somewhere else:

When implementing a generic interface or extending a generic class, you have two options:

  • Preserve the genericness of the parent, the child class will also be generic
  • Specify the type variable of the interface/parent class. The child class will not be generic.
Phpstan Phpstorm

Preserving the genericness is done by repeating the same @template tags above the child class and passing it to @extends and @implements tags:

If we don’t want our class to be generic, we only use the latter tags:

Covariance & contravariance #

There’s one more use case generics solve, but first I need to explain these two terms. Covariance and contravariance describe relationships between related types.

When we describe a type being covariant it means it’s more specific in relation to its parent class or an implemented interface.

A type is contravariant if it’s more general in relation to its child class or an implementation.

All of this is important because languages need to enforce some constraints in parameter types and return types in child classes and interface implementations in order to guarantee type safety.

Parameter type must be contravariant #

Let’s say we have an interface called DogFeeder, and wherever DogFeeder is typehinted, the code is free to pass any Dog to the feed method:

Phpstorm Phpstan No Rules Detected

If we implement a BulldogFeeder that narrows the parameter type (it’s covariant, not contravariant!), we have a problem. If we pass the BulldogFeeder into the feedChihuahua()function, the code would crash, because BulldogFeeder::feed() does not accept a chihuahua:

Fortunately, PHP does not allow us to do this. But since we’re still writing a lot of types in PHPDocs only, static analysis has to check for these errors.

On the other hand, if we implement DogFeeder with a more general type than a Dog, let’s say an Animal, we’re fine:

This class accepts all dogs, and on top of that all animals as well. Animal is contravariant to Dog.

Return type must be covariant #

With return types it’s a different story. Return types can be more specific in child classes. Let’s say we have an interface called DogShelter:

When a class implements this interface, we have to make sure that whatever it returns, it can still bark(). It would be wrong to return something less specific, like an Animal, but it’s fine to return a Chihuahua.

These rules are useful, but sometimes limiting #

Sometimes I’m tempted to have a covariant parameter type even if it’s forbidden. Let’s say we have a Consumer interface for consuming RabbitMQ messages:

Phpstan For Phpstorm

When we’re implementing the interface to consume a specific message type, we’re tempted to specify it in the parameter type:

Which isn’t valid because the type isn’t contravariant. But we know that this consumer will not be called with any other message type thanks to how we’ve implemented our infrastructure code.

What can we do about this?

One option is to comment out the method in the interface and ignore the fact that we’d be calling an undefined method:

But that’s dangerous territory.

There’s a better and completely type-safe way thanks to generics. We have to make the Consumer interface generic and the parameter type should be influenced by the type variable:

The consumer implementation specifies the message type using the @implements tag:

We can choose to omit the method PHPDoc and PHPStan will still know that $message can only be SendMailMessage. It will also check all calls to SendMailMessageConsumer to report whether only SendMailMessagetype is passed into the method.

If you use an IDE and want to take advantage of autocompletion, you can add @param SendMailMessage $message above the method.

This way is totally type-safe. PHPStan will report any violations that don’t adhere to the type system. Even Barbara Liskov is happy with it.

Phpstan Phpstorm

IDE compatibility #

Unfortunately the current generation IDEs do not understand @template and related tags. You can choose to use type variables only inside @phpstan-prefixed tags, and leave non-prefixed tags with types that IDEs and other tools understand today:

Type-safe iterators and generators #

Some built-in PHP classes are generic in their nature. To safely use an Iterator, you should specify what keys and values it contains. All of these examples can be used as types in phpDocs:

Generator is a complex PHP feature. Besides iterating over the generator and getting its keys and values, you can also send values back to it and even use the return keyword besides yield in the same method body. That’s why it needs a more complex generic signature:

And PHPStan can type-check all of that. Try it out in the on-line PHPStan playground:

Your turn! #

Phpstan Phpstorm

Now that you understand what generics are for, it’s up to you to come up with possible uses inside the codebases you work with. They allow you to describe more specific types coming to and from functions and methods. So anywhere you currently use mixed and object but could take advantage of more precise types, generics could come in handy. They bring type safety to otherwise unapproachable places.

PHPStan 0.12 with generics support (and much much more!) is coming out this week.