Reputation: 37
My question to get code completion working in a set of classes that I have.
+ abstract Model :: make(array{...})
+- SubModel
+- AnotherSubModel
+- ...
I have an abstract Model class, and several child classes extending it. The Model class has a make function which accepts an array of properties and returns a new static instance of whichever child class it's called on. It uses reflection to assign each property from the array to the correct property in the child class.
Sample Code:
abstract class Model {
public static function make(array $props): static
{
// ... magic
}
}
class SubModel extends Model {
protected ?string $subModelProp = null;
}
$instance = SubModel::make(['subModelProp' => 'Hello, World']);
// I'd like code completion in the IDE to tell me notAProp isn't valid:
$instanceTwo = SubModel::make(['notAProp' => 'Whoops']);
I'm trying to figure out how to get completion to work in PhpStorm so that only valid array keys are passed. Each sub model has a distinct list of properties which can be set. Ideally I'd like to be able to declare the shape of the properties array in the child class, and have the IDE alert me if an invalid property is in the array, a property is missing, and just general completion support to know which properties are allowed.
The phpstan docs for array shapes, e.g. array{'foo': int, "bar": string}
seems like an ideal way of describing the shape my input array needs to match the associated model class. However, I can't figure out how/where to declare the array shape in my sub class without re-implementing the make method in every sub class.
Upvotes: 1
Views: 41
Reputation: 5661
This is easy to achieve with generics. Some resources first:
First you need to make your abstract base class generic with @template
tags. And use the T
type as $props
parameter type:
/**
* @template T of array<string, mixed>
*/
abstract class Model {
/**
* @param T $props
*/
public static function make(array $props): static
{
// ... magic
}
}
Then the child class extending Model
has to use @extends
PHPDoc tag from generics to specify the T
from Model
with the array shape you want to allow to pass to the make
method:
/**
* @extends Model<array{
* subModelProp: string,
* }> */
class SubModel extends Model {
}
When calling the make
method, PHPStan can now verify the keys and values passed as an argument:
// Error:
// Parameter #1 $props of static method Model<array<string, string>>::make() expects array{subModelProp: string}, array{notAProp: 1} given.
// Array does not have offset 'subModelProp'.
SubModel::make(['notAProp' => 1]);
// Error:
// Parameter #1 $props of static method Model<array<string, string>>::make() expects array{subModelProp: string}, array{subModelProp: 1} given.
// Offset 'subModelProp' (string) does not accept type int.
SubModel::make(['subModelProp' => 1]);
// OK - No errors
SubModel::make(['subModelProp' => 'foo']);
Here's all the code on PHPStan's on-line playground: https://phpstan.org/r/5a02a2c7-e1a4-4a04-ae77-73788fbebb09
Upvotes: 0