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

Ideas for a better global search filter implementation #94

Open
darkons opened this issue Oct 6, 2022 · 0 comments
Open

Ideas for a better global search filter implementation #94

darkons opened this issue Oct 6, 2022 · 0 comments

Comments

@darkons
Copy link

darkons commented Oct 6, 2022

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.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant