eslam-dev / laravel-searchable
Laravel Searchable — dedicated searchable columns for Eloquent with cleaning, FULLTEXT search, and rebuild tooling
Requires
- php: ^8.2
- illuminate/console: ^12.0
- illuminate/contracts: ^12.0
- illuminate/database: ^12.0
- illuminate/support: ^12.0
Requires (Dev)
- mockery/mockery: ^1.6
- orchestra/testbench: ^10.0
- phpunit/phpunit: ^11.0
README
A Laravel package for dedicated searchable text columns on Eloquent models: automatic fill on save, text cleaning, and MySQL FULLTEXT search.
Features
✅ Migration Macros - Simple searchableColumn() macro for creating LONGTEXT columns with FULLTEXT indexes
✅ Automatic Population - Searchable columns are automatically updated on model save
✅ Text Cleaning - Comprehensive text normalization (HTML stripping, Arabic normalization, diacritic removal)
✅ FULLTEXT Search - Native MySQL FULLTEXT search with natural language mode
✅ Multiple Columns - Support for multiple searchable columns per model
✅ Relationship Support - Extract text from relationships using dot notation
✅ Translatable Fields - Automatic extraction from JSON/translatable fields
✅ Queue Support - Rebuild searchable columns via queue for large datasets
✅ Highly Configurable - Customize cleaning rules, separators, and extraction methods
Requirements
- PHP 8.2+
- Laravel 12.0+
- MySQL 5.6+ or MariaDB 10.0+
Installation
Install via Composer:
composer require eslam-dev/laravel-searchable
Publish the configuration file:
php artisan vendor:publish --tag=laravel-searchable-config
Quick Start
1. Add Searchable Columns to Migration
use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; Schema::create('products', function (Blueprint $table) { $table->id(); $table->json('title'); $table->json('description'); $table->string('sku'); // Add searchable columns with automatic FULLTEXT index $table->searchableColumn('title_search'); $table->searchableColumn('description_search'); $table->searchableColumn('combined_search'); $table->timestamps(); });
2. Add Trait to Model
use Illuminate\Database\Eloquent\Model; use EslamDev\SearchableColumns\Traits\HasSearchableColumns; class Product extends Model { use HasSearchableColumns; protected array $searchableColumns = [ // Simple: one source field 'title_search' => ['title'], // Multiple source fields 'description_search' => ['description', 'short_description'], // Combined search across multiple fields 'combined_search' => ['title', 'description', 'sku'], ]; }
3. Search Your Data
// Search across all defined searchable columns $products = Product::search('iphone 13 pro')->get(); // Search specific column $products = Product::searchIn('title_search', 'iphone')->get(); // Search multiple columns $products = Product::searchIn(['title_search', 'description_search'], 'phone')->get(); // Combine with other filters $products = Product::search('laptop') ->where('status', 'published') ->inStock() ->get();
Advanced Usage
Relationship Fields
Extract text from related models:
protected array $searchableColumns = [ 'search_text' => [ 'title', 'description', 'brand.name', // BelongsTo relationship 'categories.*.name', // BelongsToMany/HasMany ], ];
Translatable Fields
For models using Spatie's Translatable or JSON fields:
protected array $searchableColumns = [ 'search_text' => [ 'sources' => ['title', 'description', 'brand.name'], 'extract_translations' => 'all', // 'all', 'default', or false 'separator' => ' | ', ], ];
Options:
'all'- Combines all translations: "Phone هاتف"'default'- Uses only the default localefalse- Treats as regular string
Custom Cleaning Rules
Override cleaning rules per model:
protected array $searchableCleaningRules = [ 'strip_html' => true, 'decode_entities' => true, 'normalize_arabic' => true, 'remove_diacritics' => true, 'lowercase' => true, 'remove_symbols' => false, // Keep symbols 'collapse_whitespace' => true, ];
LIKE Fallback
For cases where FULLTEXT isn't suitable:
// Force LIKE search $products = Product::searchLike('title_search', 'partial')->get(); // Search multiple columns with LIKE $products = Product::searchLike(['title_search', 'description_search'], 'text')->get();
Artisan Commands
Rebuild Searchable Columns
Rebuild searchable columns for existing records:
# Rebuild all models php artisan searchable:rebuild # Specific model php artisan searchable:rebuild --model="App\Models\Product" # With progress bar php artisan searchable:rebuild --model="App\Models\Product" --verbose # Queue processing for large datasets php artisan searchable:rebuild --model="App\Models\Product" --queue # Specific queue php artisan searchable:rebuild --model="App\Models\Product" --queue --queue-name=search-rebuild
Configuration
The configuration file config/laravel-searchable.php provides extensive customization options:
return [ // Enable/disable FULLTEXT indexes 'enable_fulltext' => true, // Default text cleaning rules 'cleaning_rules' => [ 'strip_html' => true, 'decode_entities' => true, 'normalize_arabic' => true, 'remove_diacritics' => true, 'lowercase' => true, 'remove_symbols' => true, 'collapse_whitespace' => true, ], // Arabic normalization mappings 'arabic_normalizations' => [ 'أ' => 'ا', 'إ' => 'ا', 'آ' => 'ا', 'ة' => 'ه', 'ى' => 'ي', ], // Translation extraction mode 'extract_translations' => 'all', // 'all', 'default', or false // Queue settings 'queue' => 'default', 'chunk_size' => 500, // ... and more ];
Text Cleaning Pipeline
The package applies a comprehensive cleaning pipeline:
- Strip HTML - Removes all HTML tags
- Decode Entities - Converts HTML entities (
&→&) - Normalize Arabic - Standardizes Arabic character variants
- Remove Diacritics - Strips Arabic tashkeel marks
- Convert to Lowercase - Standardizes case for better matching
- Remove Symbols - Strips punctuation and special characters
- Collapse Whitespace - Normalizes spacing
Performance Optimization
MySQL Configuration
For better Arabic search, configure MySQL:
# /etc/mysql/my.cnf [mysqld] ft_min_word_length = 3
Then rebuild the FULLTEXT index:
OPTIMIZE TABLE products;
Queue Processing
For large datasets, use queue processing:
php artisan searchable:rebuild --model="App\Models\Product" --queue
Configure chunk size in config/laravel-searchable.php:
'chunk_size' => 500, // Adjust based on your needs
Eager Loading
The package automatically eager loads relationships used in searchable columns to prevent N+1 queries.
Testing
The package includes comprehensive unit and feature tests:
composer test
Test Requirements
Tests require a MySQL database. Configure in phpunit.xml:
<env name="DB_CONNECTION" value="mysql"/> <env name="DB_DATABASE" value="searchable_test"/> <env name="DB_USERNAME" value="root"/> <env name="DB_PASSWORD" value=""/>
Examples
E-commerce Product Search
class Product extends Model { use HasSearchableColumns, HasTranslations; public $translatable = ['title', 'description']; protected array $searchableColumns = [ 'search_text' => [ 'title', 'description', 'sku', 'barcode', 'brand.name', 'categories.*.name', ], ]; } // Search usage $results = Product::search('iphone 13 pro') ->published() ->inStock() ->get();
Multi-language Content
class Article extends Model { use HasSearchableColumns, HasTranslations; public $translatable = ['title', 'content']; protected array $searchableColumns = [ 'search_text' => [ 'sources' => ['title', 'content', 'author.name'], 'extract_translations' => 'all', // Index all languages ], ]; } // Searches in both English and Arabic $results = Article::search('tutorial')->get();
Selective Searchable Columns
class Post extends Model { use HasSearchableColumns; protected array $searchableColumns = [ 'title_search' => ['title'], 'content_search' => ['content', 'excerpt'], 'full_search' => ['title', 'content', 'excerpt', 'tags.*.name'], ]; } // Search only titles Post::searchIn('title_search', 'laravel')->get(); // Search content Post::searchIn('content_search', 'tutorial')->get(); // Full search Post::search('laravel tutorial')->get();
Troubleshooting
FULLTEXT Search Not Working
- Ensure you're using MySQL 5.6+ or MariaDB 10.0+
- Check that FULLTEXT indexes are created:
SHOW INDEX FROM products WHERE Key_name LIKE '%search%';
- Verify minimum word length matches your content:
SHOW VARIABLES LIKE 'ft_min_word_length';
Arabic Search Issues
- Set
ft_min_word_length = 3in MySQL configuration - Rebuild FULLTEXT indexes:
OPTIMIZE TABLE products; - Verify Arabic normalization is enabled in config
Performance Issues
- Use queue processing for bulk rebuilds
- Adjust chunk size in configuration
- Add database indexes on frequently filtered columns
- Consider using Laravel Scout for more advanced search needs
Security
If you discover any security-related issues, please open a private security advisory on the repository instead of using the public issue tracker.
License
The MIT License (MIT). Please see License File for more information.
Credits
- eslam-dev
- All Contributors
Support
- 🐛 Issues: GitHub Issues
- 📖 Documentation: Full Documentation