Skip to content
This repository was archived by the owner on Jun 27, 2024. It is now read-only.
This repository was archived by the owner on Jun 27, 2024. It is now read-only.

Ideas for a better global search filter implementation #94

Open
@darkons

Description

@darkons

Hello,

I currently use global search in almost all my tables. I was looking for a way to improve the example documentation and would like to collaborate with other users on a possible cleaner option.

Currently according to the documentation you can do this:

$globalSearch = AllowedFilter::callback('global', function ($query, $value) {
    $query->where(function ($query) use ($value) {
        Collection::wrap($value)->each(function ($value) use ($query) {
            $query
                ->orWhere('name', 'LIKE', "%{$value}%")
                ->orWhere('email', 'LIKE', "%{$value}%");
        });
    });
});

And use like:

$users = QueryBuilder::for(User::class)
    ->defaultSort('name')
    ->allowedSorts(['name', 'email', 'language_code'])
    ->allowedFilters(['name', 'email', 'language_code', $globalSearch])
    ->paginate()
    ->withQueryString();

This works fine, but does not allow you to search through relationships globally in a simple and clean way.

Instead of the above, you can create a custom filter class like this:

<?php

namespace App\Classes\QueryBuilder\Filters;

use Illuminate\Support\Collection;
use Spatie\QueryBuilder\Filters\Filter;
use Illuminate\Database\Eloquent\Builder;

class GlobalSearchFilter implements Filter
{
    public function __construct(private array $keys)
    {
    }

    public function __invoke(Builder $query, $value, string $property): void
    {
        $query->where(function ($query) use ($value) {
            Collection::wrap($value)->each(function ($value) use ($query) {
                Collection::make($this->keys)->each(function ($key) use ($value, $query) {
                    if (str_contains($key, '.')) {
                        list($column, $relation) = $this->parseRelationKey($key);

                        $constraint = fn ($query) => $query->where($column, 'LIKE', "%{$value}%");

                        return $query->orWhereHas($relation, $constraint)
                            ->with($relation, $constraint);
                    }

                    return $query->orWhere($key, 'LIKE', "%{$value}%");
                });
            });
        });
    }

    private function parseRelationKey(string $key)
    {
        $array = explode('.', $key);
        $column = array_pop($array);
        $relation = implode('.', $array);

        return [$column, $relation];
    }
}

Then you can search through model columns and relationships with a few lines of code keeping your controller more cleaner:

$users = QueryBuilder::for(User::class)
    ->defaultSort('name')
    ->allowedSorts(['name', 'email', 'language_code'])
    ->allowedFilters(['name', 'email', 'language_code', AllowedFilter::custom('global', new GlobalSearchFilter([
        'name',
        'phone.number',
        'another.nested.relation',
    ])])
    ->paginate()
    ->withQueryString();

I am not a query builder expert so the filter could very possibly be optimised. Any suggestions would be appreciated.

I just wanted to share this idea with other users to improve the implementation of this table feature. Maybe we could get an almost perfect global filter class that could be included in the package itself.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions