cmsig/seal-redisearch-adapter
RediSearch adapter for the SEAL search engine. Index and query documents in a Redis Stack instance using RediSearch + RedisJSON. Supports ext-redis/ext-json and DSN-based configuration; note: no GeoBoundingBox or HIGHLIGHT support.
Install Dependencies:
composer require cmsig/seal cmsig/seal-redisearch-adapter
Ensure ext-redis and ext-json PHP extensions are enabled.
Configure Redis:
redis-cli MODULE LIST
redisearch and redisjson.Basic Setup in Laravel:
config/database.php:
'connections' => [
'redis' => [
'driver' => 'redis',
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', 6379),
],
],
app/Search/Schemas/ProductSchema.php):
return [
'fields' => [
'id' => ['type' => 'TAG'],
'name' => ['type' => 'TEXT', 'SORTABLE' => true],
'price' => ['type' => 'NUMERIC'],
'category' => ['type' => 'TAG'],
],
];
Initialize Engine:
AppServiceProvider):
use CmsIg\Seal\Engine;
use CmsIg\Seal\Adapter\RediSearch\RediSearchAdapter;
use Illuminate\Support\Facades\Redis;
public function register()
{
$schema = include __DIR__.'/../Search/Schemas/ProductSchema.php';
$this->app->singleton('search.engine', function () {
return new Engine(new RediSearchAdapter(Redis::connection()), $schema);
});
}
First Query:
$engine = app('search.engine');
$results = $engine->search('query', ['limit' => 10]);
Bulk Indexing: Use Laravel’s queues to offload indexing for large datasets:
use Illuminate\Support\Facades\Bus;
Bus::dispatch(new IndexProductsJob($products));
// IndexProductsJob.php
public function handle()
{
$engine = app('search.engine');
foreach ($this->products as $product) {
$engine->index($product->id, $product->toSearchArray());
}
}
Model Observers: Auto-index models on update:
// ProductObserver.php
public function saved(Product $product)
{
$engine = app('search.engine');
$engine->index($product->id, $product->toSearchArray());
}
Basic Search:
$results = $engine->search('laptop', [
'limit' => 10,
'offset' => 0,
'sortBy' => 'price',
]);
Filtered Search:
Use RediSearch’s FILTER syntax via SEAL’s query builder:
$results = $engine->search('laptop', [
'filter' => 'category:{electronics}',
]);
Aggregations:
$aggregations = $engine->aggregate('category', [
'groupBy' => 'category',
'reduce' => 'COUNT',
]);
Dynamic Schemas: Load schemas dynamically based on environment:
$schema = config('search.schemas.'.config('app.env'));
$engine = new Engine(new RediSearchAdapter(Redis::connection()), $schema);
Partial Updates: Update specific fields without reindexing:
$engine->update($productId, ['price' => 999.99]);
Service Container Binding: Bind the engine to an interface for easier mocking in tests:
$this->app->bind(
SearchEngineInterface::class,
function () {
return new Engine(new RediSearchAdapter(Redis::connection()), $schema);
}
);
API Resources: Transform search results into API responses:
// ProductSearchResource.php
public function toArray($request, Product $product)
{
return [
'id' => $product->id,
'name' => $product->name,
'price' => $product->price,
];
}
Blade Directives: Create a Blade directive for search forms:
Blade::directive('searchForm', function ($expression) {
return "<?php echo $expression; ?>";
});
@searchForm('<form action="/search" method="GET">
<input type="text" name="q" value="{{ request('q') }}">
<button type="submit">Search</button>
</form>')
Request Handling:
// routes/web.php
Route::get('/search', function () {
$query = request('q');
$engine = app('search.engine');
$results = $engine->search($query, [
'limit' => 20,
'withPayload' => true,
]);
return view('search.results', compact('results'));
});
Caching Results: Cache frequent queries to reduce Redis load:
$cacheKey = "search:{$query}:".implode(',', $filters);
return Cache::remember($cacheKey, now()->addMinutes(5), function () use ($engine, $query, $filters) {
return $engine->search($query, $filters);
});
Event-Driven Indexing: Use Laravel events to keep search index in sync:
// ProductCreated.php
public function handle()
{
$engine = app('search.engine');
$engine->index($this->product->id, $this->product->toSearchArray());
}
Periodic Reindexing: Schedule a Laravel command to reindex stale data:
php artisan schedule:run
// ReindexCommand.php
public function handle()
{
$engine = app('search.engine');
$engine->reindex(Product::all()->pluck('id')->toArray());
}
Unit Tests: Mock the Redis connection for isolated tests:
$mockRedis = Mockery::mock(Redis::class);
$mockRedis->shouldReceive('search')->andReturn(['result']);
$engine = new Engine(new RediSearchAdapter($mockRedis), $schema);
Feature Tests: Test search endpoints with real data:
public function test_search_endpoint()
{
$response = $this->get('/search?q=laptop');
$response->assertStatus(200);
$response->assertSee('Laptop Pro');
}
Redis Connection Pooling:
$engine = new Engine(new RediSearchAdapter(Redis::connection('redis-cache')), $schema);
Schema Validation:
$validTypes = ['TEXT', 'TAG', 'NUMERIC', 'GEO', 'HASH'];
foreach ($schema['fields'] as $field) {
assert(in_array($field['type'], $validTypes), "Invalid field type: {$field['type']}");
}
Error Handling:
try {
$results = $engine->search($query);
} catch (RedisException $e) {
Log::error("Search failed: {$e->getMessage()}");
return back()->withError('Search service unavailable');
}
Performance Tuning:
INDEX definitions:
$engine->createIndex([
'index' => 'products',
'fields' => [
'name' => ['TYPE' => 'TEXT', 'WEIGHT' => 10],
'description' => ['TYPE' => 'TEXT', 'WEIGHT' => 5],
],
]);
Laravel Scout Alternative:
How can I help you explore Laravel packages today?