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

Manticoresearch Php Laravel Package

manticoresoftware/manticoresearch-php

Official PHP client for Manticore Search with a fluent query builder, bulk indexing, and support for HTTP and SQL-style APIs. Easily integrates with Laravel or plain PHP apps to add fast full-text search.

Deep Wiki
Context7

Getting Started

Install and connect.

composer require manticoresoftware/manticoresearch-php

You need a running Manticore instance (dev: docker run -p 9306:9306 -p 9308:9308 manticoresearch/manticore). The package ships no Laravel service provider, so register the client yourself:

// config/manticore.php
return [
    'host'      => env('MANTICORE_HOST', '127.0.0.1'),
    'port'      => env('MANTICORE_PORT', 9308),     // 9308 = HTTP, 9306 = SQL
    'transport' => env('MANTICORE_TRANSPORT', 'http'),
    'username'  => env('MANTICORE_USERNAME'),
    'password'  => env('MANTICORE_PASSWORD'),
];
// AppServiceProvider::register()
$this->app->singleton(\Manticoresearch\Client::class, fn ($app) =>
    new \Manticoresearch\Client(config('manticore'))
);

Where to look first: the README examples for Client, Index, and Search. Those three classes are 90% of the day-to-day surface. Ignore the Query\* classes until you need boolean/range logic — start with plain query strings.

First use case — index an Eloquent model and search it. Create a real-time index once (RT indexes are required for inserts; plain indexes are built offline by the indexer CLI):

$client->index('articles')->create([
    'title'        => ['type' => 'text'],
    'body'         => ['type' => 'text'],
    'author_id'    => ['type' => 'integer'],
    'category'     => ['type' => 'string'],
    'published_at' => ['type' => 'timestamp'],
]);
$index = $client->index('articles');
$index->addDocument(['id' => $post->id, 'title' => $post->title, 'body' => strip_tags($post->body), /* … */]);

$results = $index->search('@title laravel')
    ->limit(10)
    ->get();

foreach ($results as $doc) { /* $doc['title'], $doc['_id'] */ }
$results->getTotal(); // total matched

Implementation Patterns

Wrap it, don’t inject it. Manticoresearch\Client has a non-DI-friendly constructor and swaps transports between versions. Bind it as a singleton, then expose a small domain service (Search\ArticleIndex) that owns index names, field mapping, and result shaping. This is the seam you test against and the place you add logging.

Eloquent observers, not model events everywhere. Register saved/deleted listeners that push to a queued job (IndexArticleJob). Use replaceDocuments() for upserts (idempotent) rather than addDocument so re-runs are safe:

public function handle(ManticoreService $search): void
{
    $docs = Post::query()->whereIn('id', $this->ids)
        ->get()->map(fn ($p) => $p->toSearchableArray())->all();

    $result = $search->index('articles')->replaceDocuments($docs);
    if (! empty($result['errors'])) {
        throw new \RuntimeException('Manticore rejected '.count($result['items']).' docs');
    }
}

Bulk indexing via queues. addDocuments()/replaceDocuments() are the only sane way to load existing data — chunk at 1–5k documents and set a large max_allowed_packet on the server. Do it after the DB transaction commits (or drive it from a created/updated outbox table); Manticore has no cross-store transaction.

Laravel Scout custom engine. Manticoresearch\Client is not a Scout driver. Wire one up if you want the Scout ergonomics:

// AppServiceProvider::boot()
Scout::extend('manticore', fn ($app) => new ManticoreEngine(
    $app->make(\Manticoresearch\Client::class)
));

Implement update, delete, search, and paginate. delete should be addDocuments([['id' => $id, 'deleted' => 1]]) plus a deleted = 0 filter in search — simpler than tracking deletion state elsewhere, and it survives partial failures.

Query building. Start with search('@title laravel') (Manticore’s extended query syntax), then layer the fluent builder:

$index->search('')                                  // match-all
    ->equals('category', 'php')
    ->range('published_at', ['gte' => $since->timestamp])
    ->sort('published_at', 'desc')
    ->facet(['category'], ['limit' => 20])
    ->highlight(['title', 'body'])
    ->limit(20)->offset($offset)
    ->get();

For complex boolean logic, switch to the Query\* nodes (Match, BoolQuery, QueryString, In, Range) and pass them to ->query(...). These are plain value objects — unit testable, and the right building block for a query-object layer in your app.

Multi-tenant / per-environment index names. Manticore has no schemas. Namespace indexes (articles_tenant_42, articles_staging) via a config helper and centralize it in your service. Reindexing then becomes “create new, backfill, swap alias or update the pointer in config” — Manticore supports aliases in recent versions, but for most apps a config constant is simpler.

Gotchas and Tips

  • Two very different transports. transport: 'sql' uses ext-mysqli (port 9306) and a more limited query surface; transport: 'http' (port 9308) uses an elasticsearch-php-derived JSON client and supports more features. Don’t assume a method exists on both. Check which transport your install uses — most Docker PHP images lack mysqli, which is a classic silent failure on first deploy.
  • HTTP transport pulls in elasticsearch-php. If you already use the official Elasticsearch client (or AWS/OpenSearch variants), expect version conflicts. Run composer why-not manticoresoftware/manticoresearch-php before you pin. This is the most common install-time blocker.
  • RT indexes only for writes. addDocument on a plain index is a no-op or error. If you came from “just point at a DB source”, you need Index::create() + a backfill, not indexer config.
  • Bulk results lie by omission. replaceDocuments() returns a response with an errors boolean and per-item statuses. If you don’t inspect it, rejected docs disappear. Always assert empty($result['errors']) inside the job and alert.
  • max_matches defaults to 1000. Deep pagination past ~1000 results requires ->maxMatches() and a server-side max_matches bump. For UI pagination, use search_after-style attribute filters or cap the exposed page count.
  • Sorting and filtering only hit attributes. Fulltext fields (type: text) can’t be sorted or filtered on. If you need where published_at > $x or order by title, declare them as attributes (timestamp, string, integer) at index creation — schema changes require a rebuild.
  • Highlights come back under _highlight (or highlight, depending on version/transport) as arrays. Strip HTML from indexed content first, or your snippets will be garbage. Confirm the key against your installed major version — it changed between the SQL and HTTP clients.
  • No migrations for Manticore. Index::create/drop are imperative. Keep index definitions in version-controlled PHP (an Artisan search:rebuild command), not in a one-off Tinker session. Treat schema as code from day one.
  • Long-running queue workers can lose the connection. The mysqli transport will silently drop after idle periods. Either cap --timeout on workers or add a ping()/reconnect at job start. The HTTP transport is more forgiving but still times out.
  • Laravel’s HTTP client tooling won’t see requests. Telescope, Debugbar, and Http::fake() cover Illuminate\Support\Facades\Http, not this package. Add an explicit logging decorator around your service if you need request visibility; for tests, hit a real Manticore container (manticoresearch/manticore) instead of mocking — query semantics are too easy to mock incorrectly.
  • Extension points. Subclass Manticoresearch\Query\AbstractQuery for custom query nodes (useful for reusable “published + tenant + permission” filters), a custom Scout engine for Laravel integration, and the Index wrapper for domain-specific helpers (suggest(), similar(), moreLikeThis()). Stored procedures (CALL SUGGEST, CALL KEYWORDS) are not first-class in the fluent API — route them through a raw SQL connection if you need them.
  • Pin the major version and read the changelog. The v2 line unified the HTTP and SQL clients and renamed several Index/Search methods versus v1. Code found in older blog posts frequently won’t compile against current releases; compare method signatures against the version in composer.lock, not against search results.
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.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core