Type aliases: name the contract
An order line is array{sku: non-empty-string, quantity: positive-int, pence: int<0, max>}. The basket builds it, the invoice totals it, and the warehouse picks it. If each of them writes that shape out, you have three contracts that only agree until someone changes one of them.
Someone always does. They rename qty to quantity in the basket, the invoice still reads qty, and every line comes to £0.
Name it once
@phpstan-type gives a type a name, on a class's docblock:
/** * @phpstan-type Address array{line1: non-empty-string, postcode: non-empty-string, country: 'GB'|'IE'} */ final class CustomerProfile { /** @return Address */ public function deliveryAddress(): array
Any other class can import it by name:
/** * @phpstan-import-type Address from CustomerProfile */ final class LabelPrinter { /** @param Address $to */ public function print(array $to): string
Now there's one definition. Change it, and PHPStan checks every class that imports it. If a name clashes, rename it on the way in: @phpstan-import-type Address from CustomerProfile as ShippingAddress.
An alias is just a name for a type. It doesn't exist at runtime, and it's still an array underneath. It can be any type, not only a shape: @phpstan-type Skus non-empty-list<non-empty-string> works too.
Who owns it?
Put the alias on the class that produces the data, not on whichever class needed it first. The producer decides what an order line contains. Everyone else consumes it, so they import it. When the shape needs to change, there's one obvious place to change it.
Power-up
<?php final class CustomerProfile { /** @return array{line1: string, postcode: string, country: string} */ public function deliveryAddress(): array { /* ... */ } } final class LabelPrinter { /** @param array{line1: string, postCode: string} $to copied once, then drifted */ public function print(array $to): string { /* ... */ } }
<?php /** * @phpstan-type Address array{line1: non-empty-string, postcode: non-empty-string, country: 'GB'|'IE'} */ final class CustomerProfile { /** @return Address */ public function deliveryAddress(): array { /* ... */ } } /** * @phpstan-import-type Address from CustomerProfile */ final class LabelPrinter { /** @param Address $to */ public function print(array $to): string { /* ... */ } }