Installation
composer require bkstg/search-bundle
Add to config/app.php under providers:
Backstage\SearchBundle\SearchServiceProvider::class,
Publish the config file:
php artisan vendor:publish --provider="Backstage\SearchBundle\SearchServiceProvider" --tag=config
Basic Configuration
Edit config/search.php to define your search engines (e.g., Algolia, Elasticsearch, or custom adapters). Example:
'engines' => [
'default' => [
'driver' => 'algolia',
'api_key' => env('ALGOLIA_API_KEY'),
'app_id' => env('ALGOLIA_APP_ID'),
'index_name' => 'products',
],
],
First Search Query
Inject the Search facade or service into a controller:
use Backstage\SearchBundle\Facades\Search;
public function search(Request $request)
{
$results = Search::query($request->input('q'))
->engine('default')
->get();
return view('results', compact('results'));
}
Model Integration
Add a Searchable trait to your Eloquent model:
use Backstage\SearchBundle\Traits\Searchable;
class Product extends Model
{
use Searchable;
}
Define searchable fields in config/search.php under models.Product.
Chaining Methods Leverage fluent methods for complex queries:
$results = Search::query('laptop')
->engine('default')
->filter('price', '<', 1000)
->sort('created_at', 'desc')
->limit(10)
->get();
Dynamic Filtering Use request input for dynamic filters:
$filters = $request->only(['category', 'min_price', 'max_price']);
$results = Search::query($request->q)
->engine('default')
->filters($filters)
->get();
Pagination Integrate with Laravel's pagination:
$results = Search::query($request->q)
->engine('default')
->paginate(15);
Auto-Sync on Save
Enable automatic sync to search engine via config/search.php:
'sync' => [
'enabled' => true,
'events' => ['creating', 'updating', 'deleting'],
],
Manually trigger sync for a model:
$product->syncToSearch();
Bulk Sync Sync multiple models at once:
Search::sync([$product1, $product2, $product3]);
Backstage\SearchBundle\Contracts\SearchEngine:
class CustomEngine implements SearchEngine
{
public function search(string $query, array $filters = []): array
{
// Custom logic
}
public function sync(array $models): void
{
// Custom sync logic
}
}
Register in config/search.php:
'engines' => [
'custom' => [
'driver' => 'custom',
'class' => \App\Search\CustomEngine::class,
],
],
Search::query() in controllers/views for brevity.AppServiceProvider for dependency injection:
$this->app->bind('search', function ($app) {
return new \Backstage\SearchBundle\SearchService($app['config']['search']);
});
Query Logging
Enable debug mode in config/search.php:
'debug' => env('APP_DEBUG', false),
Logs will appear in storage/logs/laravel.log.
Adapter-Specific Errors Handle engine-specific exceptions:
try {
$results = Search::query($q)->engine('algolia')->get();
} catch (\Backstage\SearchBundle\Exceptions\AlgoliaException $e) {
Log::error('Algolia error: ' . $e->getMessage());
return back()->withError('Search service unavailable');
}
Environment Variables
Ensure .env variables are loaded (e.g., ALGOLIA_API_KEY). Use php artisan config:clear if changes aren’t reflected.
Index Name Conflicts Avoid duplicate index names across engines. Prefix with engine name if needed:
'index_name' => 'algolia_products',
Field Mapping
Explicitly define searchable fields in config/search.php to avoid runtime errors:
'models' => [
\App\Models\Product::class => [
'fields' => ['name', 'description', 'sku'],
'custom_mapping' => [
'price' => 'numeric',
'tags' => 'tags',
],
],
],
Batch Sync For large datasets, use chunking:
Product::chunk(100, function ($products) {
Search::sync($products);
});
Cache Results Cache frequent queries:
$results = Cache::remember("search_{$request->q}", now()->addHours(1), function () use ($request) {
return Search::query($request->q)->engine('default')->get();
});
Event Listeners Listen for sync events to add pre/post-processing:
Search::listen(function ($model) {
// Modify model before sync
$model->searchableData['boost'] = 2;
});
Custom Highlighting
Extend the SearchResult class to add custom highlighting:
class CustomSearchResult extends \Backstage\SearchBundle\SearchResult
{
public function highlight(string $field): string
{
// Custom logic
}
}
Bind in AppServiceProvider:
$this->app->bind(\Backstage\SearchBundle\SearchResult::class, \App\Search\CustomSearchResult::class);
Missing Sync Events
Ensure sync events are enabled in config/search.php to avoid stale data.
Case Sensitivity Algolia/Elasticsearch may treat queries case-sensitively by default. Configure in engine settings:
'options' => [
'ignore_case' => true,
],
Rate Limiting Respect API rate limits (e.g., Algolia’s 1000 requests/minute). Implement retries:
use Backstage\SearchBundle\Exceptions\RateLimitExceeded;
try {
$results = Search::query($q)->engine('algolia')->get();
} catch (RateLimitExceeded $e) {
sleep(60); // Wait and retry
$results = Search::query($q)->engine('algolia')->get();
}
How can I help you explore Laravel packages today?