Sponsored PHPStan feature: require-extends and require-implements phpDoc
I spent a few days implementing @phpstan-require-extends
and @phpstan-require-implements
semantics in PHPStan.
People using psalm might find this feature familiar as it is already supported in psalm.
The idea is to define at interface or trait level, which requirements the usage class has to fulfill.
The development of this feature was possible, thanks to sponsoring by Pixel & Tonic, the team behind Craft CMS. In addition Ondřej Mirtes provided excellent feedback and guidance during the development.
The feature was implemented in separate Pull Requests, which built on top of each other:
- Support for
require-extends
andrequire-implements
in phpdoc-parser - Plumbing for
@phpstan-require-extends
and@phpstan-require-implements
- Implement ClassReflectionExtension
- Implement
require-extends
andrequire-implements
rules require-extends
should not error on interfaces- Support
require-extends
andrequire-implements
in result cache
… which fixed the following issues:
- PHP^8.2: Access to an undefined property when type hinting interface with
@property
@phpstan-require-use
for requiring implementors/subclasses to use certain traits- PHP8.2 - Interface property annotation not found inside class
… and eventually became the headlining feature of PHPStan 1.10.56.
If you are in need of a certain feature or bugfix in PHPStan, Rector or related tooling, please get in touch.
@phpstan-require-extends
trait-example
It’s best described with an example, so have a look at the psalm documentation example:
/**
* @phpstan-require-extends DatabaseModel
*/
trait SoftDeletingTrait {
// useful but scoped functionality, that depends on methods/properties from DatabaseModel
}
With this declaration we define that a class which uses the SoftDeletingTrait
has to extend the DatabaseModel
class.
If not, PHPStan will report an error. See the full example running in the PHPStan sandbox
@phpstan-require-extends
interface-example
Similar to what was shown above, the same is possible with interfaces:
/**
* @phpstan-require-extends DatabaseModel
*/
interface SoftDeletingMarkerInterface {
}
With this declaration we define that a class which implements the SoftDeletingMarkerInterface
has to extend the DatabaseModel
class.
When using interfaces we can achieve more though, because the interface type can be used as e.g. a parameter-type:
/**
* @phpstan-require-extends DatabaseModel
*/
interface SoftDeletingMarkerInterface {
}
class DatabaseModel {
public string $tableName;
public function softDelete():void { /* … */ }
}
// its allowed to call lookup properties and call method of the require-extends type, when using the interface-type
function runSoftDelete(SoftDeletingMarkerInterface $model):void {
$tableName = $model->tableName;
$model->softDelete();
// …
}
Since its only valid to implement the SoftDeletingMarkerInterface
when extending the DatabaseModel
class,
PHPStan will not error when accessing public properties or calling public methods of DatabaseModel
, based on the SoftDeletingMarkerInterface
type.
See the full example running in the PHPStan sandbox
NOTE: Looking up properties/calling methods on the interface type is currently only possible in PHPStan. I have opened a dedicated psalm feature request #10538 for discussion.
@phpstan-require-implements
trait-example
Similar to the @phpstan-require-extends
trait example, its supported to use @phpstan-require-implements
on traits:
/**
* @phpstan-require-implements DatabaseModelInterface
*/
trait SoftDeletingTrait {
// useful but scoped functionality, that depends on methods/properties from DatabaseModel
}
With this declaration we define that a class which uses the SoftDeletingTrait
has to implement a DatabaseModelInterface
interface.
See the full example running in the PHPStan sandbox
psalm compatibility
As with most phpDoc annotations, PHPStan will happily accept a psalm-prefixed @psalm-require-implements
.
NOTE: Looking up properties/calling methods on the interface type is currently only possible in PHPStan. I have opened a dedicated psalm feature request #10538 for discussion.
read more
The new feature is mentioned in the PHPStan docs and PHPStan blog and was recently announced by Ondřej Mirtes on Twitter and mastodon.
Future scope: generics support
We plan to support generics in these phpDoc annotations in the future, see the described idea by Ondřej Mirtes. If you are interested in this or any other feature addition, please considering sponsoring it.
Found a bug? Please help improve this article.