eslam-dev/laravel-searchable

Laravel Searchable — dedicated searchable columns for Eloquent with cleaning, FULLTEXT search, and rebuild tooling

Maintainers

Package info

github.com/eslam-dev/laravel-searchable

pkg:composer/eslam-dev/laravel-searchable

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

1.0.0 2026-05-02 14:33 UTC

This package is auto-updated.

Last update: 2026-05-02 14:36:32 UTC


README

A Laravel package for dedicated searchable text columns on Eloquent models: automatic fill on save, text cleaning, and MySQL FULLTEXT search.

Latest Version License: MIT

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 locale
  • false - 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:

  1. Strip HTML - Removes all HTML tags
  2. Decode Entities - Converts HTML entities (&&)
  3. Normalize Arabic - Standardizes Arabic character variants
  4. Remove Diacritics - Strips Arabic tashkeel marks
  5. Convert to Lowercase - Standardizes case for better matching
  6. Remove Symbols - Strips punctuation and special characters
  7. 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

  1. Ensure you're using MySQL 5.6+ or MariaDB 10.0+
  2. Check that FULLTEXT indexes are created:
    SHOW INDEX FROM products WHERE Key_name LIKE '%search%';
  3. Verify minimum word length matches your content:
    SHOW VARIABLES LIKE 'ft_min_word_length';

Arabic Search Issues

  1. Set ft_min_word_length = 3 in MySQL configuration
  2. Rebuild FULLTEXT indexes: OPTIMIZE TABLE products;
  3. Verify Arabic normalization is enabled in config

Performance Issues

  1. Use queue processing for bulk rebuilds
  2. Adjust chunk size in configuration
  3. Add database indexes on frequently filtered columns
  4. 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

Support