Writing even better Eloquent Filters 🎉
🔥🔥🔥 Including Relationships and Deferred Execution. 🔥🔥🔥
If you have been through this article, you might be familiar with Filters, a concept I have learned to use to handle query strings in GET requests to Laravel APIs.
https://medium.com/@mykeels/writing-clean-composable-eloquent-filters-edd242c82cc8
Here are a few things you could do with it, so tell me … good ideas or nah?
1. Loading Relationships
You can actually use ?with_{relationship}
as a query string to include a relationship … Here’s an example.
In a sample API, a user has companies and we wish to list all users, include their companies in a GET request.
Where our Company
and User
model classes are defined as
If you don’t understand laravel relationships, now is a good time to learn it.
As we learned in the last article, we then write our UserFilter.php
class as
Note how sleek the with_companies
method looks 😍
Once we filter the User
model query with our UserFilter
as we learned in the previous article, we can make a GET request like:
~/api/users?with_companies
and get a response like:
[
{
"id": 1,
"name": "Mykeels",
"companies": [
{
"id": 1,
"name": "The XYZ Company",
"user_id": 1
}
]
}
]
The inclusion is done by the query builder, so a single query is made to the DB, to make this happen.
2. Defer Loading Relationships
The previous technique only works when there is a static association between the relationships such as via a belongsTo
, belongsToMany
, hasMany
, or hasManyThrough
method.
If we need a model instance to create the relationship, such as in scoped queries, like when attempting to load the staff in a user’s companies in the User
class:
public function scopeStaff() {
$ids = $this->companies()->pluck('id');
return Staff::whereIn('company_id', $ids);
}
The ->with('staff')
query builder method used in the UserFilter.php
class would not work, but there’s a workaround for that.
Ladies and Gentlemen, I hereby present QueryFilters.php
class 2.0 🎉
If UserFilters
extends this, we can have its with_staff
method defined as
public function with_staff() {
return $this->defer(function ($user) {
$user->staff = $user->staff()->get();
return $user;
});
}
then transform the users data we get from our query like
User::get()->map(function ($user) use ($filters) {
return $filters->transform($user);
});
So, when we load ~/api/users?with_staff
we would receive a response like:
[
{
"id": 1,
"name": "Mykeels",
"staff": [
{
"id": 2,
"user_id": 2,
"company_id": 1
}
]
}
]
Which looks so cool! 😍 But, be wary though … Having multiple DB queries such as in deferred executions like this, could potentially slow your requests down.
Enjoy! 💃