hasOneOfMany: latest(), oldest(), ofMany()

Pluck a single 'winner' out of a hasMany relationship — the latest order, the top score.

You have a hasMany, but 90% of the time you only want one of them: a user's most recent login, an account's latest invoice, a product's highest bid. Before Laravel 8 people wrote sub-queries by hand. Now there is a first-class helper.

The three flavours

  • latestOfMany() — newest by the model's primary key (or a column you name).
  • oldestOfMany() — oldest.
  • ofMany('column', 'max'|'min') — the winner by any aggregate, with optional extra constraints.
// User.php
public function latestOrder()
{
    return $this->hasOne(Order::class)->latestOfMany();
}

// $user->latestOrder;  // a single Order, eager-loadable

Because it is still a hasOne, you can with('latestOrder') it and Laravel builds an efficient correlated sub-query instead of loading every order into memory.

Example

Example · php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasOne;

class User extends Model
{
    // Newest order by created_at (falls back to PK ordering).
    public function latestOrder(): HasOne
    {
        return $this->hasOne(Order::class)->latestOfMany();
    }

    // The single most expensive PAID order, ties broken by id
    // so the result is stable across runs.
    public function biggestPaidOrder(): HasOne
    {
        return $this->hasOne(Order::class)->ofMany(
            ['total_cents' => 'max', 'id' => 'max'],
            fn ($query) => $query->where('status', 'paid'),
        );
    }
}

// --- Usage: still eager-loadable, still one query per relation ---
$users = User::query()
    ->with(['latestOrder', 'biggestPaidOrder'])
    ->get();

foreach ($users as $user) {
    // No N+1 — each relation resolved via a correlated subquery.
    echo $user->latestOrder?->reference;
    echo $user->biggestPaidOrder?->total_cents;
}

When to use it

  • A user model uses hasOne()->latestOfMany() to always fetch the user's most recently created order without a separate query or PHP sorting.
  • A product model uses hasOne()->ofMany('price','min') to expose the cheapest variant as a direct relationship attribute.
  • A customer support system uses hasOne()->ofMany() with a custom closure to find each user's oldest unresolved ticket in a single relationship.

More examples

Latest and oldest of many

latestOfMany() and oldestOfMany() narrow a hasMany to a single record using an efficient subquery instead of loading the full collection.

Example · php
class User extends Model
{
    // Most recent order
    public function latestOrder(): HasOne
    {
        return $this->hasOne(Order::class)->latestOfMany();
    }

    // Very first order
    public function oldestOrder(): HasOne
    {
        return $this->hasOne(Order::class)->oldestOfMany();
    }
}

Min/max of many with ofMany()

ofMany('price','min') creates a HasOne that automatically selects the variant with the lowest price using a SQL MIN subquery.

Example · php
class Product extends Model
{
    // The cheapest variant
    public function cheapestVariant(): HasOne
    {
        return $this->hasOne(Variant::class)->ofMany('price', 'min');
    }

    // The most expensive variant
    public function premiumVariant(): HasOne
    {
        return $this->hasOne(Variant::class)->ofMany('price', 'max');
    }
}

Custom ofMany with additional constraints

Passes a closure to ofMany() to add WHERE constraints so only orders matching a specific status are considered when finding the oldest.

Example · php
class User extends Model
{
    public function oldestActiveOrder(): HasOne
    {
        return $this->hasOne(Order::class)
            ->ofMany(
                ['created_at' => 'min'],
                fn($query) => $query->where('status', 'active')
            );
    }
}

Discussion

  • Be the first to comment on this lesson.