Installation
composer require ahmedabdo/searchable
Publish the config (if needed):
php artisan vendor:publish --provider="Abdo\Searchable\SearchableServiceProvider"
First Use Case
Add the Searchable trait and define searchable columns in your model:
use Abdo\Searchable\Searchable;
use Abdo\Searchable\Attributes\SearchColumns;
class User extends Authenticatable
{
use Searchable;
#[SearchColumns]
public $searchable = [
"columns" => ["name", "email"],
];
}
Search via a query scope:
$results = User::search('john')->get();
Where to Look First
searchable array in your model.search() and filter() methods in queries.filter-blade-script for UI integration.Basic Search
// Search across defined columns
User::search('john doe')->get();
Filtering
// Filter by exact match (default)
User::filter('role.name', 'admin')->get();
// Use operators (e.g., `contains`, `starts_with`)
User::filter('email', 'contains', 'test@example')->get();
Combining Search and Filter
User::search('john')
->filter('role.name', 'admin')
->get();
Dynamic Column Configuration
Override searchable columns per query:
User::search('query', ['columns' => ['name', 'email', 'phone']])->get();
Custom Search Logic
Use customSearch() for complex queries:
User::customSearch(function ($query, $search) {
return $query->where('name', 'like', "%{$search}%")
->orWhere('email', 'like', "%{$search}%");
})->get();
return User::search(request('q'))->filter(request('filters'))->get();
@filterForm(['role.name', 'email'])
partialMock for model tests:
$mock = $this->partialMock(User::class, ['searchable']);
Eager Loading
eager relationships in searchable causes N+1 queries:
#[SearchColumns]
public $searchable = [
"columns" => ["role.name"], // Requires eager loading
"eager" => ["role"] // <-- Critical!
];
Case Sensitivity
search() method.Reserved Keywords
created_at without escaping (use backticks in raw queries if needed).Performance
FULLTEXT indexes).DB::enableQueryLog();
User::search('test')->toSql(); // Dump raw SQL
filter() with invalid operators silently fails. Validate inputs:
$validOperators = ['contains', 'starts_with', 'exact'];
if (!in_array(request('operator'), $validOperators)) {
abort(400, 'Invalid operator');
}
Custom Operators
Extend the FilterOperator class or add via config:
// config/searchable.php
'operators' => [
'custom_operator' => function ($column, $value) {
return "UPPER({$column}) LIKE UPPER('%{$value}%')";
},
];
Global Searchable Config
Override defaults in config/searchable.php:
'default_columns' => ['name', 'email'], // Fallback columns
'default_eager' => [], // Global eager loads
Macros for Query Builder Add reusable search/filter macros:
\Illuminate\Database\Eloquent\Builder::macro('scopeAdvancedSearch', function ($query, $search) {
return $query->where(function ($q) use ($search) {
$q->where('name', 'like', "%{$search}%")
->orWhere('email', 'like', "%{$search}%");
});
});
contains or starts_with for flexible searches:
User::filter('name', 'contains', 'doe')->get(); // Matches "John Doe"
User::search('query')->paginate(10);
Cache::remember("search_{$query}", now()->addHours(1), function () use ($query) {
return User::search($query)->pluck('name')->toArray();
});
How can I help you explore Laravel packages today?