Did You Know: Five Additional Filters in belongsTo() or hasMany()

Typical Eloquent one-to-many relationship is defined with belongsTo() and hasMany() methods in the model. But did you know you can chain more methods to filter specific records for that relationship? Let me show you what I mean.

1. Where Statements in Relationships

Let’s assume you have relationship from Comment to Post, like this:

class Post extends Model {
    
    function comments() 
    {
        return $this->hasMany(Comment::class);
    }

}

Now, you want to filter already approved comments, with field comments.approved_at IS NOT NULL.

You can do the filtering in Controller, like this:

$post = Post::find($post_id);
$comments = $post->comments()->whereNotNull('approved_at');

But you also can create a separate relationship for this:

class Post extends Model {
    
    function comments() 
    {
        return $this->hasMany(Comment::class);
    }

    function approved_comments()
    {
        return $this->hasMany(Comment::class)->whereNotNull('approved_at');
    }

}

Then, you just call the relationship like this in your Controller:

$comments = $post->approved_comments();

2. Soft-deletes withTrashed()

If you’re using Soft Deleting functionality and have a deleted_at column in your database table, you may still want to show that parent record even if it’s soft-deleted.

For example, you’re trying to list the comments with their posts, in Blade:

@foreach ($comments as $comment)
<tr>
  <td>{{ $comment->created_at }}</td>
  <td>{{ $comment->post->title ?? '' }}</td>
  <td>... more fields ...</td>
</tr>
@endforeach

And what if that original post is soft-deleted, but you still want to show it in the comments list?

Just add withTrashed() method in your relationship:

class Comment extends Model {

    function post()
    {
        return $this->belongsTo(Post::class)->withTrashed();
    }
 
}

3. orderBy() Directly in the Model

Typical scenario to list all the comments in chronological order would be this:

$ordered_comments = $post->comments()->orderBy('approved_at', 'desc');

Did you know you can specify that ordering directly in the model?

class Post extends Model {
    
    function comments() 
    {
        return $this->hasMany(Comment::class)->orderBy('approved_at', 'desc');
    }

}

Now, every time you call $post->comments(), all comments would be sorted automatically.


4. Select the Fields You Want to Get

If you have database tables with many fields, but for certain relationships you need only a couple of them, it’s worth specifying them, and you can do that – again – directly in the model, just by using select() method.

class Comment extends Model {

    function post()
    {
        return $this->belongsTo(Post::class)->select(['id', 'title']);
    }
 
}

5. withDefault() to Prevent the Errors

If you try to reference the parent entry that doesn’t exist anymore, you will get error, something like trying to get property of non-object. See the same example:

@foreach ($comments as $comment)
<tr>
  <td>{{ $comment->created_at }}</td>
  <td>{{ $comment->post->title }}</td>
  <td>... more fields ...</td>
</tr>
@endforeach

If the post has already been deleted, then the error will appear, and stop all the page from loading. Of course, you can prevent that with conditional sentence, like in example above: {{ $comment->post->title ?? ” }}. But there’s even more elegant way – you can use withDefault() in the relationship:

public function post()
{
    return $this->belongsTo(Post::class)->withDefault();
}

In this case, no error will be shown, and any of the properties on non-existing object will be shown as empty strings.

But you can override even the default values, by specifying them as parameters:

public function post()
{
    return $this->belongsTo(Post::class)->withDefault([
        'title' => '[Deleted post]',
    ]);
}

So these are five ways to filter Eloquent relationships in the models, without doing this work in Controllers or elsewhere.

For more Eloquent “magic”, check out my online-course Eloquent: Expert Level!

Like our articles?
Check out our Laravel online courses!

2 COMMENTS

  1. You could also do it like this to avoid redeclaring the main relationship:
    “`php
    class Post extends Model {

    function comments()
    {
    return $this->hasMany(Comment::class);
    }

    function approved_comments()
    {
    return $this->comments()->whereNotNull(‘approved_at’);
    }

    }
    “`

LEAVE A REPLY

Please enter your comment!
Please enter your name here