|
| 1 | +--- |
| 2 | +id: 80 |
| 3 | +title: "Rector 2.2: New rules for Array Docblocks" |
| 4 | +perex: | |
| 5 | + As you know, we provide an [upgrade services](https://getrector.com/hire-team) to speed up the modernization of codebases. Part of this service is getting PHPStan to level 8 with no baseline (only edge cases). |
| 6 | +
|
| 7 | + Level 6 is known for requesting more detailed types over `array`, |
| 8 | + `iterable` or `Iterator` type hints. Bare `mixed` or `array` should be replaced with explicit key/value types, e.g., `string[]` or `array<int, SomeObject>`. |
| 9 | +
|
| 10 | + At first, we did this work manually. Later, we made custom Rector rules that we kept private. |
| 11 | +
|
| 12 | + Today, we are open-sourcing these rules to help you with the same task. |
| 13 | +--- |
| 14 | + |
| 15 | +<div class="alert alert-warning mt-3 mb-5"> |
| 16 | +This feature is experimental. We look for your feedback in real projects. Found a glitch, or do you expect a different output? Let us known <a href="https://github.com/rectorphp/rector/issues">on GitHub</a>. |
| 17 | +</div> |
| 18 | + |
| 19 | +We designed these rules to avoid filling useless types like `mixed`, `mixed[]`, or `array`. If the Rector doesn't know better, it will skip these cases. We want to fill those types that humans would do to improve code readability and static analysis. |
| 20 | + |
| 21 | +<br> |
| 22 | + |
| 23 | +Let's look at a few examples that are missing detailed types and that Rector can improve now: |
| 24 | + |
| 25 | +## 1. Known Return Scalar |
| 26 | + |
| 27 | +```php |
| 28 | +function getNames(): array |
| 29 | +{ |
| 30 | + return ['John', 'Jane']; |
| 31 | +} |
| 32 | +``` |
| 33 | + |
| 34 | +<br> |
| 35 | + |
| 36 | +Now this is straightforward; it can be improved to: |
| 37 | + |
| 38 | +```diff |
| 39 | ++/** |
| 40 | ++ * @return string[] |
| 41 | ++ */ |
| 42 | + function getNames(): array |
| 43 | + { |
| 44 | + // ... |
| 45 | + } |
| 46 | +``` |
| 47 | + |
| 48 | +Why do this manually in 100s of places, if Rector can do it for you? |
| 49 | + |
| 50 | +<br> |
| 51 | + |
| 52 | +## 2. Known Return Objects |
| 53 | + |
| 54 | +Let's look at another example: |
| 55 | + |
| 56 | +```php |
| 57 | +function getUsers(): array |
| 58 | +{ |
| 59 | + $user = []; |
| 60 | + |
| 61 | + $users[] = new User('John'); |
| 62 | + $users[] = new User('Jane'); |
| 63 | + |
| 64 | + return $users; |
| 65 | +} |
| 66 | +``` |
| 67 | + |
| 68 | +<br> |
| 69 | + |
| 70 | +No-brainer: |
| 71 | + |
| 72 | +```diff |
| 73 | ++/** |
| 74 | ++ * @return User[] |
| 75 | ++ */ |
| 76 | + function getUsers(): array |
| 77 | + { |
| 78 | + // ... |
| 79 | + } |
| 80 | +``` |
| 81 | + |
| 82 | +<br> |
| 83 | + |
| 84 | +## 3. Known Shared Object Type |
| 85 | + |
| 86 | +What if there are multiple different objects that all share a single contract interface? |
| 87 | + |
| 88 | +```php |
| 89 | +final class ExtensionProvider |
| 90 | +{ |
| 91 | + public function provide(): array |
| 92 | + { |
| 93 | + return [ |
| 94 | + new FirstExtension(), |
| 95 | + new SecondExtension(), |
| 96 | + ]; |
| 97 | + } |
| 98 | +} |
| 99 | +``` |
| 100 | + |
| 101 | +<br> |
| 102 | + |
| 103 | + |
| 104 | +In a real project, we would have to open all of those classes, check parent classes and interfaces, and try to find the first common one. Now we don't have to, Rector does it for us: |
| 105 | + |
| 106 | +```diff |
| 107 | ++ /** |
| 108 | ++ * @return ExtensionInterface[] |
| 109 | ++ */ |
| 110 | + public function provide(): array |
| 111 | + { |
| 112 | + // ... |
| 113 | + } |
| 114 | +``` |
| 115 | + |
| 116 | +<br> |
| 117 | + |
| 118 | +## 4. Known `array_map()` return |
| 119 | + |
| 120 | +We can infer the type from functions like `array_map()`: |
| 121 | + |
| 122 | +```diff |
| 123 | ++/** |
| 124 | ++ * @return string[] |
| 125 | ++ */ |
| 126 | + public function getNames(array $users): array |
| 127 | + { |
| 128 | + return array_map(fn (User $user): string => $user->getName(), $users); |
| 129 | + } |
| 130 | +``` |
| 131 | + |
| 132 | +<br> |
| 133 | + |
| 134 | +## 5. Known private method types |
| 135 | + |
| 136 | +What if the method is private and is called only in a local class? We can now collect all the method calls and learn their type: |
| 137 | + |
| 138 | +```diff |
| 139 | + final class IncomeCalculator |
| 140 | + { |
| 141 | + public function addCompanyTips(): void |
| 142 | + { |
| 143 | + $this->addTips([100, 200, 300]); |
| 144 | + } |
| 145 | + |
| 146 | + public function addPersonalTips(): void |
| 147 | + { |
| 148 | + $this->addTips([50, 150]); |
| 149 | + } |
| 150 | + |
| 151 | ++ /** |
| 152 | ++ * @param int[] $tips |
| 153 | ++ */ |
| 154 | + private function addTips(array $tips): void |
| 155 | + { |
| 156 | + } |
| 157 | + } |
| 158 | +``` |
| 159 | + |
| 160 | +...and many more. Right now, the initial set contains **[15 rules](https://github.com/rectorphp/rector-src/blob/main/src/Config/Level/TypeDeclarationDocblocksLevel.php)**, and we plan to extend it further. Got an idea for an obvious rule that you keep doing manually and is not covered yet? Let us know. |
| 161 | + |
| 162 | +<br> |
| 163 | + |
| 164 | +## Smart Override |
| 165 | + |
| 166 | +Rector is smart enough to keep detailed types, but override those dummy ones: |
| 167 | + |
| 168 | +```diff |
| 169 | + /** |
| 170 | +- * @return mixed[] |
| 171 | ++ * @return string[] |
| 172 | + */ |
| 173 | + function getNames(): array |
| 174 | + { |
| 175 | + return ['one', 'two'] |
| 176 | + } |
| 177 | +``` |
| 178 | + |
| 179 | +<br> |
| 180 | + |
| 181 | +## Start with Levels |
| 182 | + |
| 183 | +The best way to start using this set is via [level feature](/documentation/levels). Add this single line to your `rector.php` config: |
| 184 | + |
| 185 | +```php |
| 186 | +<?php |
| 187 | + |
| 188 | +use Rector\Config\RectorConfig; |
| 189 | + |
| 190 | +return RectorConfig::configure() |
| 191 | + ->withTypeCoverageDocblockLevel(0); |
| 192 | +``` |
| 193 | + |
| 194 | +<br> |
| 195 | + |
| 196 | +And take it one level at a time: |
| 197 | + |
| 198 | +```diff |
| 199 | + <?php |
| 200 | + |
| 201 | + use Rector\Config\RectorConfig; |
| 202 | + |
| 203 | + return RectorConfig::configure() |
| 204 | +- ->withTypeCoverageDocblockLevel(0); |
| 205 | ++ ->withTypeCoverageDocblockLevel(1); |
| 206 | +``` |
| 207 | + |
| 208 | +<br> |
| 209 | + |
| 210 | +In a rush or feeling lucky? Add full set: |
| 211 | + |
| 212 | +```php |
| 213 | +<?php |
| 214 | + |
| 215 | + use Rector\Config\RectorConfig; |
| 216 | + |
| 217 | +return RectorConfig::configure() |
| 218 | + ->withPreparedSets(typeDeclarationDocblocks: true); |
| 219 | +``` |
| 220 | + |
| 221 | +<br> |
| 222 | + |
| 223 | +We've put a lot of work into making rules balanced and reliable, but it's still in the early testing phase. Give it a go, let us know how slim your baseline got after a single Rector run. |
| 224 | + |
| 225 | +<br> |
| 226 | + |
| 227 | +Happy coding! |
0 commit comments