Before starting pipelines in Laravel, you must know that what are pipeline design patterns. The Pipeline Design Pattern is a very useful design pattern where the complex process is divided into individual tasks and each individual task can be reused again and again.

Think about pipelines in real life. Each pipe could be connected with straight pipe, bent pipe, elbow or it could be ended and you can add the tap at the end from which water is flowing.

Now, how we will be able to translate this into code. In coding, many times you will see that bigger code will be broken into steps. In those steps, we need to give some input and process it and then give that processed input to the next step. This cycle continues until the final destination reached.

Now, you understand what pipelines are. Let's take a look into Laravel. Laravel already ships with an implementation of pipelines. So, we don't need to implement it from scratch. We can simply use it.

The best example of pipelines in Laravel is 'filtering and sorting data'. Let's say you have a list of data on a page and you want to filter and sort the data. Then you can use this technique also. First, we will use a simple way then we will move to the pipeline method. So first we will set up our project files.

Setup

Just a simple setup one route, controller, and view file. So in my route file, I have added one route i.e.,

...
Route::get('/demo', 'DemoController@index');
...

So, as you see the route, I am pointing my '/demo' route to DemoController's index method. So, my controller looks like this,

<?php

namespace App\Http\Controllers;

use App\User;
use Illuminate\Http\Request;

class DemoController extends Controller
{
    public function index(Request $request)
    {
        $users = User::all();
        return view('demo', compact('users'));
    }
}

As you see the index method, I am using the User model which fetches all the data from the users' table. After that passing that data to view and my blade file i.e. 'demo.blade.php' look like this,

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Pipelines</title>
</head>
<body>
    <table style="width: 100%; text-align: center;">
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Email</th>
                <th>Role</th>
                <th>Created At</th>
                <th>Updated At</th>
            </tr>
        </thead>
        <tbody>
            @foreach($users as $user)
                <tr>
                    <td>{{ $user->id }}</td>
                    <td>{{ $user->name }}</td>
                    <td>{{ $user->email }}</td>
                    <td>{{ $user->role }}</td>
                    <td>{{ $user->created_at }}</td>
                    <td>{{ $user->updated_at }}</td>
                </tr>
            @endforeach
        </tbody>
    </table>
</body>
</html>

After all this setup my output in the browser looks like this, on this page, I have 100 records but due to large image, I cropped the first 25.

Till now everything is common. Just a controller, model, and view stuffs.

Note: I have used dummy data by creating a factory and I will only use the users' table.

Let's Get Started

Let's say I want to filter users by roles, then we need to adjust the controller's method in which we need to check that if there is a 'role' key present in request then we need to filter the data by its value. So, my updated method looks like this,

<?php

namespace App\Http\Controllers;

use App\User;
use Illuminate\Http\Request;

class DemoController extends Controller
{
    public function index(Request $request)
    {
        /* this time we need query because we are checking the filters */
        $users = User::query();

        /* checking role filter */
        if ($request->has('role')) {
            $users->where('role', $request->input('role'));
        }

        /* get filtered data */
        $users = $users->get();

        return view('demo', compact('users'));
    }
}

Let's hit request in URL in my case URL is, http://dev.demo/demo?role=User,

As you see the above image, I am using the 'role' key in URL which filters data by user role. Now, let's say we want to filter the name as well. We can add one more condition in method,

<?php

namespace App\Http\Controllers;

use App\User;
use Illuminate\Http\Request;

class DemoController extends Controller
{
    public function index(Request $request)
    {
        ...
        /* checking name filter */
        if ($request->has('name')) {
            $users->where('name', 'LIKE', '%' . $request->input('name') . '%');
        }
        ...
    }
}

Now check your output by adding one more query. In my case my URL and query strings look like http://dev.demo/demo?role=User&name=wendy, in your case, it may vary.

As you see that we are using simple 'if' cases to filter the data. Our controller's method will get bigger and bigger if there are so many filters. So let's use the pipeline here.

Now we separate each filter with classes. Let's create one folder name Filters in app directory. Create one class name RoleFilter inside it. It will look like this,

<?php

namespace App\Filters;

use Closure;

class RoleFilter
{
    /**
     * we need to use laravel convention so we need to create the
     * method name 'handle' because when we use pipeline in laravel
     * then default method name is handle
     *
     * @param $request => argument that you passed in send() method
     * @param Closure $next => this closure will pass your argument to
     *                         next element in array that you have passed
     *                         in through() method
     * @return mixed => you need to return your filtered data to next element
     */
    public function handle($request, Closure $next)
    {
        if (!request()->has('role')) {
            return $next($request);
        }

        return $next($request)->where('role', request()->input('role'));
    }
}

After that, you need to modify your DemoController's index method and implement the pipeline.

<?php

namespace App\Http\Controllers;

use App\User;
use Illuminate\Http\Request;
use Illuminate\Pipeline\Pipeline;

class DemoController extends Controller
{
    public function index(Request $request)
    {
        $users = app(Pipeline::class)
            /* this send method will pass your query to handle method */
            ->send(User::query())
            /* this is the list of filter classes which will hit on each next request */
            ->through([
                \App\Filters\RoleFilter::class
            ])
            /* this will return the final output */
            ->thenReturn();

        /* checking name filter */
        if ($request->has('name')) {
            $users->where('name', 'LIKE', '%' . $request->input('name') . '%');
        }

        /* get filtered data */
        $users = $users->get();

        return view('demo', compact('users'));
    }
}

Now check your output. Everything is the same as the previous output. Same you can do for name filter as well. Just create NameFilter class inside the Filters folder. Your class will look like this,

<?php

namespace App\Filters;

use Closure;

class NameFilter
{
    public function handle($request, Closure $next)
    {
        if (!request()->has('role')) {
            return $next($request);
        }

        return $next($request)->where('name', 'LIKE', '%' . request()->input('name') . '%');
    }
}

Now change the DemoController's index method.

<?php

namespace App\Http\Controllers;

use App\User;
use Illuminate\Http\Request;
use Illuminate\Pipeline\Pipeline;

class DemoController extends Controller
{
    public function index(Request $request)
    {
        $users = app(Pipeline::class)
            /* this send method will pass your query to handle method */
            ->send(User::query())
            /* this is the list of filter classes which will hit on each next request */
            ->through([
                \App\Filters\RoleFilter::class,
                /* one more added */
                \App\Filters\NameFilter::class
            ])
            /* this will return the final output */
            ->thenReturn()
            /* chaining get() method directly */
            ->get();

        return view('demo', compact('users'));
    }
}

Now check the output you will see the same results as before.

Conclusion

We have used two methods i.e. first one is a normal if-else case and the second is the pipeline. Yeah, I know this can be refactored more. But this is the only explanation so I am leaving the refactoring part. You can create more filters and sortings with this. The best place where you can implement this is the server-side data table or some other filtration kinds of stuff.

Last Modified: 4 months ago