Mastering Data Filtering in Laravel Eloquent: A Guide to Custom Filters
Data filtering is a crucial aspect of web applications, allowing users to extract specific information from vast datasets. Laravel, a popular PHP web framework, provides an elegant ORM called Eloquent that simplifies database interactions.
While basic filtering options are available in Eloquent, complex filtering requirements call for custom filters. In this article, we will explore how to create custom filters for Eloquent queries in Laravel, empowering developers to efficiently retrieve relevant data from their databases.
Building a Custom Filter Class:
To start, let's create a base abstract class, Filter, which will serve as the foundation for our custom filters. This class will handle common filtering logic that we can reuse across multiple filters.
// app/Http/Filters/Filter.php
namespace App\Http\Filters;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
abstract class Filter
{
protected Builder $builder;
protected array $filterable = [];
public function __construct(protected Request $request) {}
/**
* Apply the filters on the builder.
*
* @param Builder $builder
*
* @return Builder
*/
public function apply(Builder $builder): Builder
{
$this->builder = $builder;
foreach ($this->getFilters() as $filter => $value) {
if (method_exists($this, $filter)) {
call_user_func_array([$this, $filter], [$value]);
}
}
return $this->builder;
}
/**
* Get the list of filters and their values from the request.
*
* @return array
*/
protected function getFilters(): array
{
return array_filter($this->request->only($this->filterable));
}
}
Implementing the Filterable Trait:
Now, let's create a trait called Filterable, which we'll include in models that require filtering capabilities. This trait will provide a scope method named filter, responsible for applying the filters based on a specific filter class.
// app/Models/Traits/Filterable.php
namespace App\Models\Traits;
use App\Http\Filters\Filter;
use Illuminate\Database\Eloquent\Builder;
trait Filterable
{
// Apply all relevant filters
public function scopeFilter(Builder $query, Filter $filter): Builder
{
return $filter->apply($query);
}
}
Creating Custom Filters:
With the groundwork set, let's create a custom filter class, UserFilter, to demonstrate how to implement specific filters. In this example, we'll create filters for "parentId" and "keyword" to filter the User model.
// app/Http/Filters/UserFilter.php
namespace App\Http\Filters;
class UserFilter extends Filter
{
protected array $filterable = [
'parentId', // Filter for parent_id
'keyword', // Filter for keyword search
];
public function parentId($value = null): void
{
if ($value) {
$this->builder->where('parent_id', $value);
}
}
public function keyword($value = null): void
{
if ($value) {
$this->builder->where('first_name', 'like', "%{$value}%")
->orWhere('last_name', 'like', "%{$value}%")
->orWhere('email', 'like', "%{$value}%");
}
}
}
Implementing the User Model:
Now, let's implement the User model and include the Filterable trait. This will enable the model to use the custom filters when querying the database.
// app/Models/User.php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use App\Models\Traits\Filterable;
class User extends Model
{
use HasFactory, Filterable;
// The rest of your model implementation...
}
Applying Filters in the Controller:
Now, let's see how to apply these custom filters in a controller to retrieve filtered data from the User model.
// app/Http/Controllers/UserController.php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use App\Http\Filters\UserFilter;
class UserController extends Controller
{
public function index(Request $request, UserFilter $filter): JsonResponse
{
$perPage = $request->input('perPage', 15);
$users = User::query()
->filter($filter)
->orderBy('first_name')
->paginate($perPage);
// Add filters to the pagination links
$users->appends($request->query());
return response()->json([
'data' => $users,
]);
}
}
Now that we have created custom filters for the User model and implemented them in the UserController, it's time to explore how to retrieve filtered data by setting up appropriate routes and using URL parameters.
Setting Up the Route
Define a route in the routes/web.php file that maps to the UserController's index method. The index method will handle retrieving and returning the filtered data based on the provided URL parameters.
// routes/web.php
use App\Http\Controllers\UserController;
Route::get('/users', [UserController::class, 'index'])->name('users.index');
In this example, we're defining a GET route that points to the index method of the UserController when a request is made to the /users endpoint.
Filtering Data with URL Parameters
With the route set up, users can now access the filtered data by making GET requests to the /users endpoint with the desired filter parameters in the URL. The UserController will process these parameters and return the filtered results accordingly.
For instance, let's say we want to filter users based on their parent_id or perform a keyword search. Here's how you can make the corresponding requests:
To filter by parent_id:
/users?parentId=123
To perform a keyword search:
/users?keyword=john
To combine filters:
/users?parentId=123&keyword=john
When the user makes one of these requests, the UserController will extract the filter parameters from the URL using the Request object. It will then create an instance of the UserFilter and pass it to the filter() method of the User model, effectively applying the desired filters to the database query.
Pagination and Preserving Filters in URLs
The UserController also handles pagination, allowing users to access the filtered data in paginated format. By default, it will return 15 records per page, but users can modify this by passing the per_page parameter in the URL, such as /users?perPage=20
.
Additionally, the UserController preserves the filters in the pagination links. This means that when users navigate through the paginated results, the URL will retain the filters, allowing them to keep their selected filters intact as they explore different pages.
Conclusion:
Custom filters in Laravel Eloquent gives us a powerful tool to efficiently filter and retrieve specific data from their databases. By creating reusable filter classes and applying them using the Filterable trait, we can keep our code organized, maintainable, and highly performant.
Embrace the power of custom filters to master data filtering in your Laravel applications and elevate the user experience to new heights.
Remember, this article offers a foundational understanding of custom filters in Laravel Eloquent. Feel free to extend the concept to fit your specific project needs and explore more complex filtering scenarios. Happy coding!