Skip to content

feat(doctrine): eagerly load translations of translatable resources - #88

Merged
paullla merged 1 commit into
Locastic:masterfrom
paullla:feat/query-collection-extension
Jul 28, 2026
Merged

feat(doctrine): eagerly load translations of translatable resources#88
paullla merged 1 commit into
Locastic:masterfrom
paullla:feat/query-collection-extension

Conversation

@paullla

@paullla paullla commented Jul 28, 2026

Copy link
Copy Markdown
Member
Q A
Branch? master
Bug fix? yes (the #10 N+1, open since 2019)
New feature? yes, TranslationsEagerLoadingExtension (experimental) + eager_load_translations option
BC breaks? no (responses unchanged; only the generated SQL differs; kill switch provided)
Deprecations? no
Issues Fix #10
License MIT

Problem

Reading a translated field resolves the locale through matching() on the EXTRA_LAZY translations collection, which issues one query per entity per locale: listing N resources costs N extra queries (#10).

Changes

  • New TranslationsEagerLoadingExtension (experimental): fetch-joins the translations of TranslatableInterface resources on collection and item queries, so the collections come back initialized and matching() filters in memory.
  • All locales are joined, not only the current one: a locale-constrained fetch join would mark the collection initialized with a subset, breaking fallback-locale reads and the translations serialization group. Locale-constrained joins added by filters are ignored by the guard for the same reason, and get a dedicated unconstrained join next to them.
  • New eager_load_translations option (default true); set false to restore lazy loading.
  • The service is registered only when the API Platform Doctrine ORM extension interfaces exist (api-platform/symfony does not require the doctrine-orm bridge); tag priorities mirror core's EagerLoadingExtension (collection -18, item -8).
  • README (config block, Limitations), CHANGELOG and UPGRADE-2.1 entries.

Verification

  • 100 unit/functional tests green, 8 new: produced DQL for collection and item, hydrated collections are initialized, non-translatable resources and the disable switch are no-ops, no duplicate join on double application, coexistence with a locale-constrained filter join. PHPStan level 6 and php-cs-fixer clean.
  • Verified in demo apps (API Platform 4.3 and 3.4) over real HTTP with a query-logging DBAL middleware: listing 6 books costs 8-9 queries with eager loading off and 3 flat with it on (Doctrine Paginator count + ids + fetch join), responses byte-identical in both modes; item GET runs in 1 query; ?locale=, ?groups[]=translations, PATCH merge and fallback reads unchanged; invalid config rejected at compile time.

Reading a translated field resolves the locale through matching() on the
EXTRA_LAZY translations collection, which issues one query per entity
per locale: listing N resources costs N extra queries (Locastic#10).

Add a TranslationsEagerLoadingExtension (experimental) that fetch-joins
the translations of TranslatableInterface resources on collection and
item queries, so the collections come back initialized and matching()
filters in memory. All locales are joined, not only the current one: a
locale-constrained fetch join would mark the collection initialized with
a subset, breaking fallback-locale reads and the translations group.
Locale-constrained joins added by filters are ignored by the guard for
the same reason, and get a dedicated unconstrained join next to them.

The new eager_load_translations option (default true) restores lazy
loading when set to false. The service is registered only when the
api-platform Doctrine ORM extension interfaces exist, since
api-platform/symfony does not require the doctrine-orm bridge.
@paullla
paullla merged commit 3c97da7 into Locastic:master Jul 28, 2026
6 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.

Too many db queries with collectionOperations

1 participant