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.
## Technical Evaluation
### **Architecture Fit**
- **Strengths**:
- **Eloquent Integration**: Seamlessly integrates with Laravel's Eloquent ORM, leveraging existing query builder patterns without requiring external search engines (e.g., Scout/Algolia). Ideal for applications where SQL-based search suffices or where full-text search engines are overkill.
- **Multi-Column/Morph Support**: Enables complex search queries across direct columns, relations (BelongsTo/HasMany), polymorphic relations, and even cross-database relations. This aligns well with Laravel applications requiring hierarchical or polymorphic data traversal (e.g., comments on posts/videos, multi-tenant systems).
- **Filament Compatibility**: Native support for Filament’s admin panel (global search, table search) reduces frontend-backend friction, accelerating MVP development for admin interfaces.
- **Lightweight**: No additional infrastructure (e.g., Elasticsearch) or config files; zero migration overhead. Fits microservices or serverless architectures where database operations are preferred.
- **Weaknesses**:
- **Performance Limitations**: Relies on `LIKE '%term%'` queries, which are inefficient for large datasets (>1M rows) due to full-table scans. Indexes on search columns won’t help, and correlated subqueries for relations add overhead.
- **No Full-Text Indexing**: Lacks advanced features like fuzzy matching, stemming, or relevance scoring (unlike Scout/Meilisearch). Case sensitivity is database-dependent (e.g., MySQL collations).
- **Cross-Database Workarounds**: External relation searches use `WHERE IN` with a hard cap (50 IDs by default), risking incomplete results for broad queries. Requires manual tuning (`externalLimit`).
- **Scout Conflict**: Potential naming collisions with Laravel Scout’s `search()` method, necessitating trait aliasing or manual method invocation.
### **Integration Feasibility**
- **Prerequisites**:
- **Laravel 10+** (PHP 8.2+): Compatible with modern Laravel stacks but may require minor adjustments for legacy projects (e.g., custom query builders).
- **Database Support**: Tested on MySQL, PostgreSQL, SQLite. Cross-database relations require additional configuration (e.g., connection aliases).
- **Filament (Optional)**: If using Filament, ensures zero-config global/table search. Non-Filament projects can ignore this feature.
- **Dependencies**:
- **Core**: Zero external dependencies beyond Laravel/Eloquent.
- **Filament**: Requires Filament v3+ for admin integration.
- **Scout**: Coexists but requires explicit method aliasing to avoid conflicts.
- **Customization**:
- **Dynamic Columns**: Supports runtime column filtering (`in`, `include`, `except`), enabling flexible search logic per query.
- **Query Chaining**: Works alongside Eloquent’s `where()`, `orderBy()`, etc., preserving existing query patterns.
### **Technical Risk**
- **High-Risk Areas**:
- **Performance at Scale**: Unsuitable for high-volume search (>100K rows/table) without optimizations (e.g., `pg_trgm` on PostgreSQL, Scout fallback).
- **Cross-Database Queries**: External relation limits (50 IDs) may truncate results. Requires testing with real-world data volumes.
- **Case Sensitivity**: Behavior varies by database (e.g., SQLite’s ASCII-only `LIKE`). May need collation adjustments.
- **Trait Conflicts**: Existing `scopeSearch` methods (e.g., custom builders) require manual resolution via `applySearch()` or trait aliasing.
- **Mitigation Strategies**:
- **Hybrid Approach**: Use Scout for global search + this package for admin/table searches (e.g., Filament).
- **Fallback Logic**: Implement a `scoutFallback()` method to switch to Scout when query performance degrades (e.g., based on row count).
- **Testing**: Validate cross-database relations with production-like data volumes and edge cases (e.g., empty results, large `IN` clauses).
- **Documentation**: Clearly outline trait aliasing steps for Scout/Filament integrations in the team’s Laravel style guide.
### **Key Questions**
1. **Search Volume/Scale**:
- How many rows will be searched per table? If >100K, is Scout/Meilisearch a viable fallback?
- Are there specific performance benchmarks (e.g., max acceptable query time)?
2. **Data Complexity**:
- Will searches frequently traverse polymorphic relations or cross-database links? If so, test `externalLimit` thresholds.
- Are there case-sensitivity requirements (e.g., exact matches for usernames)?
3. **Integration Constraints**:
- Does the project use custom Eloquent builders (e.g., Corcel) or parent model `scopeSearch` methods? If yes, plan for `applySearch()` usage.
- Is Filament used? If not, can the package’s Filament features be ignored?
4. **Maintenance**:
- Who will handle trait conflicts (e.g., Scout aliasing) during onboarding? Should this be automated via a base model?
- Are there plans to extend search functionality (e.g., fuzzy matching)? If so, Scout may be a better long-term fit.
5. **Observability**:
- How will slow queries be monitored? Consider logging `LIKE` query execution times.
- Are there plans to add query caching for frequent searches?
---
## Integration Approach
### **Stack Fit**
- **Ideal Use Cases**:
- **Admin Interfaces**: Filament-powered dashboards with global/table search (e.g., e-commerce product catalogs, CMS content).
- **Internal Tools**: Applications where search is secondary to CRUD (e.g., support ticket systems, analytics dashboards).
- **Polyglot Persistence**: Systems with cross-database relations (e.g., SaaS multi-tenancy with separate DBs per tenant).
- **MVP Development**: Rapid prototyping where full-text search engines are premature.
- **Poor Fit**:
- **High-Traffic Public Faces**: E-commerce product search, job boards, or any user-facing search with >10K queries/day.
- **Advanced Search Features**: Need for fuzzy matching, synonyms, or relevance ranking (e.g., "Did you mean?" suggestions).
- **Legacy Systems**: Projects with deeply customized Eloquent builders or trait conflicts that can’t be resolved.
### **Migration Path**
1. **Assessment Phase**:
- Audit existing search implementations (e.g., custom `LIKE` queries, Scout).
- Identify models requiring search functionality and their relation structures.
2. **Pilot Integration**:
- Start with a non-critical model (e.g., `Comment` or `Tag`) to test:
- Basic column search (`searchableColumns`).
- Relation traversal (e.g., `author.name`).
- Filament integration (if applicable).
- Measure query performance (e.g., `EXPLAIN ANALYZE`) and compare to existing solutions.
3. **Incremental Rollout**:
- **Phase 1**: Replace simple `LIKE` queries with the package’s trait (e.g., `Post::where('title', 'LIKE', '%term%')` → `Post::search('term')`).
- **Phase 2**: Add relation/morph support for complex queries.
- **Phase 3**: Integrate with Filament (global/searchable columns).
- **Phase 4**: Implement Scout fallback for slow queries (e.g., via middleware or a `Searchable` interface).
4. **Conflict Resolution**:
- For Scout conflicts, create a base model with trait aliasing:
```php
abstract class SearchableModel extends Model
{
use Mozex\Searchable\Searchable {
scopeSearch as scopeDatabaseSearch;
}
}
```
- For custom builders, override `search()` to delegate to `applySearch()`:
```php
class ProductBuilder extends Builder
{
public function search($term, array $options = [])
{
return $this->getModel()->applySearch($this, $term, ...$options);
}
}
```
### **Compatibility**
- **Laravel Versions**: Officially supports Laravel 10+. Test compatibility with Laravel 11 if using beta/RC.
- **Database Compatibility**:
- **MySQL/PostgreSQL**: Fully supported. PostgreSQL benefits from `pg_trgm` for `LIKE` performance.
- **SQLite**: Case-insensitive only for ASCII; may need collation adjustments for non-ASCII data.
- **SQL Server**: Untested; may require custom query adjustments.
- **Relation Types**:
- **BelongsTo/HasMany**: Native support.
- **Polymorphic**: Requires explicit morph map configuration.
- **ManyToMany**: Supported via relation dot notation (e.g., `tags.name`).
- **External DBs**: Works but with `WHERE IN` limits (configurable via `externalLimit`).
### **Sequencing**
1. **Core Integration**:
- Add `Searchable` trait to models and define `searchableColumns()`.
- Test basic searches (`Model::search('term')`).
2. **Relation Support**:
- Implement relation/morph searches (e.g., `author.name`, `commentable:post.title`).
- Validate cross-database queries with `externalLimit` tuning.
3. **Query Optimization**:
- Add indexes to filtered columns (e
How can I help you explore Laravel packages today?