Sitemap

Modernizing PHPDoc Array annotations with Rector

3 min readOct 17, 2025
Press enter or click to view image in full size
Before vs After Rector 🏘️

In one of my project, I’m working with Symfony Serializer, phpstan/phpdoc-parser and phpdocumentor/reflection-docblock to handle serialization of complex objects.
These tools rely on PHPDoc annotations to understand data types, particularly for arrays of objects.

In PHPDocs, there are (at least) 2 possible syntaxes for array values:

/**
* @param Foo[] $arrayOfFoos
*/
/**
* @param array<Foo> $arrayOfFoos
*/

Since 8.1 we have the concept of list in PHP.
That makes Foo[] ambiguous because is it could be:

  • a list
  • an array where the key is important

list<Foo> is almost the same as array<Foo> (implicit int keys) or array<int, Foo> with a slight difference you can be sure that integers in keys consecutive numbers from 0 up.

But here’s where things get interesting: with recent updates to tools like Psalm, PHPStan and Symfony’s Reflection-based Serializer handler, this Type[]|array syntax started causing issues. These tools began struggling with the ambiguity - they couldn't reliably determine whether we meant a strict list or a more flexible array structure.

Exploring Options

I therefore had many solution to resolve my issue:

Faced with hundreds of annotations needing updates across my codebase, I had three potential paths:

1. Manual transformation β€” Copy-paste and find-replace across hundreds of files. Let’s be honest, this would be error-prone and mind-numbing work. Hard pass.

2. AI-assisted refactoring β€” Ask Claude or another AI to parse my entire codebase. While tempting, my project has thousands of files that would exceed token limits, and I wanted a repeatable, version-controllable solution.

3. Rector rule β€” Write once, run everywhere, with the confidence that comes from automated refactoring tools. This felt like the best approach.

The choice was obvious. Plus, creating a Rector rule means other developers facing the same challenge can benefit from the work.

Plot twist: While writing this article, I discovered that PHP CS Fixer already provides a similar rule called phpdoc_array_type that handles these transformations! Sometimes reinventing the wheel teaches you how wheels work ✨

A custom Rector Rule

So, instead of manually transforming hundreds of annotations in my project, I created a custom Rector rule that automates this task.
Here’s how it works:

The TransformArrayTypeAnnotationRector Rule

final class TransformArrayTypeAnnotationRector extends AbstractRector
{
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition(
'Transform array type annotations from "Type[]|array" to "array<int, Type>"',
[
new CodeSample(
'/**
* @var \App\Entity\Environment[]|array
*/
private $environments;',
'/**
* @var array<int, \App\Entity\Environment>
*/
private $environments;'
),
]
);
}

public function getNodeTypes(): array
{
return [Property::class, ClassMethod::class, Class_::class];
}

public function refactor(Node $node): ?Node
{
$docComment = $node->getDocComment();
if (null === $docComment) {
return null;
}
$originalText = $docComment->getText();
$transformedText = $this->transformDocComment($originalText);
if ($originalText === $transformedText) {
return null;
}
$node->setDocComment(new Doc($transformedText));
return $node;
}
}

Configuration and Usage

In my rector.php file, I registered my custom rule:

<?php

use App\Utils\Rector\TransformArrayTypeAnnotationRector;
use Rector\Config\RectorConfig;

return static function (RectorConfig $rectorConfig): void {
$rectorConfig->paths([
__DIR__.'/src',
__DIR__.'/tests',
]);
$rectorConfig->rule(TransformArrayTypeAnnotationRector::class);
};

Results

Running this rule on my project automatically transformed 243 files in seconds:

./vendor/bin/rector
243/243 [β–“β–“β–“β–“β–“β–“β–“β–“β–“β–“β–“β–“β–“β–“β–“β–“β–“β–“β–“β–“β–“β–“β–“β–“β–“β–“β–“β–“] 100%

243 files with changes
Applied rules:
* TransformArrayTypeAnnotationRector
[OK] 243 files have been changed by Rector

Each transformation follows this pattern:

/**
- * @var \App\Dto\Output\Category[]|array
+ * @var array<int, \App\Dto\Output\Category>
*/

❀️ Love on your keyboards.

Sources

PHPStan (Sept 2025 ) PHPDoc Types.
https://phpstan.org/writing-php-code/phpdoc-types

PHP (Feb 2022) array_is_list.
https://www.php.net/manual/en/function.array-is-list.php

PHP Coding Standards Fixer (Feb 2019) PHPDoc array<T> type must be used instead of T[].
https://cs.symfony.com/doc/rules/phpdoc/phpdoc_array_type.html

Resources

Claude AI, https://claude.ai
Helped with writing and text refinement.

Imagen, https://deepmind.google/models/imagen/
Generated the very accurate article’s cover illustration.

--

--

Kevin Wenger
Kevin Wenger

Written by Kevin Wenger

Swiss Web Developer & Open Source Advocate @antistatique | @Webmardi organizer | Speaker | Author | Drupal 8 Core contributor