Skip to content

feat: support phpdocumentor/reflection-docblock 6 and type-resolver 2 - #840

Open
timtheisinger wants to merge 2 commits into
thecodingmachine:masterfrom
timtheisinger:feat/reflection-docblock-6
Open

timtheisinger wants to merge 2 commits into
thecodingmachine:masterfrom
timtheisinger:feat/reflection-docblock-6

Conversation

@timtheisinger

Copy link
Copy Markdown

Problem

GraphQLite requires phpdocumentor/reflection-docblock ^5.4 and phpdocumentor/type-resolver ^1.7. Current projects increasingly lock reflection-docblock 6 (which requires type-resolver 2) through other dependencies, for example a fresh Shopware 6.7 project via mcp/sdk. In such a project, Composer cannot install GraphQLite or anything that depends on it without downgrading reflection-docblock (-W).

Only widening the constraints is not enough. type-resolver 2 replaces Types\Collection (an AbstractList) with PseudoTypes\Generic (an Object_), so generic iterables such as \ArrayObject<int, User> or \Iterator<string, int> stop mapping to GraphQL lists: 17 tests fail and PHPStan reports the missing Collection class.

Fix

  • BaseTypeMapper maps a Generic output type to a list of its last type parameter, which is what Collection (an AbstractList with that value type) did with type-resolver 1.
  • TypeHandler matches a phpdoc generic against a non-generic return type (\ArrayObject / \ArrayObject<string, TestObject>) for both Collection and Generic.
  • composer.json allows reflection-docblock ^5.4 || ^6.0 and type-resolver ^1.7 || ^2.0.
  • Each of the two classes only exists in one major version, so phpstan.neon ignores class.notFound for them in these two files.

Nothing else in GraphQLite depends on the API that changed between the major versions.

Tests

The existing tests already cover generic iterables and fail on type-resolver 2 without this change. With it all tests pass.

We also built our production schema (183 types) with reflection-docblock 5 and 6. The output is byte-identical.

reflection-docblock 6 requires type-resolver 2, which replaces the
`Types\Collection` list type with `PseudoTypes\Generic`, an `Object_`.
Generic iterables such as `\ArrayObject<int, User>` therefore no longer
mapped to GraphQL lists. Handle `Generic` next to `Collection` so both
major versions work.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codecov-commenter

codecov-commenter commented Oct 13, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.07%. Comparing base (53f9d49) to head (9b800ec).
⚠️ Report is 181 commits behind head on master.

Additional details and impacted files
@@             Coverage Diff              @@
##             master     #840      +/-   ##
============================================
- Coverage     95.72%   92.07%   -3.66%     
- Complexity     1773     2175     +402     
============================================
  Files           154      205      +51     
  Lines          4586     5777    +1191     
============================================
+ Hits           4390     5319     +929     
- Misses          196      458     +262     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

// (an `Object_`) instead of `Collection` (an `AbstractList`).
if ($type instanceof Generic && $type->getTypes() !== []) {
$genericTypes = $type->getTypes();
$innerType = $this->topRootTypeMapper->toGraphQLOutputType(end($genericTypes), $subType, $reflector, $docBlockObj);

@oojacoboo oojacoboo Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we only pass the last type of the $type->getTypes() for the OutputType? Does this mean support for union and intersection types aren't properly supported here?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're working with lists here and want to get the type of items in that list. For all common PHP list types like ArrayObject or Iterator (not true for Generator) this should be last the type parameter. This behavior is also exactly what getValueType did in type-resolver v1.
See: https://github.com/phpDocumentor/TypeResolver/blob/1.12.0/src/TypeResolver.php#L457-L466
https://github.com/phpDocumentor/TypeResolver/blob/1.12.0/src/Types/Collection.php#L40
It reverses the params and the last one lands in valueType.

I'm not sure exactly what you mean by missing support for unions/intersections. For example if we have a \ArrayObject<int, A&B>, the whole intersection A&B will be the value type, as before. The tests cover this already as far as I can see.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@timtheisinger what does getTypes return for a phpdoc like:

@return \ArrayObject<int, A&B>|bool

Unlike `Collection`, type-resolver 2's `Generic` is an `Object_`, so
`IteratorTypeMapper` took `\ArrayObject<int, User>` for a raw iterator in
the `\ArrayObject|User[]` pattern. Other union members were then dropped
instead of failing: `\ArrayObject<int, User>|bool` mapped to `[User!]!`.
Skip `Generic` wherever `Collection` used to fall through, so mapping
results and errors match type-resolver 1.

This branch has not been deployed

No deployments
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.

3 participants