Poznajemy relację has one of many na podstawie Movie i Review będących w relacji hasMany/belongsTo. Do dzieła.

Ok, nasz model:

class Movie extends Model
{
    use HasFactory;

    protected $fillable = ["title", "director", "length", "release_date"];

    public function reviews(){
        return $this->hasMany(Review::class);
    }
   
}

Dodajemy hasOne (of Many), ostatnia recenzja i najstarsza:

public function latestReview()
        {
            return $this->hasOne(Review::class)->latestOfMany();
        }

    public function oldestReview()
        {
            return $this->hasOne(Review::class)->oldestOfMany();
        }

Proste. A inne sztuczki? Cóż, najpierw ->hasOne, potem ->ofMany, nazwa kolumny i nazwa funkcji agregującej, którą użyjemy do sortowania:

public function worstReview()
        {
            return $this->hasOne(Review::class)->ofMany('rating', 'min');
        }

        public function bestReview()
        {
            return $this->hasOne(Review::class)->ofMany('rating', 'max');
        }

Czyli tak, hasOne do modelu, na którym mamy hasMany, ofMany, rating – nazwa kolumny, min – daj najgorszy. Max – najlepszy.

To teraz nasze komendy. Pierwsza:

Artisan::command('m-latest-review {id}', function (int $id) {

    $m = Movie::with('latestReview')->findOrFail($id);
    $this->comment("Title: {$m->title}");
    $this->comment("Latest review: {$m->latestReview->created_at}");
    $this->comment("Author: {$m->latestReview->author}");
    $this->comment("Content: {$m->latestReview->content}");
    $this->comment("Rating: {$m->latestReview->rating}"); 
});

Druga podobnie:

Artisan::command('m-oldest-review {id}', function (int $id) {

    $m = Movie::with('oldestReview')->findOrFail($id);
    $this->comment("Title: {$m->title}");
    $this->comment("Oldest review: {$m->oldestReview->created_at}");
    $this->comment("Author: {$m->oldestReview->author}");
    $this->comment("Content: {$m->oldestReview->content}");
    $this->comment("Rating: {$m->oldestReview->rating}"); 
});

Trzecia, żadnej filozofii nie ma:

Artisan::command('m-best-review {id}', function (int $id) {

    $m = Movie::with('bestReview')->findOrFail($id);
    $this->comment("Title: {$m->title}");
    $this->comment("Best review: {$m->bestReview->created_at}");
    $this->comment("Author: {$m->bestReview->author}");
    $this->comment("Content: {$m->bestReview->content}");
    $this->comment("Rating: {$m->bestReview->rating}"); 
});

I czwarta:

Artisan::command('m-worst-review {id}', function (int $id) {

    $m = Movie::with('worstReview')->findOrFail($id);
    $this->comment("Title: {$m->title}");
    $this->comment("Worst review: {$m->worstReview->created_at}");
    $this->comment("Author: {$m->worstReview->author}");
    $this->comment("Content: {$m->worstReview->content}");
    $this->comment("Rating: {$m->worstReview->rating}"); 
});

To akurat nie było nic trudnego, aczkolwiek po to uczymy się SQL i po to dogłębnie zgłębiamy temat, aby nic nas nie zaskakiwało, tak samo znamy concat (w SQL) jak i atrybuty (w modelu), tak samo znamy funkcje agregujące jak i relacje Eloquenta.

Nie ma drogi na skróty, niestety, dużo jest tej nauki, ale warto!