renatomaldonado / manticore-laravel-search
Laravel driver for Manticore Search using the official PHP client
Package info
github.com/Renato27/laravel-manticore-search
pkg:composer/renatomaldonado/manticore-laravel-search
Requires
- php: ^8.0
- laravel/framework: ^10.0|^11.0|^12.0|^13.0
- manticoresoftware/manticoresearch-php: ^4.0
Requires (Dev)
- orchestra/testbench: ^8.0
- pestphp/pest: ^2.0
- pestphp/pest-plugin-laravel: ^2.0
- phpunit/phpunit: ^10.0|^11.0|^12.0|^13.0
This package is auto-updated.
Last update: 2026-04-01 11:02:32 UTC
README
A driver for integrating Manticore Search with Laravel, featuring a fluent API, Eloquent model hydration, robust pagination, and support for multiple connections.
What this library does
- Exposes a fluent query builder through
Model::manticore(). - Supports full-text search (
match) plus filters (where,whereIn,whereBetween, etc.). - Supports SQL mode (
select,groupBy,having,orderBy,toSql,rawQuery). - Hydrates results into your Model instances (including casting and attribute mapping).
- Preserves request filters during pagination, including large payloads via a cached context token.
- Provides an optional facade (
Manticore::...) for manager/client/table access.
Requirements
- PHP
^8.0 - Laravel
^10|^11|^12|^13 manticoresoftware/manticoresearch-php ^4.0
Installation
composer require renatomaldonado/manticore-laravel-search
Configuration
Publish the configuration file:
php artisan vendor:publish --provider="ManticoreLaravel\ManticoreServiceProvider"
Recommended structure (multiple connections)
return [ 'default' => env('MANTICORE_CONNECTION', 'default'), 'connections' => [ 'default' => [ 'host' => env('MANTICORE_HOST', '127.0.0.1'), 'port' => env('MANTICORE_PORT', 9312), '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), ], 'analytics' => [ 'host' => env('MANTICORE_ANALYTICS_HOST', '10.0.0.2'), 'port' => env('MANTICORE_ANALYTICS_PORT', 9312), 'max_matches' => 2000, ], ], ];
Legacy compatibility
The library keeps a fallback for the “flat” configuration format (manticore.host, manticore.port, etc.).
If connections is empty or missing, the resolver uses this legacy format.
Model usage
Use 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(): array { return ['companies_index']; } }
If the model uses the trait and does not implement
searchableAs(), aRuntimeExceptionis thrown.
Quick example
$results = Company::manticore() ->match('startup') ->where('countryiso', 'BR') ->orderBy('entityid', 'desc') ->limit(10) ->get();
Builder public API
Query building
match(string $keywords, ?string $field = null, string $boolean = 'AND')where(string $field, mixed $operatorOrValue, mixed $value = null)orWhere(string $field, mixed $operatorOrValue, mixed $value = null)whereNot(string $field, mixed $operatorOrValue, mixed $value = null)whereIn(string $field, array $values)whereNotIn(string $field, array $values)whereBetween(string $field, array $range)whereGeoDistance(string $field, float $lat, float $lon, float $distanceMeters)orderBy(string|array $column, ?string $direction = null)select(array|string $fields)groupBy(array|string $fields)having(array|string $conditions)aggregate(string $name, array $aggregation)expression(string $name, mixed $exp)option(string $key, mixed $value)maxMatches(int $value)withHighlight()with(array|string ...$relations)when($condition, callable $callback, ?callable $default = null)
Execution context
usingConnection(string $name)→ selects a named connection.useIndex(array|string $indexes)→ overrides the index(es) returned bysearchableAs().rawQuery(string $raw, bool $rawMode = false)→ executes manual SQL.toSql()→ inspects the generated SQL.
Execution and return values
get()first()last()count()pluck(string $field)toArray()toJson(int $options = 0)paginate(int $perPage = 15, string $pageName = 'page', ?int $page = null)getFacets()(not supported inrawQuery)
Consolidation (deduplication by key)
consolidateBy(string $groupField, string $historyAttribute = 'history', bool $preserveGroupFieldInHistory = true)- Returns one consolidated item.
consolidateAllBy(string $groupField, string $historyAttribute = 'history', bool $preserveGroupFieldInHistory = true)- Returns a consolidated collection.
getConsolidatedBy(...)- Alias for
consolidateAllBy.
- Alias for
paginateConsolidatedBy(string $groupField, int $perPage = 15, string $pageName = 'page', ?int $page = null, ...)- Paginates results that have already been consolidated.
Pagination and filter preservation
paginate() and paginateConsolidatedBy() automatically preserve filters from the current request:
- GET query params
- POST/JSON payloads
- custom page names (
pageName) - case variations in the page key (
Page,page)
When the query string becomes too large, the library stores the filters in cache and uses a short token (_mctx) in the link.
To recover the full payload in subsequent requests:
use ManticoreLaravel\Builder\ManticoreBuilder; $input = ManticoreBuilder::resolvePaginationInputFromRequest('page');
Relevant config (config/manticore.php):
pagination.context_keypagination.max_query_lengthpagination.context_ttlpagination.cache_prefixpagination.total_cache_ttl
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');
Practical examples
SQL mode (select + group + having)
$rows = Company::manticore() ->match('fintech') ->select(['countryiso', 'COUNT(*) as total']) ->groupBy('countryiso') ->having('COUNT(*) > 1') ->orderBy('total', 'desc') ->limit(10) ->get();
Raw SQL
$rows = Company::manticore() ->rawQuery("SELECT * FROM companies_index WHERE countryiso = 'BR' LIMIT 0, 20") ->get();
Facets
$facets = Company::manticore() ->aggregate('countryiso_agg', [ 'terms' => [ 'field' => 'countryiso', 'size' => 5, ], ]) ->getFacets();
Eager loading after hydration
$rows = Company::manticore() ->match('cloud') ->with('owner:id,name', 'tags') ->limit(20) ->get();
Important notes
max_matchesis always included in the SQLOPTIONclause (connection value or method override).usingConnection()invalidates the builder's local client/config cache to avoid incorrect state.useIndex()invalidates the builder's resolved index cache.- The library includes “UTF-8 safe” clients/responses to handle responses with invalid encoding.
Tests
Run feature tests (without a Manticore server):
./vendor/bin/pest tests/Feature
Run the resolver unit test (without a server):
./vendor/bin/phpunit tests/ManticoreConnectionResolverTest.php
Run builder integration tests (requires Manticore with a test index):
./vendor/bin/phpunit tests/ManticoreBuilderTest.php