Installation
composer require torann/laravel-cloudsearch
Publish the config file:
php artisan vendor:publish --provider="Torann\CloudSearch\CloudSearchServiceProvider"
Update .env with your Amazon CloudSearch domain endpoint and API key (if applicable):
CLOUDSEARCH_DOMAIN=your-domain.search.amazonaws.com
CLOUDSEARCH_API_KEY=your-api-key
Configure a Model
Add the Searchable trait to your Eloquent model:
use Torann\CloudSearch\Searchable;
class Post extends Model
{
use Searchable;
protected $searchable = [
'title', // Fields to index
'body',
'author_name' // Computed field (if needed)
];
First Indexing Trigger a manual index update (e.g., in a seeder or command):
Post::searchable()->index();
Or use a model observer to auto-index on saved/created:
Post::observe(CloudSearchObserver::class);
Basic Search
$results = Post::search('laravel')->get();
// Or with pagination:
$results = Post::search('laravel')->paginate(10);
Manual Indexing Useful for one-off updates (e.g., after bulk imports):
Post::searchable()->index(); // Full reindex
Post::searchable()->update(); // Incremental update
Automatic Indexing Leverage observers for real-time updates:
// app/Observers/PostObserver.php
class PostObserver
{
public function saved(Post $post)
{
$post->searchable()->update();
}
}
Scheduled Reindexing Use Laravel’s scheduler for periodic full reindexes:
// app/Console/Kernel.php
$schedule->command('cloudsearch:reindex')->daily();
Query Building
Use CloudSearch’s query syntax (supports AND, OR, NOT, facets, etc.):
$results = Post::search('laravel OR php', ['facet' => 'tags'])->get();
Highlighting Results
Enable highlighting in config (config/cloudsearch.php):
'highlight' => [
'enabled' => true,
'fields' => ['title', 'body'],
],
Access highlights in results:
foreach ($results as $post) {
dd($post->search_highlights);
}
Pagination & Sorting
$results = Post::search('laravel')
->orderBy('published_at', 'desc')
->paginate(15);
Soft Deletes
Extend the Searchable trait to handle soft deletes:
public function searchable()
{
return $this->searchable()->whereNull('deleted_at');
}
Custom Fields
Use searchable() method to define dynamic fields:
protected $searchable = function () {
return [
'title' => $this->title,
'full_text' => $this->title . ' ' . $this->body,
'category' => $this->category->name,
];
};
Multi-Domain Search For apps with multiple CloudSearch domains, bind them dynamically:
CloudSearch::setDomain('domain-2');
Post::search('query')->get();
API Key Restrictions
CLOUDSEARCH_API_KEY in .env and ensure IAM permissions are set.Field Mapping Mismatches
$searchable but forget to update the domain’s schema, searches will fail.cloudsearch:schema Artisan command to generate a schema file, then apply it via the AWS Console or SDK.Rate Limiting
index() in batches or schedule updates during off-peak hours.Observer Conflicts
saved()/updated() observers, ensure searchable()->update() isn’t called recursively (e.g., if the observer triggers another save).if (!$this->wasRecentlySaved) {
$this->searchable()->update();
}
Enable Logging
Add to config/cloudsearch.php:
'debug' => env('APP_DEBUG', false),
Check logs for failed requests (e.g., storage/logs/laravel.log).
Raw API Responses Inspect raw CloudSearch responses for errors:
$response = Post::search('test')->getSearchResponse();
dd($response->toArray());
Schema Validation
Use the cloudsearch:schema:dump command to verify your schema matches the domain:
php artisan cloudsearch:schema:dump
Custom Search Handlers
Override the search() method to add pre/post-processing:
public function scopeSearch($query, $searchTerm)
{
$results = parent::search($searchTerm)->get();
return $results->filter(fn ($item) => $item->isPublished());
}
Webhook-Based Indexing For high-frequency updates, use CloudSearch’s webhooks instead of Laravel observers.
Hybrid Search Combine CloudSearch with Laravel Scout for fallback logic:
if (CloudSearch::isHealthy()) {
return Post::search($query)->get();
}
return Post::scoutSearch($query)->get();
How can I help you explore Laravel packages today?