avadim / manticore-query-builder-php
Manticore Search Query Builder for PHP (unofficial PHP client)
Package info
github.com/aVadim483/manticore-query-builder-php
pkg:composer/avadim/manticore-query-builder-php
Requires
- php: ^7.4|^8.1
- ext-json: *
- ext-mbstring: *
- ext-pdo: *
- psr/log: >=1.1
Requires (Dev)
- phpunit/phpunit: ^9.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
English | Русский
Manticore Search Query Builder for PHP (unofficial PHP client)
Query Builder for Manticore Search in PHP with Laravel-like syntax
Features
- MySQL multiple connections via PDO (because Manticore is SQL-first)
- Placeholders as prefix in table names
- Named parameters in expressions
- Clear Laravel-like syntax
- Multiple INSERT and REPLACE
- Support MATCH() and multi-level WHERE for SELECT
- Support faceted search, JOIN of two tables and KNN vector search
- MVA ANY/ALL filters and geographic radius searches, distance columns and sorting
- Column types are applied in both directions: PHP values on write, PHP types on read
- The helpers of the Laravel query builder: aggregates, chunked walks, conditional building, conditions on dates, upsert and the rest
- CALL helpers:
callAutocomplete(),callSuggest(),callQsuggest(),callKeywords(),callSnippets()andcallPq()(percolate queries) - A rejected read throws, a rejected write answers with false or zero and keeps its reason
- PSR-3 logging of queries and EXPLAIN of full-text expressions
Manticore Search server documentation: https://manual.manticoresearch.com/
Related packages
This is the standalone builder: PHP and PDO are all it needs, and the two packages below are built on it.
avadim/manticore-query-builder-laravel— the integration with Laravel and Lumen: a service provider,config/manticore.php, named connections, a facade, and reads answering with aCollectionof row objects.avadim/manticore-laravel-scout— the ManticoreSearch driver for Laravel Scout: full-text search of Eloquent models asPost::search('manticore')->get().
Requirements
- PHP 7.4 or above with the PDO extension
- Manticore Search reachable over its SQL interface (port 9306 by default). The HTTP/JSON API is not used.
What needs more than the bare server
Most of the builder talks to the server and nothing else. A few methods depend on a library loaded by the server, or on Manticore Buddy running next to it:
| Method | Needs | Without it |
|---|---|---|
| everything else | the server alone | — |
floatVector(), whereKnn() |
KNN library | the table cannot be created: knn library not loaded |
columnar(), columnEngine('columnar'), engine => 'columnar' |
Columnar library | the table cannot be created: columnar library not loaded |
rename() |
Manticore Buddy | the server answers with a syntax error |
callAutocomplete() |
Manticore Buddy and table infixes | the server rejects the call |
The KNN and columnar libraries both come from the Manticore Columnar Library; the secondary-index library ships with them and speeds filtering up without changing anything in the API.
The libraries have to match the daemon: it checks their ABI version on load and refuses one
that is too new, saying so only when started with --console. Which ones did load is written
in the version string:
SHOW STATUS LIKE 'version'; -- 28.6.6 … (columnar 13.8.3 …) (secondary 13.8.3 …) (knn 13.8.3 …)
A library that is missing from that line is not loaded, whatever the files on disk say.
Installation
composer require avadim/manticore-query-builder-php
Quick start guide
use avadim\Manticore\QueryBuilder\Builder as ManticoreDb; use avadim\Manticore\QueryBuilder\Schema\SchemaTable; // Define config $config = [ 'defaultConnection' => 'default', 'connections' => [ // Default connection which will be used with environment variables 'default' => [ 'host' => 'localhost', 'port' => 9306, 'username' => null, 'password' => null, 'timeout' => 5, 'prefix' => 'test_', // prefix that will replace the placeholder "?<table_name>" 'force_prefix' => false, ], ], ]; // Init query builder ManticoreDb::init($config); // Create table ManticoreDb::create('?products', function (SchemaTable $table) { $table->timestamp('created_at'); $table->string('manufacturer'); $table->text('title'); $table->json('info'); $table->float('price'); $table->multi('categories'); $table->bool('on_sale'); }); // Insert single row $singleRow = [ 'created_at' => time(), 'manufacturer' => 'Samsung', 'title' => 'Galaxy S23 Ultra', 'info' => ['color' => 'Red', 'storage' => 512], 'price' => 1199.00, 'categories' => [5, 7, 11], 'on_sale' => true, ]; $id = ManticoreDb::table('?products')->insertGetId($singleRow); // insert() returns true/false, insertGetId() - the <id> of the new record // Insert multiple rows $multipleRows = [ [ 'created_at' => time(), 'manufacturer' => '...', 'title' => '...', 'info' => [], // ... ], [ 'created_at' => time(), 'manufacturer' => '...', 'title' => '...', 'info' => [], // ... ], ]; $res = ManticoreDb::table('?products')->insertResultSet($multipleRows); // $res->result() => array of <id> of new records // Update and delete answer with the number of affected rows $updated = ManticoreDb::table('?products')->where('price', '<', 100)->update(['on_sale' => true]); $deleted = ManticoreDb::table('?products')->where('price', '<', 1)->delete(); // A failed write is not thrown - insert() gives false, update()/delete() give 0, // and the reason is in the ResultSet left behind if (!ManticoreDb::table('?products')->insert($singleRow)) { $error = ManticoreDb::lastResultSet()->error(); } // A failed read is thrown instead: null in place of the rows would look like an empty table try { $rows = ManticoreDb::table('?products')->where('nosuchcolumn', 1)->get(); } catch (�vadim\Manticore\QueryBuilder\QueryErrorException $e) { $error = $e->getMessage(); } // Search: get() returns rows keyed by document id, with values cast back to PHP types // ('info' as an array, 'categories' as int[], 'on_sale' as bool) $rows = ManticoreDb::table('?products')->match('galaxy')->where('price', '>', 1100)->get();
Documentation
More detailed documentation is available in the /docs folder: configuration, searching, tables, result set, logging.
Документация на русском языке: README.ru.md и папка /docs/ru.
Tests
vendor/bin/phpunit --testsuite unit # SQL building and parsing, no server needed vendor/bin/phpunit # adds the integration tests vendor/bin/phpunit --exclude-group buddy # omit new autocomplete tests on a server without Buddy
The integration tests create and drop their own tables on a live server. They are skipped,
not failed, when nothing listens on MANTICORE_HOST:MANTICORE_PORT (127.0.0.1:9306
by default).
GitHub Actions runs the full suite against Manticore 29.9.0 on PHP 7.4 and 8.4,
plus a separate latest run on PHP 8.4. Before PHPUnit, tests/ci/check-manticore.php
waits for working SQL, Columnar filtering, KNN search and a Buddy table rename,
using temporary tables that it removes afterwards. Run this probe only against a test server.
The readiness timeout defaults to 90 seconds (MANTICORE_READY_TIMEOUT, 1–120 seconds);
the probe uses the same host and port environment variables as the integration tests.
CI uses --fail-on-skipped --fail-on-incomplete: missing components cannot produce
a successful full run. Local test skipping remains unchanged. JUnit reports, readiness
output and container diagnostics are uploaded as artifacts, including on failure,
and retained for 14 days. The workflow also supports manual runs from the Actions tab
once the workflow is on the default branch. Both pinned-version and latest runs must pass.
Want to support?
If you find this package useful, just give me a star on GitHub :)