# Modern PHPDoc Annotations

[Modern PHPDoc Annotations](http://suckup.de/2020/02/modern-phpdoc-annotations/?pk_campaign=feed&pk_kwd=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](http://manual.phpdoc.org/HTMLSmartyConverter/HandS/phpDocumentor/tutorial_phpDocumentor.pkg.html)“ 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](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](https://wiki.php.net/rfc/static_return_type)) for the current class. Because of „[late static binding](https://www.php.net/manual/en/language.oop5.late-static-bindings.php)“ 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://cdn.hashnode.com/res/hashnode/image/upload/v1664948317678/_41AwRYzZ.png)

[https://blog.jetbrains.com/phpstorm/2019/09/phpstorm-2019-2-2-is-released/](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](https://psalm.dev/r/922d4ba5b1)

– PHPStan: [https://phpstan.org/r/ce657ef4-9f18-46a1-b21a-e51e3a0e6d2d](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](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](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](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](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://blog.jetbrains.com/phpstorm/2019/02/new-phpstorm-meta-php-features/)
*   [https://gist.github.com/voku/477e5c22a67c2d37ca42150d94e371ea](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://docs.phpdoc.org/latest/guides/types.html)
*   [https://scrutinizer-ci.com/docs/tools/php/php-analyzer/guides/annotating\_code](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/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://github.com/php-fig/fig-standards/blob/master/proposed/phpdoc.md#appendix-a-types)
*   [https://psalm.dev/docs/annotating\_code/supported\_annotations/](https://psalm.dev/docs/annotating_code/supported_annotations/)

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1664948319919/lA8Mf7oZJ.gif)
