Installation:
composer require shureban/laravel-searcher
Register the service provider in config/app.php:
Shureban\LaravelSearcher\SearcherServiceProvider::class,
Publish the config file (optional):
php artisan vendor:publish --provider="Shureban\LaravelSearcher\SearcherServiceProvider"
First Use Case:
Create a searcher class in app/Http/Searchers (e.g., UserSearcher.php):
namespace App\Http\Searchers;
use App\Models\User;
use Illuminate\Database\Eloquent\Builder;
use Shureban\LaravelSearcher\Filters\Like;
use Shureban\LaravelSearcher\Searcher;
class UserSearcher extends Searcher
{
protected function getQuery(): Builder
{
return User::query();
}
protected function getFilters(): array
{
return [
'name' => new Like('name'),
'email' => new Like('email'),
];
}
}
First Request Handling:
Create a request class (e.g., UserSearchRequest.php) with validation rules:
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rules\In;
use Shureban\LaravelSearcher\Enums\SortType;
class UserSearchRequest extends FormRequest
{
public function rules(): array
{
return [
'name' => 'nullable|string|max:255',
'email' => 'nullable|email',
'sort_column' => ['nullable', new In(['name', 'email', 'created_at'])],
'sort_type' => ['nullable', new In([SortType::ASC, SortType::DESC])],
];
}
}
First Controller Usage:
namespace App\Http\Controllers;
use App\Http\Requests\UserSearchRequest;
use App\Http\Searchers\UserSearcher;
use Illuminate\Http\JsonResponse;
class UserController extends Controller
{
public function search(UserSearchRequest $request): JsonResponse
{
$searcher = new UserSearcher($request);
$results = $searcher->paginate(); // or ->get(), ->all()
return response()->json($results);
}
}
Base Class: Extend Shureban\LaravelSearcher\Searcher.
Key Methods:
getQuery(): Return the base Eloquent query builder.getFilters(): Define request param → filter mappings.sortColumn() (optional): Customize default sort column.applySortBy() (optional): Override sort logic for specific columns.Example Workflow:
class ProductSearcher extends Searcher
{
protected function getQuery(): Builder
{
return Product::with(['category', 'reviews']);
}
protected function getFilters(): array
{
return [
'price' => new BetweenRange('price'),
'category_id' => new Equal('category_id'),
'in_stock' => new Boolean('is_active'),
'name' => new Like('name'),
'reviews.rating' => new Relation('reviews', new Gte('rating')),
];
}
}
| Filter Class | Use Case | Example |
|---|---|---|
Equal |
Exact match | 'id' => new Equal('id') |
Like |
Partial match (LIKE) | 'name' => new Like('name') |
Between |
Range (numeric) | 'price' => new Between('price') |
BetweenRange |
Range (numeric, inclusive) | 'salary' => new BetweenRange('salary') |
BetweenDates |
Date range | 'created_at' => new BetweenDates('created_at') |
In/NotIn |
Array of values | 'statuses' => new In('status') |
Gt/Gte/Lt/Lte |
Greater/Less than | 'age' => new Gt('age') |
IsNull |
NULL checks | 'image_id' => new IsNull('image_id') |
| Filter Class | Use Case | Example |
|---|---|---|
OrNull |
Match value or NULL | 'manager_id' => new OrNull(new Equal('manager_id')) |
OrEmpty |
Match value or empty string | 'full_name' => new OrEmpty(new Like('full_name')) |
MultipleOr |
Combine filters with OR logic | 'owner_id' => new MultipleOr(new Equal('user_id'), new Like('manager_id')) |
Relation |
Filter nested relations | 'reviews.rating' => new Relation('reviews', new Gte('rating')) |
Callback |
Custom SQL logic | 'only_every_even' => new Callback(fn($query, $value) => $query->whereRaw('(id % 2 = 0)')) |
Override sortColumn() to customize the default sort field:
protected function sortColumn(): ?string
{
return $this->request->get('sort_column', 'name'); // Default to 'name'
}
Override applySortBy() for complex sorting (e.g., multi-column sorts):
protected function applySortBy(Builder $query, string $sortColumn, string $sortType): Builder
{
return match ($sortColumn) {
'name' => $query->orderBy('name', $sortType)->orderBy('created_at', $sortType),
'price' => $query->orderByRaw('ABS(price - 100)', $sortType),
default => parent::applySortBy($query, $sortColumn, $sortType),
};
}
| Method | Use Case | Example |
|---|---|---|
get() |
Get results as a Collection |
$searcher->get() |
all() |
Get all results (no pagination) | $searcher->all() |
paginate() |
Get paginated LengthAwarePaginator |
$searcher->paginate(15) |
Combine with Laravel API Resources for structured responses:
// UserSearcher.php
public function toArray($request): array
{
return [
'data' => $this->paginate()->through(fn($user) => new UserResource($user)),
'meta' => ['page' => $this->paginate()->currentPage()],
];
}
public function test_searcher_filters()
{
$request = new UserSearchRequest(['name' => 'John']);
$searcher = new UserSearcher($request);
$query = $searcher->getQuery();
$query->toSql(); // Should contain `WHERE name LIKE '%John%'`
$this->assertDatabaseHas('users', ['name' => 'John']);
}
public function test_search_endpoint()
{
$response = $this->get('/api/users/search?name=John');
$response->assertStatus(200)
->assertJsonStructure(['data', 'meta']);
}
getFilters() must exactly match the request input keys.
// ❌ Fails: 'user_name' !== 'name'
return ['user_name' => new Like('name')];
IsNull may not work as expected with OrNull/OrEmpty due to query precedence.
// ❌ May not work as intended
return [
'id' => new OrNull(new Equal('id')),
'is_null' => new IsNull('id'), // Conflicts with above
];
IsNull alone or clarify logic with Callback.Relation('reviews', ...)) require the relation to be eager-loaded in getQuery().
// ❌ Fails: Relation not loaded
return ['reviews.rating' => new Relation('
How can I help you explore Laravel packages today?