Codementor Events

Modern PHPDoc Annotations

Published Apr 02, 2020
Modern PHPDoc Annotations

-> imported from https://suckup.de/2020/02/modern-phpdoc-annotations/

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;

https://blog.jetbrains.com/phpstorm/2019/09/phpstorm-2019-2-2-is-released/

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:

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/

Discover and read more posts from Lars Moelleken
get started
post commentsBe the first to share your opinion
Show more replies