# Squeezing the Eloquent builder through a Pipeline

Laravel - Quick Tip - February 16th, 2020
Squeezing the Eloquent builder through a Pipeline

A common feature everybody has to implement at some point, is the ability to mutate the database query based on the request's query parameters. Look at the following example:

https://my-site.com/api/products?orderBy=price:desc&color=yellow

We want this endpoint to return all products which are yellow and order them by price in descending order. There are several approaches to solve this problem in our Laravel app. I want to show you the most simple, most straightforward ways.

# Using an if statment

This is the probably the first solution you come up with and is mostly fine for a simple app. Your controller might look like this:

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

        if ($request->has('orderBy')) {
            // Handle order by...
        }

        if ($request->has('color')) {
            // Handle given color...
        }

        // Handle all other filters...

        $products = $query->get();

        // Return view...
    }
}

It is pretty obvious that this approach is not very DRY but might be an OK solution if you don't have any other filterable resources than a product. If you have multiple Models which should be filterable, you might consider writing a filter scope.

# Using an eloquent scope

So you have at least two Models which require the ability to be filtered based on the request's query parameters. You decide to write a scope on a trait which then can be applied to the Models which need to be filtered. Your solution might look like this:

trait Filter
{
    public function scopeFilter($builder)
    {
        $request = request();

        if ($request->has('orderBy')) {
            // Handle order by...
        }

        if ($request->has('color')) {
            // Handle given color...
        }
    }
}

Now this trait can be applied to any Model.

class Product extends Model
{
    use Filter;

    //...
}

And be used in our controller like this:

class ProductController
{
    public function index()
    {
        $products = Product::filter()->get();

        // Return view...
    }
}

Now imagine our online store, which we are programming, also should have the ability to be filtered. We would apply the Filter trait to our Model and would now be easily able to use our api endpoint like this:

https://my-site.com/api/orders?orderBy=created_at:desc

But a problem now arises. If we add &color=yellow to the query it would raise an exception since the column color doesn't exist in our database!

OK this is not great but also not a big deal. We open our Filter trait back up, go to the line where we check if the request has color in it and also add a check that the builder object is for a query on the products table.

It is easy to see that this breaks down with growing complexity in our app. That's why i prefer the next approach which is using Laravel's Pipeline class.

# Using a pipeline

Chance is, that you don't know what a Pipeline does in Laravel but u use it in every single request! Laravel's Http Middleware uses pipelines internally. It takes the request object and applies the middleware classes in succession. In other words: It takes the request and sends it through a pipeline of middlewares and then returns the transformed request.

Let's think for a minute. We basically want to take a Model's query builder and send it through a pipeline of query filters.

But what does such a Filter class look like? Basically, in order for a pipeline to work, the classes need to have a handle method (the method name can be customized though) which takes the object, that is passed through the pipeline, as an argument as well as the $next callback.

Our OrderBy filter might look like this:

class OrderBy
{
    protected $request;

    public function __construct(Request $request)
    {
        $this->request = $request;
    }

    public function handle($builder, $next)
    {
        if ($request->has('orderBy')) {
            // Handle order by...
        }

        $next($builder);
    }
}

Where's the return?

Please note that we don't need to return $next($builder) since the builder object is mutable.

We use dependency injection to inject the request object to our object, so we can use it in the handle method. Doing this in every class for our pipeline might be a bit tedious. That is why I like to extract that logic to an abstract class.

<?php

namespace App\Filters;

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

abstract class Filter
{
    /**
     * The request.
     *
     * @var Request
     */
    protected $request;

    /**
     * Load the request via dependency injection into the filter.
     *
     * @param Request $request
     */
    public function __construct(Request $request)
    {
        $this->request = $request;
    }

    /**
     * Handle the filter and return the builder.
     *
     * @param Builder $builder
     * @param Closure $next
     * @return void
     */
    public abstract function handle(Builder $builder, Closure $next);
}

Great! Now we can simply extend this class in our filter.

class OrderBy extends Filter
{
    public function handle($builder, $next)
    {
        if ($request->has('orderBy')) {
            // ...
        }

        $next($builder);
    }
}

And do the same for our color filter.

class Color extends Filter
{
    public function handle($builder, $next)
    {
        if ($request->has('color')) {
            // ...
        }

        $next($builder);
    }
}

OK, now we have our filters in place. But how do we actually apply these to our Model query? In order to make these filters composable and solve the problem described earlier where we could use &color=yellow on the Order Model, we need a method on each Model which returns an array of filters to use.

class Product extends Model
{
    protected function getFilters()
    {
        return [
            \App\Filters\OrderBy::class,
            \App\Filters\Color::class,
        ];
    }
}

And finally, let's take another look at our Filter trait which we used earlier.

trait Filter
{
    public function scopeFilter($builder)
    {
        app(Pipeline::class)
            ->send($builder)
            ->through(
                $this->getFilters()
            )
            ->thenReturn();
    }

}

I think that code reads very well. First we create a new instance of Laravel's Pipeline. Then we take the $builder object, which gets passed as an argument to every query scope method we declare, and send it through the the filters we declared on our Model. Finally we need to return the result.

With this setup, we can use the filter in our code as excpected.

class ProductController
{
    public function index()
    {
        $products = Product::filter()->get();

        // return view...
    }
}

# In conclusion

Using a Pipeline is a very expressive, composible and flexible way to implement such a feature. It is easy to extend it with more and more filters and adding functionality without touching our controller's code ever again.

The setup is a bit tedious at first and newcomers might be overwhelmed by the use of pipelines which are mostly used by Laravel internally. But in my opinion this is a tradeoff well worth it.