staudenmeir/eloquent-eager-limit-x-laravel-cte
A Laravel package that combines eager limit loading with common table expressions, letting you limit eager-loaded relationships per parent model using CTEs. Useful for fetching only the latest or top N related records for each parent in a single query.
Architecture fit. This is a compatibility shim, not a capability. It exists only because staudenmeir/eloquent-eager-limit (per-parent LIMIT compiled as ROW_NUMBER() OVER (PARTITION BY ...)) and staudenmeir/laravel-cte (WITH / WITH RECURSIVE in the query builder) both override Laravel's SQL-compilation path and relation internals, and used together they emit malformed SQL. Adopting this package is therefore a decision to own a conflict-resolution layer above two upstream dependencies we would otherwise take directly.
That makes the fit narrow and conditional: justified only where a single query shape simultaneously needs recursive/hierarchical querying and top-N-per-parent eager loading. If we need one behaviour, we take the corresponding upstream package and skip this entirely — that is the single most likely correct answer, and the TPM should force that question before anything else. Where it does fit, integration is idiomatic Eloquent: trait composition on models and relations, no service provider, no migration, no daemon. The architectural surface is genuinely small.
Integration feasibility. Mechanically this is a Composer require plus trait usage. The real work is proving a three-way version intersection: bridge × both parent packages × Laravel/PHP core. The last tag is February 2023, placing it in the Laravel 9/10 window. Laravel 11 and 12 changed grammar and relation internals, and a package whose entire value is overriding protected compilation behaviour is precisely what breaks on a major bump. Compatibility must be proven by spike, never assumed from the version constraint.
Technical risk — the dominant finding.
PARTITION BY key. A wrong partition returns plausible-looking wrong data rather than an error — the worst failure class for a feed or reporting surface, because it survives QA and surfaces as a business-data dispute.Key questions to resolve before commitment.
latestOfMany() / ofMany(), a whereIn subquery, a precomputed summary table) already been ruled out? This is the cheapest possible outcome and must be checked first.Stack fit. Adopt only on Laravel projects running MySQL 8+/PostgreSQL/SQLite 3.25+/SQL Server where at least one model graph genuinely requires both behaviours. Framework-native, PSR-4, MIT, no infrastructure footprint and no configuration surface to secure. The decision is therefore almost entirely about ownership and version pinning rather than architecture — which is good news, because it means the decision is reversible if we wrap it properly.
Migration path. Pick the route explicitly at the outset; do not drift into one.
app/Support/ or a private package. Given zero dependents and a stale release, this is the realistic default rather than a pessimistic edge case. MIT makes it clean and the surface area is small enough that a fork is a legitimate build decision, not a rescue plan.If only one behaviour is needed, depend on the upstream package directly. If the requirement is "latest one per parent," latestOfMany() / ofMany() or a summary table likely closes it with zero new dependencies — verify first.
Compatibility. Three constraints must all hold simultaneously and must be asserted, not inferred: the bridge supports our Laravel/PHP major; both upstream packages do too; and our database clears the window-function and CTE floor. Pin the exact version, add it to the Laravel-major upgrade checklist, and add a CI test asserting generated SQL for representative queries so a framework bump fails loudly at PR time instead of in production.
Sequencing.
toSql() or the query log. Confirm the partition key and the ROW_NUMBER predicate match what we would hand-write. This is the highest-value verification step and gates everything downstream.Maintenance. This is the dominant lifecycle cost and it is ownership, not operations. A single-maintainer package with a 2023 release and no dependents means no upstream support contract in practice. Budget roughly one day to vendor or reimplement the trait if upstream stalls — the logic is small — and treat the fork as a planned contingency with a named owner rather than an unassigned "if it breaks we'll figure it out." Add the dependency to the Laravel-major upgrade checklist so it is a scheduled cost, not a surprise.
Support. No vendor, no SLA, no community to escalate through. Internal support means the team that wrapped it owns it: the helper layer is the support boundary, and it should be documented enough that someone who did not write it can debug the generated SQL. Because there is no schema or infrastructure change, runbooks stay simple — the troubleshooting tool is the query log.
Scaling. Favourable and predictable. Per-parent limiting through window functions moves work from PHP fetch-then-slice into the database, which is strictly better on memory and round trips for large relation collections — the p95 improvement is the point of adopting it. CTEs likewise replace PHP-side recursion that degrades past a handful of levels. The counterweight is that heavy PARTITION BY and recursive CTE queries concentrate cost on the database tier; index coverage on the partition key becomes a real performance concern and should be reviewed alongside the queries, not after the first latency incident.
Failure modes. Ranked by what a TPM actually has to plan for:
Notably absent: data corruption, stalled workers, queue backlogs, external-service outages. Compile-time-only behaviour is the reason this risk profile is tolerable.
Ramp-up. Low for engineers already fluent in Eloquent internals, moderate for everyone else — the concepts (PARTITION BY, ROW_NUMBER, recursive CTEs) are not everyday knowledge on a typical product team. Plan for the wrapper helper to carry the explainability burden: call sites should read as "top N per parent" in domain terms, never as window-function SQL. Two costs to name explicitly: the initial spike (days, not weeks) and the organisational conversation about the database floor, which has budget and hosting implications and should be decided deliberately rather than as a side effect of a Composer require.
Recommendation. Green-light as a scoped, pinned dependency behind one internal helper for the specific feed/hierarchy work — not as a platform standard. Buy the capability, pin the artifact, and make removal a one-file change.
How can I help you explore Laravel packages today?