Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Radish Bundle Laravel Package

jadu/radish-bundle

Symfony bundle that integrates the Radish Redis client, letting you configure connections and clients through the service container and inject them into your services, commands, and controllers.

View on GitHub
Deep Wiki
Context7

Getting Started

First, the reality check: jadu/radish-bundle is a Symfony bundle, not a Laravel package. There is no service provider, no facade, no config/radish.php waiting to be published. If you're on Laravel, you almost certainly want the underlying library — composer require jadu/radish — and about 40 lines of glue. The bundle is still worth reading as documentation: its Resources/config/services.xml (or .yaml) shows you exactly which classes need wiring, which is the fastest way to learn the API.

Radish is a Redis-backed inverted index: you push documents into an Index, commit() a batch, and run term queries that return scored IDs. It is not Elasticsearch or Meilisearch. No typo tolerance, no facets, no highlighting, no payloads — just IDs and scores. That constraint is the whole pitch: if Redis is already in your stack, you get relevance-ranked search with zero new infrastructure.

Minimal setup:

composer require jadu/radish
// config/radish.php
return [
    'client'  => env('RADISH_REDIS_CLIENT', 'default'), // key in database.redis
    'index'   => env('RADISH_INDEX', 'app_search'),
];
// app/Providers/RadishServiceProvider.php
use Radish\Index;
use Radish\Engine\RedisEngine;
use Radish\Engine\PredisEngine;

public function register(): void
{
    $this->mergeConfigFrom(config_path('radish.php'), 'radish');

    $this->app->singleton(Index::class, function ($app) {
        $conn   = $app['redis']->connection(config('radish.client'));
        $client = $conn->client(); // \Redis or Predis\Client

        $engine = $client instanceof \Redis
            ? new RedisEngine($client)
            : new PredisEngine($client);

        return new Index(config('radish.index'), $engine);
    });
}

First use case: take a single products table (a few thousand to low millions of rows) where you currently do where('name', 'like', "%{$q}%") across three columns, and replace it with a real weighted relevance query. Index name heavily and description lightly, then sort by score. That's a two-hour change that visibly improves search quality and takes pressure off MySQL full scans.

Implementation Patterns

Model events → queued job, never synchronous. Indexing on saved will make every write pay Redis round-trips and will corrupt your index when a transaction rolls back. Dispatch a job after commit:

class SyncProductToRadish implements ShouldQueue
{
    public function __construct(public int $productId) {}

    public function handle(Index $index): void
    {
        $product = Product::find($this->productId);

        if (! $product) {
            $index->removeDocument($this->productId); // soft-deleted / purged
            $index->commit();
            return;
        }

        $doc = new Document();
        $doc->setId($product->id);
        $doc->setName($product->name);
        $doc->setDescription($product->description);
        $doc->setCategoryId($product->category_id);   // denormalize filters
        $doc->setStatus($product->status);

        $index->addDocument($doc);
        $index->commit();
    }
}

Batch it (Bus::batch, or a per-chunk job) for imports — one commit() per document is the single biggest performance mistake people make here.

Wrap queries in a repository, and hydrate deliberately. Radish returns IDs plus scores; Eloquent is a separate step. Preserve rank order explicitly — whereIn() will not.

class ProductSearch
{
    public function __construct(private Index $index) {}

    public function search(string $term, int $page = 1, int $perPage = 20): LengthAwarePaginator
    {
        $query = new Query($this->index);
        $query->setFields(['name', 'description']);
        $query->andWhere('name', $term);
        $query->orWhere('description', $term);
        $query->setPage($page);
        $query->setPerPage($perPage);

        $result = $query->getResult();
        $ids    = array_map(fn ($doc) => $doc->getId(), $result->getResult());

        $models = Product::whereIn('id', $ids)->get()->keyBy('id');

        $ordered = collect($ids)
            ->map(fn ($id) => $models->get($id))
            ->filter()
            ->values();

        return new LengthAwarePaginator($ordered, $result->getNumFound(), $perPage, $page);
    }
}

Version the index, don't mutate it during reindex. Create products_v2, backfill in chunks, verify counts, then flip RADISH_INDEX and drop products_v1. Reindexing in place leaves you with a half-populated index and no rollback.

Denormalize anything you filter on. Radish has no joins. Store category_id, status, tenant_id as documents fields and add them as query terms. For multi-tenancy, prefix the index name per tenant (tenant_42_products) rather than filtering — it keeps rebuilds isolated and lets you drop a tenant's index in one call.

Test against a dedicated Redis database. Point RADISH_REDIS_CLIENT at a testing connection using database => 15, call FLUSHDB in setUp(), and assert on IDs, not on scores (scores are implementation detail and will churn when you tune weights).

Gotchas and Tips

  • Zero stars, zero dependents means you are the QA team. Pin an exact version, read the source before relying on any behavior, and expect to patch. Budget a day for "figure out what this actually does" before promising it to a stakeholder.
  • commit() is not optional and not free. Documents sit in a buffer until commit. Forget it and your index is silently empty; call it per-document and you'll do thousands of round-trips. Batch, then commit.
  • The index is not your source of truth. Redis persistence is best-effort and an index can be lost or corrupted. Always be able to rebuild from SQL, and make deleted-document cleanup part of your normal write path — otherwise deleted rows resurface in results and your whereIn() hydration quietly drops them, giving you "20 results per page" that renders 14.
  • English-centric tokenization. If you have multilingual content, accented characters, or SKUs like AB-1234/X, verify tokenization behavior early with a fixture set. Pre-normalize on write (lowercase, strip accents, keep an unmodified copy for display) rather than fighting the analyzer.
  • Field weights are your only relevance knob. No BM25, no boost-at-query-time magic. Tune by building a small fixture of ~20 representative queries with expected top-3 results and running it as a test after every weight change.
  • Watch Redis memory. Inverted indexes are term-heavy; redis-cli --bigkeys and INFO memory after a full import. There is no TTL on index keys — abandoned indexes linger forever.
  • Debugging: redis-cli MONITOR on a dev instance shows the exact commands Radish issues, which answers most "why is nothing matching?" questions. Use KEYS <prefix>* and ZCARD on dev only — never in production.
  • Don't blindly copy the bundle's Symfony config. Translate service definitions into your provider manually; the bundle's defaults assume Symfony's parameter resolution and event dispatcher, and you'll inherit dead configuration you don't understand.
  • Know when to stop. No facets, no highlighting, no typo tolerance, no aggregations. If a stakeholder asks for any of those, the correct answer is Meilisearch via Laravel Scout, not a Radish workaround. Radish's niche is narrow and legitimate: small-to-medium corpora, Redis already present, simple weighted term matching, low operational appetite. If that's not your situation, don't force it.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core