mozex/laravel-searchable
Add a Searchable trait to any Eloquent model to search multiple columns and related data (relations, morphs, even cross-database) via a single ->search() call. Works with Laravel Scout and includes optional Filament table/global search integration.
Installation:
composer require mozex/laravel-searchable
No additional configuration or migrations are required.
Basic Setup:
Add the Searchable trait to your Eloquent model and define searchableColumns():
use Mozex\Searchable\Searchable;
class Comment extends Model
{
use Searchable;
public function searchableColumns(): array
{
return ['body', 'author.name']; // Direct column + relation
}
public function author()
{
return $this->belongsTo(User::class);
}
}
First Search:
$results = Comment::search('laravel')->get();
Or chain with other queries:
Comment::query()->where('published', true)->search($request->q)->paginate();
Mozex\Searchable\Searchable trait and Mozex\Searchable\SearchableScope classMozex\Searchable\Filament\SearchableGlobalSearchProviderDefine Searchable Columns:
Use dot notation for relations (author.name) and colon notation for morphs (commentable:post.title).
public function searchableColumns(): array
{
return [
'title', // Direct column
'author.name', // BelongsTo relation
'tags.name', // HasMany relation
'commentable:post.title', // Morph relation
];
}
Search Execution:
// Basic search
Model::search('term')->get();
// With query constraints
Model::query()->where('status', 'active')->search('term')->get();
// Dynamic column filtering
Model::search('term', in: ['title', 'body'])->get();
Filament Integration:
// Table column
TextColumn::make('title')->advancedSearchable()->sortable();
// Global search (register provider in panel)
$panel->globalSearch(SearchableGlobalSearchProvider::class);
Cross-Database Relations:
Automatically handled for BelongsTo/MorphTo relations. Adjust externalLimit if needed:
Model::search('term', externalLimit: 200)->get();
Dynamic Column Control: Override searchable columns per query:
Model::search('term', include: ['slug'], except: ['author.name'])->get();
Conflict Resolution:
use Mozex\Searchable\Searchable as DatabaseSearchable;
class Model extends BaseModel {
use DatabaseSearchable { scopeSearch as scopeDatabaseSearch; }
}
search() in the builder:
class CustomBuilder extends Builder {
public function search($term) {
$this->getModel()->applySearch($this, $term);
return $this;
}
}
Performance Optimization:
pg_trgm GIN indexes on PostgreSQL for LIKE '%term%' queries.tenant_id) before applying search.Case Sensitivity:
LIKE/ILIKE). For case-sensitive searches, modify column collation in the database.Café ≠ café).Wildcard Performance:
%term%) prevent index usage. For large tables, consider:
tsvector, MySQL FULLTEXT).Cross-Database Limits:
50 IDs by default (adjust with externalLimit).IN clauses may cause memory issues or timeouts.Method Conflicts:
scopeSearch is already defined (e.g., in a parent model), alias the trait method:
use Mozex\Searchable\Searchable { scopeSearch as scopeDbSearch; }
search() to delegate to applySearch().Filament Global Search:
getGloballySearchableAttributes().Searchable trait use Filament’s default global search.Query Inspection:
Use Laravel’s query logging or toSql() to verify generated SQL:
$query = Model::search('term')->toSql();
Column Validation:
Ensure relation paths in searchableColumns() match your model’s actual relations (e.g., author.name requires author() method).
External Relation Debugging:
externalLimit if searches return incomplete results.Performance Profiling:
DB::enableQueryLog() to analyze subqueries for morph/relation searches.EXPLAIN ANALYZE (PostgreSQL) or EXPLAIN (MySQL) to identify bottlenecks.Custom Search Logic:
Override applySearch() in your model for bespoke behavior:
public function applySearch(Builder $query, $term, array $options = [])
{
// Custom logic here
parent::applySearch($query, $term, $options);
}
Filament Macro Customization:
Extend advancedSearchable() for additional parameters:
TextColumn::macro('customSearchable', function () {
return $this->advancedSearchable(method: 'databaseSearch', include: ['slug']);
});
Scout Hybrid Search: Combine database and Scout searches:
$databaseResults = Model::databaseSearch('term')->get();
$scoutResults = Model::search('term')->get();
$merged = $databaseResults->merge($scoutResults);
Dynamic Column Loading:
Use Laravel’s with() to eager-load relations for search:
Model::with(['author', 'tags'])->search('term')->get();
How can I help you explore Laravel packages today?