Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

CPHM's avatar
Level 2

Comments on Automatic API filter and sort Trait

I'm building an API with Laravel and came across the problem of filtering and sorting, after some research and stumbling upon this helpful article I decided on using RHS Colon for filtering and sorting and built in Laravel way of pagination.

Sample URL: /users?full_name=start:ali&sort_by=last_name:asc&perPage=10&page=3

Being a bit lazy I don't want to write all the custom logic needed for this kind of filtering and sorting for every resource in my app, so I created this PHP Trait that is supposed to be used in an Eloquent Model and be used in index action of a resource controller like this:

return User::queried()->paginate($request->input('perPage', 10));

The Code for the trait:

namespace App\Traits;

trait Queried
{
    /**
     * Provide Automatic API filtering and sorting for Eloquent Models
     * Define array of filterable attributes as `static $filterable` on the Eloquent Model using the Trait
     * Define array of sortable attributes `static $sortable` on the Eloquent Model using the Trait
     * In case of invalid filter or sort attribute names in request will abort with appropriate JSON error response
     *
     * @return mixed
     */
    public static function queried()
    {
        $querySegments = array_map('urldecode', explode('&', request()->getQueryString()));
        $whereClauses = array();
        $whereIns = array();
        $sorts = array();
        $errors = array();
        foreach ($querySegments as $segment) {
            $regRes = array();
            if (preg_match('/(.+)=(eq|lte|gte|start|end|have|in):(.+)/', $segment, $regRes) === 1) {
                if (in_array($regRes[1], static::$filterable)) {
                    switch ($regRes[2]) {
                        case 'eq':
                            array_push($whereClauses, [$regRes[1], '=', $regRes[3]]);
                            break;
                        case 'lte':
                            array_push($whereClauses, [$regRes[1], '<=', $regRes[3]]);
                            break;
                        case 'gte':
                            array_push($whereClauses, [$regRes[1], '>=', $regRes[3]]);
                            break;
                        case 'start':
                            array_push($whereClauses, [$regRes[1], 'LIKE', $regRes[3] . '%']);
                            break;
                        case 'end':
                            array_push($whereClauses, [$regRes[1], 'LIKE', '%' . $regRes[3]]);
                            break;
                        case 'have':
                            array_push($whereClauses, [$regRes[1], 'LIKE', '%' . $regRes[3] . '%']);
                            break;
                        case 'in':
                            array_push($whereIns, ['attribute' => $regRes[1], 'values' => explode(',', $regRes[3])]);
                            break;
                    }
                } else {
                    $errors[$regRes[1]] = 'Invalid attribute for filter.';
                }
            } else if (preg_match('/sort_by=(.+):(asc|desc)/', $segment, $regRes) === 1) {
                if (in_array($regRes[1], static::$sortable)) {
                    array_push($sorts, ['attribute' => $regRes[1], 'order' => $regRes[2]]);
                } else {
                    $errors[$regRes[1]] = 'Invalid attribute for sort.';
                }
            }
        }
        if (!empty($errors)) {
            abort(400, json_encode(['errors' => $errors]));
        }
        $return = static::where($whereClauses);
        foreach ($whereIns as $whereIn) {
            $return = $return->whereIn($whereIn['attribute'], $whereIn['values']);
        }
        foreach ($sorts as $sort) {
            $return = $return->orderBy($sort['attribute'], $sort['order']);
        }
        $return = $return->orderBy('created_at', 'desc');
        return $return;
    }
}

I would appreciate it if you could comment on the way I've done things especially on:

  • Problems or improvements for the Trait
  • Is Laravel offset and limit pagination scalable for say 10000 instances of a resource in the database?
  • How can I implement some sort of caching for this kind of pagination?

And finally I was wondering why no such functionality is built into Laravel? Seems in line with the way Laravel provides convenient if somewhat opinionated ways of doing everyday things.

0 likes
0 replies

Please or to participate in this conversation.