Native types stop at array, string and int. Docblocks, read by both analyzers and ignored by the engine, say more: list<T>, array<K, V>, shapes such as array{sku: string}, non-empty-string, positive-int, int<0, 100>, class-string<T>, @template generics (Subsection 4.11.15), @phpstan-type aliases and @phpstan-assert for narrowing functions.
<?php
/** @phpstan-type Line array{sku: non-empty-string, qty: positive-int} */
final class LineParser {
/**
* @param list<string> $rows "sku,qty" pairs from a CSV upload
* @return list<Line>
*/
public static function parse(array $rows): array {
return array_map(fn($r) => ['sku' => strtok($r, ','), 'qty' => (int) strtok(',')], $rows);
}
}
echo json_encode(LineParser::parse(['BK-SQL-01,2', ',0'])), "\n";PHP printed [{"sku":"BK-SQL-01","qty":2},{"sku":"0","qty":0}]: SKU "0" with quantity 0, because strtok() skips a leading comma. PHPStan 376,908 at level 10 had already rejected the function:
...
9 Method LineParser::parse() should return list<array{sku: non-empty-string, qty:
int<1, max>}> but returns list<array{sku: non-empty-string|false, qty: int}>.
...Validate each row and PHPStan narrows the types itself. Write the type you mean, not the one that silences the error: a lying @var is worse than none.