Skip to content

fix(doctrine): raise orm floor to ^3.3 - #8534

Merged
soyuka merged 4 commits into
api-platform:4.4from
soyuka:fix/doctrine-orm-partial-object-floor
Sep 15, 2026
Merged

soyuka merged 4 commits into
api-platform:4.4from
soyuka:fix/doctrine-orm-partial-object-floor

Conversation

@soyuka

@soyuka soyuka commented Sep 15, 2026

Copy link
Copy Markdown
Member

Fixes the PHPUnit api-platform/doctrine-orm (PHP 8.5 lowest) job, red on both 4.4 and main.

Cause

Doctrine\ORM\Query\AST\PartialObjectExpression does not exist in doctrine/orm 3.0.0 – 3.2.3 — it was dropped in the 3.0 rewrite and reintroduced in 3.3.0. EagerLoadingExtension guarded on it:

$fetchPartial = class_exists(PartialObjectExpression::class) && ($operation?->getFetchPartial() ?? $this->fetchPartial);

so on those versions fetchPartial was silently forced to false regardless of what the caller asked for. api_platform.eager_loading.fetch_partial and Operation::getFetchPartial() were advertised options that quietly did nothing.

In the tests that surfaced as 8 errors + 2 failures: the extension emitted addSelect('relatedDummy_a1') instead of addSelect('partial relatedDummy_a1.{id,name,embeddedDummy.name}'), and the private addSelect() — sole caller of PropertyNameCollectionFactory::create() — was never reached.

Bisected: 3.1.0, 3.2.0, 3.2.3 red; 3.3.0 green.

Changes

  • doctrine/orm floor ^2.17 || ^3.0.1^2.17 || ^3.3, and the now-dead class_exists() guard removed. PartialObjectExpression is present at both ends of the new range (verified against the 2.17.0 and 3.3.0 tags).
  • doctrine/doctrine-bundle floor ^2.11^2.11.1. 2.11.0 defaults report_fields_where_declared to false and passes it to AttributeDriver::__construct, which ORM 3 rejects with InvalidArgumentException. Fixed in 2.11.1 (Enable report_fields_where_declared for ORM 3 doctrine/DoctrineBundle#1729). Neither version declares a conflict that would have prevented the pair.
  • EagerLoadingExtensionTest converted from Prophecy to PHPUnit mocks. Prophecy returned null for unstubbed calls, which is why a wrong DQL string showed up as a TypeError instead of an assertion failure; the emitted selects are now recorded and asserted explicitly.

Verification

vendor/bin/phpunit Tests/Extension/EagerLoadingExtensionTest.php inside src/Doctrine/Orm:

  • composer update --prefer-lowest --prefer-source (orm 3.3.0, doctrine-bundle 2.14.0) — OK (24 tests, 266 assertions)
  • default install (orm 3.7.1) — green

Temporarily forcing the plain-alias path back makes 7 tests fail with an explicit diff, confirming the converted tests still catch the original bug:

-    0 => 'partial relatedDummy_a1.{id,name,embeddedDummy.name}'
+    0 => 'relatedDummy_a1'

php-cs-fixer and PHPStan clean.

Note on the PHPStan entry

createMock() returns a typed ClassMetadata, so PHPStan now type-checks the fixtures' associationMappings arrays against ORM 3's mapping-object generics — a mismatch the blanket ObjectProphecy ignore hid until now. The arrays stay: the extension reads them through ArrayAccess, so they are valid under both ORM 2 and 3, and ^2.17 is still supported. Suppressed by identifier, scoped to that one file.

Doctrine\ORM\Query\AST\PartialObjectExpression is absent from doctrine/orm 3.0.0
through 3.2.3 and returns in 3.3.0. The class_exists() guard in EagerLoadingExtension
silently turned fetchPartial off on those versions, making the advertised fetch_partial
option a no-op and failing the component's --prefer-lowest CI job.

Raise the floor to ^2.17 || ^3.3 and drop the now-dead guard.

Also require doctrine/doctrine-bundle ^2.11.1: 2.11.0 passes $reportFieldsWhereDeclared
to AttributeDriver, which doctrine/orm 3 rejects.
Prophecy returned null for unstubbed calls, so a wrong DQL string surfaced as a
TypeError rather than an assertion failure. Assert the emitted selects explicitly.

createMock() returns a typed ClassMetadata, which exposes the ORM 2/3 disagreement on
the shape of $associationMappings; the fixtures stay arrays and the mismatch is ignored
for this file only.
Removing the guard reproducibly broke ContextSwitchTest's partial-fetch cases on the
Symfony lowest job (green on 4.4 across six runs, red here across two), even though
that job installs doctrine/orm 3.7.1 where PartialObjectExpression exists and the
guard should be inert. The mechanism is not understood yet.

The floors are the actual fix for the doctrine-orm lowest job; dropping dead code is
cosmetic and can wait until the interaction is explained.
eea6fc8 bumped only src/Doctrine/Orm/composer.json, but the root suite installs from
the root composer.json, where --prefer-lowest still resolved doctrine/orm 3.0.1. The
PARTIAL DQL grammar does not exist there, so dropping the class_exists() guard made the
extension emit partial selects that the parser rejects, breaking ContextSwitchTest.

Bump the root floors to match the component and restore the guard removal.
@soyuka
soyuka merged commit 9b94d14 into api-platform:4.4 Sep 15, 2026
116 of 117 checks passed
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