Laravel Filter Query Using Pipeline

Laravel Filter Query Using Pipeline

In our application sometimes we provide the search and filter functionality to the user. In this article, we will see how we can filter the data using the pipeline in Laravel.

What is Pipeline?

Pipeline is a design pattern that allows us to pass an object through a series of stages so that at each stage we can operate on the object in some way. The pipeline is also known as pipes and filters.

Fatty Controller Problem

In the controller, we write the code for the filter and search functionality. The controller becomes fat and it is not a good practice. We should keep the controller thin.

See the below example of the controller.

<?php

namespace App\Http\Controllers;

use App\Models\Book;
use Illuminate\Http\Request;

class BookController extends Controller
{
    public function index(Request $request)
    {
        $books = Book::query();

        if ($request->has('title')) {
            $books->where('title', 'like', '%' . $request->title . '%');
        }

        if ($request->has('author')) {
            $books->where('author', 'like', '%' . $request->author . '%');
        }

        if ($request->has('category')) {
            $books->where('category', 'like', '%' . $request->category . '%');
        }

        $books = $books->paginate();

        return view('books.index', compact('books'));
    }
}

Or maybe something like this.

<?php

namespace App\Http\Controllers;

use App\Models\Book;
use Illuminate\Http\Request;

class BookController extends Controller
{
    public function index(Request $request)
    {
        $books = Book::query();

        $books->when($request->has('title'), function ($query) use ($request) {
            $query->where('title', 'like', '%' . $request->title . '%');
        });

        $books->when($request->has('author'), function ($query) use ($request) {
            $query->where('author', 'like', '%' . $request->author . '%');
        });

        $books->when($request->has('category'), function ($query) use ($request) {
            $query->where('category', 'like', '%' . $request->category . '%');
        });

        $books = $books->paginate();

        return view('books.index', compact('books'));
    }
}

So we can see that the controller is fat and we should keep the controller thin.

Solution

There are many solutions to this problem. We can use the repository pattern or we can use the pipeline. In this article, we will see how we can use the pipeline to filter the data.

Create Pipeline

First, we will create a pipeline class. We will create the classes, we want to filter and search in the app/Pipelines/Filter directory.

mkdir app/Pipelines
mkdir app/Pipelines/Filter

Now we will create a Filter class for each action.

touch app/Pipelines/Filter/Title.php
touch app/Pipelines/Filter/Author.php
touch app/Pipelines/Filter/Category.php
touch app/Pipelines/Filter/Search.php

So we have created all the classes. Now we will write the code for each class. Each class will have a handle method. The handle method will accept the query and the next filter closure. We will filter the data in the handle method.

app/Pipelines/Filter/Title.php

<?php

namespace App\Pipelines\Filter;

use Closure;
use Illuminate\Database\Eloquent\Builder;

class Title
{
    public function handle(Builder $query, Closure $next) { 
        $search = request()→input('title');
        $query = $query->where('title', 'like', "%{$search}%"); 
        return $next($query);
    }
}

app/Pipelines/Filter/Author.php

<?php

namespace App\Pipelines\Filter;

use Closure;
use Illuminate\Database\Eloquent\Builder;

class Author
{
    public function handle(Builder $query, Closure $next) { 
        $search = request()→input('author');
        $query = $query->where('author', 'like', "%{$search}%"); 
        return $next($query);
    }
}

app/Pipelines/Filter/Category.php

<?php

namespace App\Pipelines\Filter;

use Closure;
use Illuminate\Database\Eloquent\Builder;

class Category
{
    public function handle(Builder $query, Closure $next) { 
        $search = request()→input('category');
        $query = $query->where('category', 'like', "%{$search}%"); 
        return $next($query);
    }
}

app/Pipelines/Filter/Search.php

<?php

namespace App\Pipelines\Filter;

use Closure;
use Illuminate\Database\Eloquent\Builder;

class Search
{
    public function handle(Builder $query, Closure $next) { 
        $search = request()→input('search');
        $query = $query->where('title', 'like', "%{$search}%")
            ->orWhere('author', 'like', "%{$search}%")
            ->orWhere('category', 'like', "%{$search}%"); 
        return $next($query);
    }
}

So our pipeline is ready. Now we will use the pipeline. To use the pipeline we can use the scopeFilter method in the model or we can use the trait. To make more sense we will use the trait.

touch app/Traits/Filterable.php

Now we will write the code in the trait.

<?php

namespace App\Traits;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Pipeline\Pipeline;

trait Filterable
{
    public function scopeFilter(Builder $query, array $filters)
    {
        return Pipeline::send($query)
            ->through($filters)
            ->thenReturn();
    }
}

Now we will use the trait in the model.

<?php

namespace App\Models;

use App\Traits\Filterable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Book extends Model
{
    use HasFactory, Filterable;

    // ...
}

Okay our all setup is ready. Now we refactor the controller.

<?php

namespace App\Http\Controllers;

use App\Models\Book;
use Illuminate\Http\Request;
use App\Pipelines\Filter\Title;
use App\Pipelines\Filter\Author;
use App\Pipelines\Filter\Category;
use App\Pipelines\Filter\Search;

class BookController extends Controller
{
    public function index(Request $request)
    {
        $books = Book::query()
        ->filter([Title::class, Author::class, Category::class, Search::class])
        ->paginate();

        return view('books.index', compact('books'));
    }
}

So we can see that our controller is thin now. We can add more filters in the pipeline. We can also add the sort functionality in the pipeline.

Conclusion

There are so many use cases of pipeline and also we can filter the data in many ways. In this article, we have seen how we can use the pipeline to filter the data. I hope you like this article.

In that case, if you need any help regarding this you can easily contact me from here, I will be happy to help you.

Reference