Skip to content

Eager load constraints on latestOfMany/ofMany not applied to inner subquery #59318

@SachinAgarwal1337

Description

@SachinAgarwal1337

Description

When eager loading a latestOfMany/ofMany relationship with constraints, the constraints are only applied to the outer query and not to the inner subquery that determines which record is the "latest." This causes silently incorrect results — no error is thrown, but the wrong record is returned.

Steps To Reproduce

Given a User model with a lastPayment relationship:

public function payments(): HasMany
{
    return $this->hasMany(Payment::class, 'payer_id');
}

public function lastPayment(): HasOne
{
    return $this->payments()->one()->latestOfMany();
}

When eager loading with a constraint:

User::with(['lastPayment' => fn ($query) => $query
    ->where('store_id', $storeId)
    ->where('type', 'store_payment')
])->get();

Expected behavior

The constraints should be applied inside the inner subquery so "latest" is determined within the scoped dataset:

SELECT payments.* FROM payments
INNER JOIN (
    SELECT MAX(payments.id) as id_aggregate, payments.payer_id
    FROM payments
    WHERE payments.payer_id IN (...)
      AND store_id = ?           -- ✅ applied here
      AND type = 'store_payment' -- ✅ applied here
    GROUP BY payments.payer_id
) AS latestOfMany ON ...
WHERE payments.deleted_at IS NULL;

Actual behavior

Constraints only on the outer query. The inner subquery picks the latest across all records, then the outer filter discards it:

SELECT payments.* FROM payments
INNER JOIN (
    SELECT MAX(payments.id) as id_aggregate, payments.payer_id
    FROM payments
    WHERE payments.payer_id IN (...)
      -- ❌ no constraints here
    GROUP BY payments.payer_id
) AS latestOfMany ON ...
WHERE store_id = ?               -- only here
  AND type = 'store_payment'     -- only here

This silently returns null for users whose latest payment overall was at a different store — even if they have payments at the target store.

Why This Matters

  • Silent data loss: No error, the relationship just returns null or the wrong record
  • Unintuitive: with(['relation' => fn($q) => $q->where(...)]) scopes the entire operation for every other relationship type
  • Historical context: PR [8.x] Improve one-of-many performance #37451 (Laravel 8.x) specifically moved constraints into the subquery for correctness and performance (212x speedup). Current behavior appears to be a regression from that intent

Workaround

Use ofMany closure to bake constraints at definition time:

return $this->payments()->one()->ofMany(
    ['id' => 'max'],
    fn ($query) => $query->where('store_id', $storeId)->where('type', 'store_payment'),
);

Works but requires a separate relationship per constraint set — no dynamic constraints at eager load time.

Versions

  • Laravel: 12.55.1
  • PHP: 8.5

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions