We will start very simple with PhpStorm and default PHPDoc, then we will increase the complexity step by step until we have auto-completion for array keys directly from the database with generics, immutable and type safety support.
1.0 PhpStorm & auto-generate PHPDoc blocks
„For documentation comments, PhpStorm provides completion that is enabled by default. PhpStorm creates stubs of „PHPDoc blocks“ when you type the /** opening tag and press Enter, or press Alt+Insert and appoint the code construct (a class, a method, a function, and so on) to document. Depending on your choice, PhpStorm will create the required tags or add an empty documentation stub.“ –
https://www.jetbrains.com/help/phpstorm/phpdoc-comments.html
Code:
/** * @param array $row * * @return array */ abstract function formatRow(array $row): array;
1.1 Return $this|static|self
It‘s quite annoying that php itself currently only have „self“ as return type (https://wiki.php.net/rfc/static_return_type) for the current class. Because of „late static binding“ you can use „static“ in your code to refer to the class a method was actually called on, even if the method is inherited. But in PHPDoc you can already use:
- @return $this: if you really return $this (e.g. for fluent interface)
- @return static: refer to the class a method was actually called on
- @return self: refer to the class a method was written in
Code:
/** * @return static */ abstract function getFoo(): self;
1.2 New (and not that new) Array Syntax
PhpStorm and (PHPStan & Psalm) are supporting some new (and some not that new) array syntax for PHPDoc types, but for now PhpStorm will not auto-generate this types.
Examples:
- int[]: an array with only INT values – [1, 4, 6, 8, 9, …]
- array<int, int>: an array with only INT values – [4 => 1, 8 => 4, 12 => 6, …]
- string[]: an array with only STRING values – [„foo“, „bar“, …]
- array<int, string>: an array with only STRING values – [4 => „foo“, 8 => „bar“, …]
- Order[]: an array with only „Order“-Object values – [Order, Order, …]
- array<int|string, Order>: an array with INT or STRING as key and „Order“-Object values – [4 => Order, ‘foo‘ => Order, …]
- array<int|string, mixed>: an array with INT or STRING as key and mixed as values – [1 => 1, 4 => „foo“, 6 => \stdClass, …]
- array<int, array<int, string>>: an array with INT as key and and an array (with INT as key and string as value) as values – [1 => [1 => „foo“], 4 => [1 => 4], …]
- array<int, string[]>: an array with INT as key and and an array (with INT as key and string as value) as values – [1 => [„foo“, „lall“], 4 => [„öäü“, „bar“], …]
- array{output: string, debug: string}: an array with the key “output” and “debug”, the values are STRING values – [‘output’ => ‘foo’, ‘debug’ => ‘bar’]
- array<int, array{output: string, debug: string}>: an array with the key “output” and “debug”, the values are STRING values – [1 => [‘output’ => ‘foo’, ‘debug’ => ‘bar’], 3 => [‘output’ => ‘foo’, ‘debug’ => ‘bar’], …]
Examples (@psalm-* || @phpstan-*): PHPStan can also use “psalm-*” prefixed annotations and Psalm understands “phpstan-*” annotations.
- list<array{output: string, debug: string}>: an array with the key “output” and “debug”, the values are STRING values – [0 => [‘output’ => ‘foo’, ‘debug’ => ‘bar’], 1 => [‘output’ => ‘foo’, ‘debug’ => ‘bar’], …]
list: represents continuous, integer-indexed arrays (always start from index zero) like: [“red”, “yellow”, “blue”]
Live-Examples:
– Psalm: https://psalm.dev/r/922d4ba5b1
– PHPStan: https://phpstan.org/r/ce657ef4-9f18-46a1-b21a-e51e3a0e6d2d
Code:
/** * @param array<int|string, mixed> $row * * @return array<int|string, mixed> */ abstract function formatRow(array $row): array;
PhpStorm support?: Sadly PhpStorm did not have good support for these types, so that you often have to add „@psalm-*“ PHPDoc comments. For example PhpStorm will accept “array<int, Order>” but PhpStorm will not understand the PHPDoc, so that you need to add e.g. “@param Order[] $order” and “@psalm-param array<int, Order> $order”.
Examples for PhoStorm + PHPStan || Psalm:
/**
* @param Order[] $order
* @psalm-param array<int, Order> $order
*
* @return void
*/
public function fooOrder($order): void { ... }
// you could also use "..." here
/**
* @param Order ...$order
*
* @return void
*/
public function fooOrder(Order ...$order): void { ... }
/**
* @param int $foo_id
*
* @return Foo[]|Generator
* @psalm-return Generator&iterable<Foo>
*/
abstract function fetchYieldByFoo($foo_id): Generator;
1.3 Dynamic Autocompletion (+ data from your database) via deep-assoc-completion
If you have a method e.g. “formatRow($row)” you can use “getFieldArray()[0]” (data from the database – you have to connection the IDE with your database and your queries need to be analyzable by PhpStorm (take a look at the next screenshot) and combine static data from “getHeaderFieldArray()”, so that you have auto-completion from different sources.
Code:
/** * @param array<int|string, mixed> $row = $this->getFieldArray()[0] + $this->getHeaderFieldArray() * * @return array<int|string, mixed> */ abstract function formatRow(array $row): array;
more information + examples: https://github.com/klesun/deep-assoc-completion
1.4 Immutability Check via Static Code Analyses (via psalm)
And there is even more. :) You can add PHPDoc annotation that will check if you really use immutable classes or at least methods. Please read more here: https://psalm.dev/articles/immutability-and-beyond
Code:
/** * @param array<int|string, mixed> $row = $this->getFieldArray()[0] + $this->getHeaderFieldArray() * * @return array<int|string, mixed> * * @psalm-mutation-free */ abstract function formatRow(array $row): array;
Live-Example:
– Psalm: https://psalm.dev/r/5bac0a9a07
1.5 Generics in PHP via Static Code Analyses
We can also use Generics via code annotations. PHPStan & Psalm both support it, but Psalm’s support is more feature complete and both tools can use the „@psalm-“-syntax. Here comes some simple examples.
array_last: Will return the last array element from the $array (type: TLast) or the $fallback (type: TLastFallback). We tell the function that the types comes from the input parameters and that the input is an array of TLast or TLastFallback from the fallback.
/** * @param array<mixed> $array * @param mixed $fallback <p>This fallback will be used, if the array is empty.</p> * * @return mixed|null * * @template TLast * @template TLastFallback * @psalm-param TLast[] $array * @psalm-param TLastFallback $fallback * @psalm-return TLast|TLastFallback */ function array_last(array $array, $fallback = null) { $key_last = \array_key_last($array); if ($key_last === null) { return $fallback; }
return $array[$key_last]; }
array_first: Will return the first array element from the $array (type: TFirst) or the $fallback (type: TFirstFallback). We tell the function that the types comes from the input params and that the input is an array of TFirst or TFirstFallback from the fallback.
/** * @param array<mixed> $array * @param mixed $fallback <p>This fallback will be used, if the array is empty.</p> * * @return mixed|null * * @template TFirst * @template TFirstFallback * @psalm-param TFirst[] $array * @psalm-param TFirstFallback $fallback * @psalm-return TFirst|TFirstFallback */ function array_first(array $array, $fallback = null) { $key_first = array_key_first($array); if ($key_first === null) { return $fallback; }
return $array[$key_first]; }
So we can define „Templates“ and map input arguments on that types, this can be even more complex if you use it in a class context and you map the „Templates“ on class properties. But the logic will be the same.
Here is a more complex example: https://github.com/voku/Arrayy/blob/master/src/Collection/CollectionInterface.php
PhpStorm support?: Noop, sadly we need to hack this via „PHPSTORM_META“, so here is an example:
- override(\array_filter(0), type(0)); // suppose first parameter type is MyClass[] then return type of array_filter will be MyClass[]
- override(\array_reduce(0), elementType(0)); // suppose first parameter type is MyClass[] then return type of array_reduce will be MyClass
Read more here:
- https://blog.jetbrains.com/phpstorm/2019/02/new-phpstorm-meta-php-features/
- https://gist.github.com/voku/477e5c22a67c2d37ca42150d94e371ea
2.0 Resume
It‘s not perfect, and type check and auto-completion only with PHPDoc is not really what I expected for the year 2020. But it‘s working and I hope PhpStorm will bring more support for the new types annotations in the future.
More Links:
- https://docs.phpdoc.org/latest/guides/types.html
- https://scrutinizer-ci.com/docs/tools/php/php-analyzer/guides/annotating_code
- https://github.com/klesun/deep-assoc-completion/blob/master/docs/deep-keys-overview.md
- https://github.com/php-fig/fig-standards/blob/master/proposed/phpdoc.md#appendix-a-types
- https://psalm.dev/docs/annotating_code/supported_annotations/