Filter Eloquent relationships "on-the-fly", when you need it

Tutorial last revisioned on August 18, 2022 with Laravel 9
I find more and more Eloquent functions that are rarely used, so continue posting these short tips on the blog. Here's the next one. Simple Eloquent relationship goes like this.
class Author extends Model
{
  public function books()
  {
    return $this->hasMany(Book::class);
  }
}
And then you get the data in the Controller:
$authors = Author::all();
foreach ($authors as $author) {
  foreach ($author->books as $book) {
  // .. do something
  }
}
But what if, in some case, you need to get only books that were written this year? Of course, you can change the original relationship, like this:
  public function books()
  {
    return $this->hasMany(Book::class)->whereYear('books.created_at', date('Y'));
  }
Notice: did you know about whereYear() function? Another option is to have a separate relationship function for this:
  public function books()
  {
    return $this->hasMany(Book::class);
  }

  public function booksThisYear()
  {
    return $this->hasMany(Book::class)->whereYear('books.created_at', date('Y'));
  }
And then load it like this:
foreach ($author->booksThisYear() as $book) ...
But you probably don't want to do that because it's not flexible, so another option is to add filtering query at the time of getting the data.
$authors = Author::with(['books' => function($query) {
  $query->whereYear('created_at', date('Y'));
}])->get();
This is probably the most flexible way - original relationship stays the same, and you use filtering only when you actually needed. Another related tip to use those internal functions - if you want to use external variables, don't forget to add use statement. For example, if you want $year to be a variable, this won't work:
$year = 2018; // Feels weird writing it on January 2, still getting used to 2018
$authors = Author::with(['books' => function($query) {
  $query->whereYear('created_at', $year);
}])->get();
It will throw error and won't recognize $year. This is the correct way:
$year = 2018;
$authors = Author::with(['books' => function($query) use ($year) {
  $query->whereYear('created_at', $year);
}])->get();

No comments or questions yet...

Like our articles?

Become a Premium Member for $129/year or $29/month
What else you will get:
  • 58 courses (1054 lessons, total 46 h 42 min)
  • 78 long-form tutorials (one new every week)
  • access to project repositories
  • access to private Discord

Recent Premium Tutorials