You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Component and root composer.json dependency floors can drift undetected #8535
A component's src/*/composer.json can declare a dependency floor that the root composer.json does not, and nothing detects the divergence.
The root package autoloads src/ directly rather than installing the split components, so src/*/composer.json is inert during a root composer update. Constraints declared there never enter the root resolution. The root's own constraints must therefore independently cover what the code under src/ requires — but that duplication is maintained by hand.
How it surfaced
In #8534, src/Doctrine/Orm/composer.json was raised to doctrine/orm: ^2.17 || ^3.3, because Doctrine\ORM\Query\AST\PartialObjectExpression and the PARTIAL DQL grammar do not exist in 3.0.0–3.2.3. The root composer.json was left at ^2.17 || ^3.0.
The PHPUnit (PHP 8.3) (Symfony lowest) job runs composer update --prefer-lowestat the repo root, so it kept installing doctrine/orm 3.0.1 — a version the component itself declares unsupported. EagerLoadingExtension emitted partial x.{...} into a parser with no such grammar, and two ContextSwitchTest cases failed with:
[Syntax Error] line 0, col 38: Expected Doctrine\ORM\Query\TokenType::T_FROM, got '.'
raised from Paginator::getCountQuery().
Nothing pointed at the cause. The job that failed was not the job the change was meant to fix, the component's own test suite was green, and the failure looked like a version-resolution or flakiness problem before the root/component split turned out to be responsible.
Current state
A scan comparing every third-party constraint in src/*/composer.json against the root's finds 17 mismatches. Most are benign — the root is stricter than the component, e.g. symfony/serializer at ^7.4.9 || ^8.0.9 vs ^7.4 in eleven components.
The dangerous direction is the root being looser than a component, which is what bit #8534. Present examples:
component
package
component
root
src/Laravel
symfony/deprecation-contracts
^3.6
^3.1
src/HttpCache
guzzlehttp/guzzle
^6.0 || ^7.0 || ^8.0
^6.0 || ^7.0
And #8534 left two siblings behind that should probably move with it:
component
package
component
root
src/Doctrine/Common
doctrine/orm
^2.17 || ^3.0
^2.17 || ^3.3
src/Doctrine/Odm
doctrine/doctrine-bundle
^2.11 || ^3.1
^2.11.1 || ^3.1
Proposal
A CI check that compares shared third-party constraints between the root composer.json and each src/*/composer.json, failing when the root permits a version a component excludes. Sibling api-platform/* constraints are out of scope — those legitimately differ, since the root uses self.version.
Whether components should also be kept consistent with each other (the Doctrine/Common vs Doctrine/Orm case above) is a separate question worth deciding as part of this.
I'd be happy to pick this one up. I prototyped the check with Composer\Semver\Intervals::isSubsetOf() (the root constraint must be a subset of each component's, api-platform/*, php and ext-* skipped), and ran it on the three branches:
4.4 and main: a single real mismatch, src/Laravel requires symfony/deprecation-contracts: ^3.6 while the root allows ^3.1.
4.3: 9 mismatches, and some look intentional, e.g. src/Mcp requires symfony/http-foundation: ^7.4 || ^8.0 while the root still allows 6.4. So a strict check needs a way to accept a known difference.
The guzzlehttp/guzzle row goes the benign way: the root (^6.0 || ^7.0) is stricter than src/HttpCache (^6.0 || ^7.0 || ^8.0), so the lowest job can't pick a version the component excludes.
Before writing it, two questions on where it should live:
pmu or this repo?composer check-dependencies already runs in CI and pmu has a baseline in its config, which would cover the accepted differences. A check-constraints command (or an option on check-dependencies) in pmu feels like the natural home, the inverse of blend. Otherwise a small script here, run next to check-dependencies.
Component vs component (Doctrine/Common vs Doctrine/Orm): I'd leave it out and only compare against the root, unless you want both.
I'd target 4.4 and raise the root symfony/deprecation-contracts floor to ^3.6 in the same PR so the check starts green. 🙂
Activity
Amoifr commentedon Sep 25, 2026
I'd be happy to pick this one up. I prototyped the check with
Composer\Semver\Intervals::isSubsetOf()(the root constraint must be a subset of each component's,api-platform/*,phpandext-*skipped), and ran it on the three branches:src/Laravelrequiressymfony/deprecation-contracts: ^3.6while the root allows^3.1.src/Mcprequiressymfony/http-foundation: ^7.4 || ^8.0while the root still allows 6.4. So a strict check needs a way to accept a known difference.guzzlehttp/guzzlerow goes the benign way: the root (^6.0 || ^7.0) is stricter thansrc/HttpCache(^6.0 || ^7.0 || ^8.0), so the lowest job can't pick a version the component excludes.Before writing it, two questions on where it should live:
composer check-dependenciesalready runs in CI and pmu has a baseline in its config, which would cover the accepted differences. Acheck-constraintscommand (or an option oncheck-dependencies) in pmu feels like the natural home, the inverse ofblend. Otherwise a small script here, run next tocheck-dependencies.Doctrine/CommonvsDoctrine/Orm): I'd leave it out and only compare against the root, unless you want both.I'd target 4.4 and raise the root
symfony/deprecation-contractsfloor to^3.6in the same PR so the check starts green. 🙂