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

Filagora Search Laravel Package

filagora/filagora-search

Filagora Search is a Laravel package that brings flexible, configurable search to your Filagora application. It lets you index and query models and content through a simple API, with sensible defaults and easy customization for fast, relevant results without complex setup. Designed for developers.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a v0.1.0, so treat the public API as unstable — pin the version and expect to re-read the contracts on minor bumps.

  1. Install the package and publish its config (php artisan vendor:publish for the package's config tag). Look at the meilisearch block first: host, key, prefix, timeout, max_total_hits, plus the health.* block. Get host/key into .env, and set a prefix from day one (see Gotchas).
  2. Read Filagora\Search\Search first — it is the whole story in one class: it asks the configured engine, logs failures, trips a circuit breaker, and delegates to the index's database fallback. Then read the SearchEngine and SearchIndex contracts; everything else (MeilisearchEngine, DatabaseEngine, ArrayEngine) is an implementation of those two.
  3. Register what is searchable in SearchIndexRegistry. Your SearchIndex implementation answers three questions: which models belong to this index, how a model becomes a Document, and how to answer a SearchQuery from the database (that last one is what powers DatabaseEngine and the fallback).
  4. Bring the engine up: php artisan filagora:search:sync-settings, then php artisan filagora:search:reindex. Confirm with php artisan filagora:search:check-health (non-zero exit on a problem) and php artisan filagora:doctor.
  5. First real use case: search and hydrate. Search gives you a SearchResult containing ids, never models (by design). So the day-one pattern is:
$result = $search->search($query);          // SearchQuery with a filter tree
$models = Post::whereIn('id', $result->ids())->get()
    ->sortBy(fn ($m) => array_search($m->id, $result->ids()))->values();

Once that loop works, writes are already handled for you: saving, deleting or restoring a model in a registered index queues SyncSearchDocument automatically.

Implementation Patterns

Writes are implicit; treat the index as eventually consistent. You do not call the indexer on save. Every save/delete/restore of a model in a registered index queues SyncSearchDocument (on the sync connection by default, after the transaction commits unless after_commit is off). The day-to-day consequence: correctness of the index is an operational concern, not a code concern. Reach for Indexer::sync/forget only for bulk or out-of-band mutations (raw SQL updates, insert() calls, mass deletes that bypass events).

Compose queries as a portable filter tree, not engine syntax. Equals, In, Range, All, Any, Not compile to a Meilisearch filter via FilterCompiler. Build helper methods on your query objects (PostQuery::published() returning an All of Equals nodes) so that swapping engines later doesn't mean rewriting call sites — that is the entire point of the abstraction.

Use GroupedSearchIndex for expensive fields. Named field groups let you Indexer::backfill a single group and use SearchEngine::update() to merge fields into existing documents. This is the right shape for denormalised counters, computed relevance hints, or anything you want to refresh on a schedule without a full reindex. Cheap fields go in the default group and ride along with normal saves.

Deploys: settings first, then documents. sync-settings waits for and verifies its Meilisearch task, so it is safe (but slow) to run in a deploy hook; run it before any code that queries new filterable attributes goes live. Follow with reindex — it resumes from a start key, so a killed deploy can be continued rather than restarted. Keep pause non-zero on large rebuilds so you don't starve the task queue for live traffic.

Circuit breaker is your degradation story — write for it. When the engine fails, Search logs, opens the breaker, and serves from the index's database fallback until the cooldown ends. That means your fallback query is load-bearing production code, not just test scaffolding. Keep it reasonably cheap and give it sensible ordering, because users will see it during an outage.

Testing: switch to ArrayEngine and never touch Meilisearch in unit tests. Bind the array engine in TestCase and your search assertions become ordinary in-memory ones. If you write your own adapter, run Filagora\Search\Testing\EngineConformance — the same suite the shipped engines pass — so you find drift before production does. Note that parity with ArrayEngine is the intended contract; when in doubt about expected semantics, the array engine is the reference.

Queue it in production. Flipping the package's queue option moves SyncSearchDocument onto a worker, which turns a Meilisearch HTTP round-trip from a save-blocking call into background work. Pair it with after_commit left on so jobs don't race an uncommitted row.

Gotchas and Tips

  • The default sync queue connection is the biggest footgun. Out of the box, every save performs an inline HTTP call to Meilisearch. With timeout too low, saves silently stop updating the index; with it too high, request latency tracks Meilisearch's. Enable the queue option before you ship.
  • Failures never break the save — and that is a feature that hides bugs. A failing engine is logged and swallowed by design. Nobody gets a 500, and nobody notices the index is a week stale. Do three things: alert on the log channel, run filagora:search:check-health from the scheduler or uptime monitor (it exits non-zero), and diff SearchIndex::expectedCount() against actual document counts on a cron as a drift canary.
  • DatabaseEngine does nothing on model events. It answers from each index's own tables and deliberately no-ops the write path. So "database engine" mode is not a caching layer and not a mirror — it is a read-through. Don't assume documents exist there.
  • New filterable fields need a settings sync, not a reindex — but do both if you're impatient. FilterCompiler requires fields to be filterable on the index; a filter on anything else is a hard Meilisearch error and, because of the circuit breaker, will look like a mysterious fallback to DB results rather than a validation failure. Check recent failed tasks in the engine stats before you go hunting through your query builder.
  • The filter tree is values-only; you cannot inject raw syntax. Values are quoted and escaped, and always-true / always-false conditions are folded away during compilation. If you find yourself wanting a raw Meilisearch string, extend the filter tree instead — otherwise you'll special-case yourself out of engine portability.
  • max_total_hits makes totals approximate. Past the cap you get a capped-total flag and a count that stops growing. Don't build "page 500 of 12,000 results" UIs off it, and treat end-of-results as "we stopped counting", not "there is nothing more".
  • Every-word matching is AND-shaped. All query terms must match; there is no OR across query text. Any in the filter tree composes filters, not search terms — conflating the two is a common early mistake when porting from a LIKE %term% implementation.
  • update() merges, it never prunes. Removing a field from a Document or renaming it leaves the old value in place until a sync/reindex. Field-group backfills have the same property. Budget a reindex for schema-ish changes, and remember reindex is resumable — start key plus batch size are your knobs when a rebuild gets interrupted.
  • Always set the index prefix, and set it per environment. It is the only thing between staging and production sharing a Meilisearch instance and silently overwriting each other's documents.
  • Circuit breaker cooldown cuts both ways. It protects you during an outage, but a transient blip keeps you on degraded DB results for the whole cooldown window, with no typo tolerance, no relevance ranking, and different result counts. Exercise the fallback path in a test so you know what degraded looks like before you meet it at 3am.
  • Extension points worth knowing: SearchEngines registers additional engines by name (useful for an in-house adapter or a per-tenant engine), the SearchEngine contract is small enough to implement against the conformance suite, and WaitsForTasks is the mixin for adapters that need to block on an async engine's task queue. If you're integrating a third engine, start from the conformance suite rather than from MeilisearchEngine — it encodes the semantics you actually have to match.
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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource