PHP has no generics, so @return list<Order> is a promise to the analyser and a comment to the engine.
/**
* @template T of Model
*/
final class Repository
{
/** @param class-string<T> $class */
public function __construct(private string $class) {}
/** @return T|null */
public function find(int $id) { /* ... */ }
}
// $repo = new Repository(Order::class);
// $repo->find(1) is Order|null, to the analyser.
The annotations are genuinely useful and are genuinely unenforced: a function returning the wrong type produces no runtime error and an analyser finding only where the analyser can see both ends. class-string<T> is the piece that makes a generic factory work and is the least obvious part of the syntax. The whole scheme depends on the analyser running in CI, because a docblock nobody checks is worse than none — it is a claim people trust.