Allowed Subtypes
PHP language doesn’t have a concept of sealed classes - a way to restrict class hierarchies and provide more control over inheritance. So any interface or non-final class can have an infinite number of child classes. But PHPStan provides an extension type to tell the analyzer the complete list of allowed child classes.
The implementation is all about applying the core concepts like reflection so check out that guide first and then continue here.
This is the interface your extension needs to implement:
namespace PHPStan\Reflection;
use PHPStan\Type\Type;
interface AllowedSubTypesClassReflectionExtension
{
public function supports(ClassReflection $classReflection): bool;
/** @return array<Type> */
public function getAllowedSubTypes(ClassReflection $classReflection): array;
}
The implementation needs to be registered in your configuration file:
services:
-
class: MyApp\PHPStan\MySubtypesExtension
tags:
- phpstan.broker.allowedSubTypesClassReflectionExtension
When you implement this extension, it has a couple of effects:
- Smarter type inference when subtracting types from each other
- Error reporting when a disallowed class implements the restricted interface/extends a restricted parent class
An example #
Let’s say you have a class Foo
and you want only Bar
and Baz
to be its child classes:
namespace MyApp\PHPStan;
use PHPStan\Reflection\AllowedSubTypesClassReflectionExtension;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Type\ObjectType;
class MySubtypesExtension implements AllowedSubTypesClassReflectionExtension
{
public function supports(ClassReflection $classReflection): bool
{
return $classReflection->getName() === Foo::class;
}
public function getAllowedSubTypes(ClassReflection $classReflection): array
{
return [
new ObjectType(Bar::class),
new ObjectType(Baz::class),
];
}
}
With this extension in place, let’s consider this code:
function foo(Foo $foo): void
{
if ($foo instanceof Bar) {
return;
}
// without the extension, $foo could be "any Foo, but not Bar"
// with the extension, the only remaining possible type is Baz
\PHPStan\dumpType($foo); // Baz
}
And if you try to extend Foo by a disallowed class:
// Error: 'Type Lorem is not allowed to be a subtype of Foo.'
class Lorem extends Foo
{
}
This extension type can be used simply to hardcode a set of classes. But it can also be used to read custom PHPDocs or class attributes.