Skip to content

Emit @var PHPDoc for hook-backed collection properties#47

Merged
ruudk merged 1 commit intomainfrom
hook-list
Apr 18, 2026
Merged

Emit @var PHPDoc for hook-backed collection properties#47
ruudk merged 1 commit intomainfrom
hook-list

Conversation

@ruudk
Copy link
Copy Markdown
Owner

@ruudk ruudk commented Apr 18, 2026

Hook-backed properties (@hook directive) whose invokable returns a collection type (list<X>, array<K, V>) were generated as public array $field { ... } with no @var docblock. PHPStan at level 6+ rejects these with missingType.iterableValue, which made @hook effectively unusable for bulk-find-by-ids and other list-returning lookups — users had to wrap results in a boilerplate value object.

The regular-field branch of DataClassGenerator already emits @var when the naked property type is a CollectionType. The hook branch skipped that step entirely. Even if it hadn't, getNakedType() only unwraps NullableType, so passing a HookPropertyType wrapper through would never reach the inner collection. Fix by unwrapping HookPropertyType via getWrappedType() before the naked-type check and dumping the wrapped type (not the wrapper) through TypeDumper.

Also pulls phpstan/phpdoc-parser in as a runtime dependency. Symfony's TypeResolver::create() only wraps its reflection resolvers with PhpDocAwareReflectionTypeResolver when that package is loadable — without it, @return list<User> on a hook invokable falls back to array<int|string, mixed> and the inner value type is lost. Making it a hard dep means hook return types declared via PHPDoc resolve correctly out of the box. Whitelisted in composer-dependency-analyser.php because it's never imported directly; Symfony's TypeResolver probes for it via class_exists.

Regression test in tests/HooksWithListReturn/ covers a list-returning hook end-to-end: generated output carries @var list<User> above the property, and the runtime invocation returns the expected objects.

Hook-backed properties (`@hook` directive) whose invokable returns a
collection type (`list<X>`, `array<K, V>`) were generated as
`public array $field { ... }` with no `@var` docblock. PHPStan at level
6+ rejects these with `missingType.iterableValue`, which made `@hook`
effectively unusable for bulk-find-by-ids and other list-returning
lookups — users had to wrap results in a boilerplate value object.

The regular-field branch of `DataClassGenerator` already emits `@var`
when the naked property type is a `CollectionType`. The hook branch
skipped that step entirely. Even if it hadn't, `getNakedType()` only
unwraps `NullableType`, so passing a `HookPropertyType` wrapper through
would never reach the inner collection. Fix by unwrapping
`HookPropertyType` via `getWrappedType()` before the naked-type check
and dumping the wrapped type (not the wrapper) through `TypeDumper`.

Also pulls `phpstan/phpdoc-parser` in as a runtime dependency. Symfony's
`TypeResolver::create()` only wraps its reflection resolvers with
`PhpDocAwareReflectionTypeResolver` when that package is loadable —
without it, `@return list<User>` on a hook invokable falls back to
`array<int|string, mixed>` and the inner value type is lost. Making it
a hard dep means hook return types declared via PHPDoc resolve
correctly out of the box. Whitelisted in
`composer-dependency-analyser.php` because it's never imported
directly; Symfony's TypeResolver probes for it via `class_exists`.

Regression test in `tests/HooksWithListReturn/` covers a list-returning
hook end-to-end: generated output carries `@var list<User>` above the
property, and the runtime invocation returns the expected objects.
@ruudk ruudk merged commit 8886eaf into main Apr 18, 2026
3 checks passed
@ruudk ruudk deleted the hook-list branch April 18, 2026 17:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant