Filtering Eloquent Models in Laravel

Filtering database records in Laravel is a common task and can be achieved using different methods.

Filtering Eloquent Models in Laravel

When you are trying to filter some data or making a search query, you have to decide how you want to implement the filtering on eloquent models. I usually ask myself, should I include filtering logic in

  • Controllers?
  • Models?
  • using a Trait
  • using some sort of Service class
  • or put the logic in a seprate Repository

All of the above approaches have valid reasons to be used. In this post, I will share how I commonly do eloquent models filtering in Laravel.

Note

It’s up to you how you want to do this, I am just sharing my approach.

My approach to achieving, filtering is a bit different as personally, I avoid to inject service classes through service container.

Let me show you, how I will do model filtering in Laravel.

Consider we have a products table with the following fields.

  • Name
  • Short Description
  • Long Description
  • Color
  • Size
  • Price
  • Quantity

A very simple structure just for demonstration.

Now we want to do provide a fuzzy search for the Name, Short Description and Long Description, while we want to do exact match search for Color and Size.

Fuzzy Search is a process that locates the relevant matching for a search argument. For example, we want to search for iPhone and this search term should be in the name, short description or long description. We also want to search for the iPhone with silver color and size should be 32GB. This is an exact matching search.

Now. let’s set up our model for products table.

use Illuminate\Database\Eloquent\Model;

class Product extends Model {

    protected $fillable = [ 'name', 'short_description', 'long_description', 
                            'color', 'size', 'price', 'quantity' ];

}

Nothing special with this model it’s just a basic one as you always have it. Now we can start interacting with this model in a controller called ProductController.

use App\Product;
use App\Http\Controllers\Controller;

class ProductController extends Controller {

    public function index()
    {
        $products = Product::all();
        $products = Product::where('name', 'LIKE', '%iPhone%')->get();
    }
}

Very basic records filtering as we always do. Consider, we have a form on your catalog listing page for filtering down the results based on different criteria. We will send the request from frontend to the backend using a POST request, Ajax request or using VueJs. Still easy as we can add checks in our controller method for this purpose, like so:

use App\Product;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class ProductController extends Controller {

    public function index(Request $request)
    {
        $products = Product::query();

        if ( $request->has('name') && trim($request->input('name')) !== '' )
        {
            $products = $products->where('name', 'LIKE', trim($request->input('name')) . '%');
        }

        $products = $products->get();

        return view('index', compact('products'));
    }
}

As you can see, your controller will start getting messy when we will add the logic for other attributes like color, size, and descriptions.

At this point, we have to refactor our code and move all filtering logic to somewhere else to keep our code clean and follow the Don’t Repeat Yourself (DRY) rule.

Welcome to Query Scopes.

Using Query Scopes

Laravel scopes allows us to easily use query logic within the models. To define a scope simply prefix a eloquent models method with scope. You can learn more about query scopes in my recent post Using Scopes in Laravel

We will transfer our filtering logic to Product model with a scope called filter, like so:

use Illuminate\Database\Eloquent\Model;

class Product extends Model {

    protected $fillable = [ 'name', 'short_description', 'long_description', 'color', 'size', 'price', 'quantity' ];

    public function scopeFilter($query, $params)
    {
        if ( isset($params['name']) && trim($params['name'] !== '') ) {
            $query->where('name', 'LIKE', trim($params['name']) . '%');
        }
        return $query;
    }
}

Now we can update our controller to using query scope within the controller by passing the request object to the scope.

use App\Product;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class ProductController extends Controller {

    public function index(Request $request)
    {
        $params = $request->except('_token');

        $products = Product::filter($params)->get();

        return view('index', compact('products'));
    }
}

You can see how clean our controller’ code is and how easy it will be to extend the filtering functionality. Now we will adjust our filter scope to filter other parameters.

use Illuminate\Database\Eloquent\Model;

class Product extends Model {

    protected $fillable = [ 'name', 'short_description', 'long_description', 'color', 'size', 'price', 'quantity' ];

    public function scopeFilter($query, $params)
    {
        if ( isset($params['name']) && trim($params['name'] !== '') ) {
            $query->where('name', 'LIKE', trim($params['name']) . '%');
        }

        if ( isset($params['color']) && trim($params['color']) !== '' )
        {
            $query->where('color', '=', trim($params['color']));
        }

        if ( isset($params['size']) && trim($params['size']) !== '' )
        {
            $query->where('size', '=', trim($params['size']));
        }
        return $query;
    }
}

In above code base, we have added color and size filtering to find the exact match, while name filtering will be done using fuzzy search.

Note

Keep in mind above query will perform an AND search. Like SELECT * FROM products where name LIKE ‘what ever you pass’ AND color = ‘what ever color you pass’. Application performance will depend on how you chain the WHERE clause.

Now using the same above approach you can add as many filters as you want.

I like to use scopes a lot because they provide more control over your logic and freedom to use wherever you want without repeating yourself. After all, it’s totally up to you how you want to write your code, I just shared my approach.

If you have any question or you want to share your method, please leave in the comments box below.

Your little help will keep this site alive and help us to produce quality content for you.