Skip to content

fix(reflection): resolve short docblock names in getIterableType - #2305

Open
radoslav-grencik wants to merge 2 commits into
tempestphp:3.xfrom
radoslav-grencik:fix/reflection-resolve-docblock-collection-types
Open

radoslav-grencik wants to merge 2 commits into
tempestphp:3.xfrom
radoslav-grencik:fix/reflection-resolve-docblock-collection-types

Conversation

@radoslav-grencik

Copy link
Copy Markdown
Contributor

Summary

PropertyReflector::getIterableType() regex-captured the bare short name from iterable docblocks (Filter[], Book[], Chapter[]) and returned a TypeReflector for a class literally named Filter / Book / Chapter — ignoring the declaring file's namespace and use statements. Everything downstream that consumes the iterable type resolved against a class that does not exist:

  • model relation hydration (/** @var Book[] */ on Author::$books)
  • request-body collection mapping (/** @var Chapter[] */ on BookRequest::$chapters)

getIterableType() now resolves docblock names through PHP namespace and use semantics.

Change

packages/reflection/src/PropertyReflector.php:

  • Builtin iterable types (int[], array, …) pass through unchanged.
  • Fully qualified names (\App\Foo[]) resolve as before — the leading \ is dropped, the FQCN is used verbatim.
  • Namespace fallback — an unqualified short name with no import resolves against the declaring class's own namespace (DocblockRelativeTarget).
  • Plain imports (use App\Foo;) and aliases (use App\Foo as Bar;) resolve both the exact class and alias-prefixed paths (use A\B as C; → C\D → A\B\D).
  • Grouped imports (use App\{Foo, Bar as Baz};) resolve first-class and aliased members, including a shared group prefix.
  • The list<Type> and array<Type> docblock forms go through the same resolution.

The parser is dependency-free (token_get_all) and deliberately skips things that are not imports:

  • closure bodies and trait use blocks (brace-depth tracking),
  • use function ... and use const ... statements,
  • inline comments inside the statement.

Parsed imports are cached per declaring file in a method-local static, so repeated property reflection per process re-parses once.

End-to-end coverage

Two existing FQCN fixture docblocks were intentionally converted to short names:

  • Author::$books → /** @var Book[] */ — relation hydration resolves and loads Book models.
  • BookRequest::$chapters → /** @var Chapter[] */ — request-body array mapping produces Chapter objects via ArrayToObjectCollectionCaster, which is selected automatically from the resolved iterable type.

New tests:

  • packages/reflection/tests/PropertyReflectorTest.php — unit coverage for imports, aliases, grouped imports (plain + aliased + prefixed), relative names, FQCN, list<…> / array<…> forms, and the no-docblock null case with a dedicated fixture model + bucket targets.
  • ArrayToObjectMapperTest::map_collection_of_objects_from_short_docblock_name — maps ['items' => [['name' => 'a'], …]] into LibraryItem instances from a short LibraryItem[] docblock (Library / Children\LibraryItem fixtures).

@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Benchmark Results

Comparison of fix/reflection-resolve-docblock-collection-types against 3.x (0f478fb487e12a5891738a15a1663fa44c47295b).

Open to see the benchmark results
Benchmark Set Mem. Peak Time Variability
DiscoveryScanBench(benchFullDiscoveryScan) - 25.887mb +0.11% 18.357ms -6.16% ±1.64% -5.63%

Generated by phpbench against commit 5e6fcfc

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