PHPackages                             renatomaldonado/manticore-laravel-search - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Search &amp; Filtering](/categories/search)
4. /
5. renatomaldonado/manticore-laravel-search

ActiveLibrary[Search &amp; Filtering](/categories/search)

renatomaldonado/manticore-laravel-search
========================================

Laravel driver for Manticore Search using the official PHP client

v1.3.0(1mo ago)1601PHPPHP ^8.1CI failing

Since May 12Pushed 1w ago1 watchersCompare

[ Source](https://github.com/Renato27/laravel-manticore-search)[ Packagist](https://packagist.org/packages/renatomaldonado/manticore-laravel-search)[ RSS](/packages/renatomaldonado-manticore-laravel-search/feed)WikiDiscussions master Synced 3w ago

READMEChangelog (10)Dependencies (9)Versions (18)Used By (0)

Laravel Manticore Search
========================

[](#laravel-manticore-search)

A driver for integrating **Manticore Search** with Laravel, featuring a fluent query builder, Eloquent model hydration, robust pagination, and support for multiple named connections.

Related packages
----------------

[](#related-packages)

If you need full Eloquent support (migrations, `create`, `update`, `delete`, relations, scopes, casts) natively on Manticore, see [laravel-manticore-eloquent](https://github.com/Renato27/laravel-manticore-eloquent)— a Laravel database driver over the Manticore MySQL protocol.

This package (`manticore-laravel-search`) focuses on the HTTP/JSON search API, fluent query builder, consolidation, and facets.

What this library does
----------------------

[](#what-this-library-does)

- Exposes a fluent query builder through `Model::manticore()`.
- Supports full-text search (`match`) plus structured filters (`where`, `whereIn`, `whereBetween`, `whereNull`, `whereRaw`, etc.).
- Supports SQL mode (`select`, `groupBy`, `having`, `orderBy`, `toSql`, `rawQuery`).
- Hydrates results into your model instances, including cast resolution and custom attribute mapping.
- Preserves request filters during pagination, including large payloads via a cached context token.
- Provides an optional `Manticore` facade for direct manager/client/table access.

Requirements
------------

[](#requirements)

DependencyVersionPHP`^8.1`Laravel`^10.0 | ^11.0 | ^12.0``manticoresoftware/manticoresearch-php``^4.0`Installation
------------

[](#installation)

```
composer require renatomaldonado/manticore-laravel-search
```

Configuration
-------------

[](#configuration)

Publish the config file:

```
php artisan vendor:publish --provider="ManticoreLaravel\ManticoreServiceProvider"
```

### Named connections (recommended)

[](#named-connections-recommended)

```
// config/manticore.php
return [
    'default' => env('MANTICORE_CONNECTION', 'default'),

    'connections' => [
        'default' => [
            'host'          => env('MANTICORE_HOST', '127.0.0.1'),
            'port'          => env('MANTICORE_PORT', 9308),
            'username'      => env('MANTICORE_USERNAME', null),
            'password'      => env('MANTICORE_PASSWORD', null),
            'transport'     => env('MANTICORE_TRANSPORT', 'Http'),
            'timeout'       => env('MANTICORE_TIMEOUT', 5),
            'persistent'    => env('MANTICORE_PERSISTENT', false),
            'max_matches'   => env('MANTICORE_MAX_MATCHES', 1000),
            'limit_results' => env('MANTICORE_LIMIT_RESULTS', 0), // 0 = disabled
        ],

        'analytics' => [
            'host'        => env('MANTICORE_ANALYTICS_HOST', '10.0.0.2'),
            'port'        => env('MANTICORE_ANALYTICS_PORT', 9308),
            'max_matches' => 2000,
        ],
    ],

    // Used internally when fetching all rows for consolidation.
    'unlimited_max_matches' => env('MANTICORE_UNLIMITED_MAX_MATCHES', 1000000),

    'pagination' => [
        'context_key'      => env('MANTICORE_PAGINATION_CONTEXT_KEY',     '_mctx'),
        'max_query_length' => env('MANTICORE_PAGINATION_MAX_QUERY_LENGTH', 1500),
        'context_ttl'      => env('MANTICORE_PAGINATION_CONTEXT_TTL',      900),
        'cache_prefix'     => env('MANTICORE_PAGINATION_CACHE_PREFIX',     'manticore:pagination:'),
        'total_cache_ttl'  => env('MANTICORE_PAGINATION_TOTAL_CACHE_TTL',  300),
    ],
];
```

### Legacy flat config (backward compatibility)

[](#legacy-flat-config-backward-compatibility)

If `connections` is empty or missing, the resolver falls back to the flat top-level keys (`manticore.host`, `manticore.port`, etc.) that predate multi-connection support.

Model setup
-----------

[](#model-setup)

Add the `HasManticoreSearch` trait and implement `searchableAs()`.

```
use Illuminate\Database\Eloquent\Model;
use ManticoreLaravel\Traits\HasManticoreSearch;

class Company extends Model
{
    use HasManticoreSearch;

    protected $fillable = ['EntityID', 'EntityName', 'CountryISO'];

    public function searchableAs(): string
    {
        return 'companies_index';
    }
}
```

`searchableAs()` may return a `string` or an `array` of index names for multi-index queries.

Quick example
-------------

[](#quick-example)

```
$results = Company::manticore()
    ->match('startup')
    ->where('countryiso', 'BR')
    ->orderBy('entityid', 'desc')
    ->limit(10)
    ->get();
```

Builder API
-----------

[](#builder-api)

### Filtering

[](#filtering)

MethodDescription`match(string $keywords, ?string $field, string $boolean)`Full-text search. `$field` defaults to `*`.`where(string $field, mixed $op, mixed $value)`Equality or comparison. Supports `=`, `!=`, ``, `>`, `>=`, `with('owner')

// Column-constrained
->with('owner:id,name')

// Closure
->with(['tags' => fn($q) => $q->where('active', true)])
```

### Connection and index overrides

[](#connection-and-index-overrides)

MethodDescription`usingConnection(string $name)`Use a named connection from config. Flushes the local client cache.`useIndex(string|array $indexes)`Override `searchableAs()`. Supports multi-index.`rawQuery(string $sql, bool $rawMode)`Execute manual SQL, bypassing the builder.### Utility chainables

[](#utility-chainables)

MethodDescription`when(mixed $condition, callable $cb, ?callable $default)`Apply a callback only when `$condition` is truthy.`tap(callable $callback)`Inspect/modify the builder mid-chain without breaking it.`dump()`Dump the compiled SQL and options, then continue the chain.`dd()`Dump the compiled SQL and options, then exit.### Execution

[](#execution)

MethodReturns`get()``Illuminate\Database\Eloquent\Collection``first()`Model or `null``last()`Model or `null``count()``int``pluck(string $field)``Collection``toArray()``array``toJson(int $options)``string``toSql()``string` — compiled SQL, no network call`chunk(int $size, callable $callback)`Iterates results in pages; returns `false` if callback returns `false`.`paginate(int $perPage, string $pageName, ?int $page)``LengthAwarePaginator``getFacets()``array` (not available in `rawQuery` mode)Consolidation (group-merge)
---------------------------

[](#consolidation-group-merge)

Consolidation fetches multiple documents and merges all rows that share the same `$groupField` value into a single model, storing the originals under a `$historyAttribute`.

```
// Single consolidated result
$company = Company::manticore()
    ->where('entity_id', 42)
    ->consolidateBy('entity_id');

// All consolidated results
$companies = Company::manticore()
    ->match('fintech')
    ->consolidateAllBy('entity_id');

// Paginated consolidated results
$paginator = Company::manticore()
    ->match('fintech')
    ->paginateConsolidatedBy('entity_id', perPage: 20);
```

`getConsolidatedBy()` is an alias for `consolidateAllBy()` kept for backward compatibility.

Pagination and filter preservation
----------------------------------

[](#pagination-and-filter-preservation)

`paginate()` and `paginateConsolidatedBy()` automatically carry the current request's filters into the paginator links — including GET params, POST/JSON bodies, and custom page key names.

When the query string exceeds `pagination.max_query_length` bytes, the filters are stored in cache and replaced with a short token (`_mctx`) in the link. The full payload is restored on the next request.

To recover the full input payload manually:

```
use ManticoreLaravel\Builder\ManticoreBuilder;

$input = ManticoreBuilder::resolvePaginationInputFromRequest('page');
```

To flush the cached total count for a query:

```
Company::manticore()->where('status', 'active')->flushPaginationTotalCache();
```

Relevant config keys under `pagination`:

KeyDefaultDescription`context_key``_mctx`URL token name for cached filter context.`max_query_length``1500`Byte threshold above which context caching activates.`context_ttl``900`Seconds to keep the filter context in cache.`cache_prefix``manticore:pagination:`Cache key prefix.`total_cache_ttl``300`Seconds to cache the total count between pages.Optional facade
---------------

[](#optional-facade)

```
use ManticoreLaravel\Facades\Manticore;

$config = Manticore::resolveConfig('default');
$client = Manticore::client('default');
$table  = Manticore::table('companies_index', 'default');
$names  = Manticore::connectionNames();

Manticore::forgetClient('default');
```

Architecture overview
---------------------

[](#architecture-overview)

```
src/
├── Builder/
│   ├── Abstracts/ManticoreBuilderAbstract.php  — properties, connection/client wiring, execution primitives
│   ├── Concerns/
│   │   ├── HasQueryConstraints.php             — where/filter methods
│   │   ├── HasSqlCompilation.php               — SQL clause builders (SELECT, WHERE, GROUP BY, …)
│   │   ├── HasResultHydration.php              — row extraction and Eloquent model hydration
│   │   ├── HasEloquentIntegration.php          — eager-loading (with)
│   │   ├── HasConsolidation.php                — group-merge logic
│   │   └── HasPagination.php                   — pagination helpers and context management
│   ├── Grammar/ManticoreGrammar.php            — SQL fragment compiler (WHERE, MATCH, IN, ranges, …)
│   ├── ManticoreBuilder.php                    — public API; composes all concerns
│   └── Utils/
│       ├── Utf8SafeClient.php                  — Client subclass that forces UTF-8 safe JSON decoding
│       ├── Utf8SafeSearch.php                  — Search subclass with UTF-8 safe responses
│       └── Utf8SafeResponse.php                — Response wrapper that re-encodes invalid sequences
├── Contracts/
│   ├── ManticoreBuilderContract.php            — full public interface of ManticoreBuilder
│   └── ConnectionResolverContract.php          — interface for ManticoreConnectionResolver
├── Exceptions/
│   ├── ManticoreConnectionException.php        — connection not found / invalid config
│   ├── ManticoreQueryException.php             — failed SQL query, stores the query string
│   └── ManticoreIndexNotFoundException.php     — index not found (extends ManticoreQueryException)
├── Support/
│   ├── ManticoreManager.php                    — client factory, caches clients by connection name
│   └── ManticoreConnectionResolver.php         — resolves connection config (named or legacy)
├── Traits/HasManticoreSearch.php               — mixed into Eloquent models; provides manticore()
├── Facades/Manticore.php
└── ManticoreServiceProvider.php

```

Practical examples
------------------

[](#practical-examples)

### SQL mode

[](#sql-mode)

```
$rows = Company::manticore()
    ->match('fintech')
    ->select(['countryiso', 'COUNT(*) as total'])
    ->groupBy('countryiso')
    ->having('COUNT(*) > 1')
    ->orderBy('total', 'desc')
    ->get();
```

### Raw SQL

[](#raw-sql)

```
$rows = Company::manticore()
    ->rawQuery("SELECT * FROM companies_index WHERE countryiso = 'BR' LIMIT 0, 20")
    ->get();
```

### Facets

[](#facets)

```
$facets = Company::manticore()
    ->aggregate('country_agg', ['terms' => ['field' => 'countryiso', 'size' => 5]])
    ->getFacets();
```

### Chunked processing

[](#chunked-processing)

```
Company::manticore()
    ->where('status', 'active')
    ->chunk(200, function ($models) {
        foreach ($models as $model) {
            // process …
        }
    });
```

### Multi-connection

[](#multi-connection)

```
$results = Company::manticore()
    ->usingConnection('analytics')
    ->match('cloud')
    ->get();
```

### Debug

[](#debug)

```
$sql = Company::manticore()
    ->match('fintech')
    ->where('status', 'active')
    ->toSql(); // no network call
```

Tests
-----

[](#tests)

```
# Unit and Feature tests — no Manticore server required
vendor/bin/pest --testsuite=Unit
vendor/bin/pest --testsuite=Feature

# Integration tests — requires a running Manticore instance
MANTICORE_HOST=127.0.0.1 MANTICORE_PORT=9308 vendor/bin/pest --testsuite=Integration
```

The CI workflow (`.github/workflows/tests.yml`) runs Unit and Feature tests across PHP 8.1–8.3 and Laravel 10–12, and Integration tests against a live Manticore Search container on PHP 8.2–8.3.

###  Health Score

45

—

FairBetter than 91% of packages

Maintenance94

Actively maintained with recent releases

Popularity11

Limited adoption so far

Community11

Small or concentrated contributor base

Maturity54

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 50% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~30 days

Recently: every ~46 days

Total

13

Last Release

47d ago

PHP version history (2 changes)v1.0.0PHP ^8.0

v1.3.0PHP ^8.1

### Community

Maintainers

![](https://www.gravatar.com/avatar/a8c005ae29c801cf6a93548c4d4abf8a795b74fed3433a772ae7a9b4f048bbe8?d=identicon)[Renato Maldonado](/maintainers/Renato%20Maldonado)

---

Top Contributors

[![Renato27](https://avatars.githubusercontent.com/u/54475563?v=4)](https://github.com/Renato27 "Renato27 (14 commits)")[![bgrou](https://avatars.githubusercontent.com/u/61094081?v=4)](https://github.com/bgrou "bgrou (12 commits)")[![Copilot](https://avatars.githubusercontent.com/in/1143301?v=4)](https://github.com/Copilot "Copilot (2 commits)")

###  Code Quality

TestsPest

### Embed Badge

![Health badge](/badges/renatomaldonado-manticore-laravel-search/health.svg)

```
[![Health](https://phpackages.com/badges/renatomaldonado-manticore-laravel-search/health.svg)](https://phpackages.com/packages/renatomaldonado-manticore-laravel-search)
```

###  Alternatives

[statamic-rad-pack/runway

Eloquently manage your database models in Statamic.

135212.4k7](/packages/statamic-rad-pack-runway)[vizra/vizra-adk

Vizra Agent Development Kit - A comprehensive Laravel package for building intelligent AI agents.

29431.7k](/packages/vizra-vizra-adk)[ecotone/laravel

Ecotone for Laravel — CQRS, Event Sourcing, Sagas, Durable Workflows, and Outbox on top of Laravel Queue, via PHP attributes.

21313.7k3](/packages/ecotone-laravel)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
