gam6itko / spiral-validator-manticore-search
Manticore Search full-text query syntax checker for spiral/validator
Package info
github.com/gam6itko/spiral-validator-manticore-search
pkg:composer/gam6itko/spiral-validator-manticore-search
Requires
- php: ^8.3
- spiral/validator: ^1.7
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.95
- nyholm/psr7: ^1.8
- phpunit/phpunit: ^11.5 || ^12.0 || ^13.0
- spiral/framework: ^3.16
This package is auto-updated.
Last update: 2026-07-24 20:15:18 UTC
README
Manticore Search full-text query syntax checker for spiral/validator.
Validates the structure of a Manticore full-text query (extended query syntax) before it is stored or executed. Useful when a query is typed in by hand — by an administrator, a content editor, an API client — and executed later: a broken expression is rejected with a validation error on input instead of throwing a query error somewhere deep in the request that runs it.
The checker is a small hand-written lexer plus a recursive-descent parser. It needs no connection to a Manticore instance and performs no queries.
Requirements
- PHP >= 8.3
- spiral/validator ^1.7 (part of spiral/framework 3.x)
Installation
composer require gam6itko/spiral-validator-manticore-search
Registration
Add the checker to the checkers section of app/config/validator.php:
<?php declare(strict_types=1); use Gam6itko\Spiral\Validator\ManticoreSearch\ManticoreSearchChecker; return [ 'checkers' => [ // ... the default spiral/validator checkers 'manticore' => ManticoreSearchChecker::class, ], ];
Or register it from a bootloader:
use Gam6itko\Spiral\Validator\ManticoreSearch\ManticoreSearchChecker; use Spiral\Boot\Bootloader\Bootloader; use Spiral\Validator\Bootloader\ValidatorBootloader; final class AppBootloader extends Bootloader { public function init(ValidatorBootloader $validator): void { $validator->addChecker('manticore', ManticoreSearchChecker::class); } }
Usage
The checker exposes a single rule — manticore::query. Its optional argument is the list of
full-text fields the @ operator is allowed to reference:
use Spiral\Filters\Attribute\Input\Query; use Spiral\Filters\Model\Filter; use Spiral\Filters\Model\FilterDefinitionInterface; use Spiral\Filters\Model\HasFilterDefinition; use Spiral\Validator\FilterDefinition; final class NewsSearchFilter extends Filter implements HasFilterDefinition { #[Query] public string $search; public function filterDefinition(): FilterDefinitionInterface { return new FilterDefinition([ 'search' => [ 'string::trim', ['string::length', 256], // only `title` and `body` may be used with the `@` operator ['manticore::query', ['title', 'body']], ], ]); } }
Without the field list only the syntax of @ is checked, not the field names:
['manticore::query'] // `@whatever word` passes, `@whatever` (no operand) does not
The rule is deliberately narrow: an empty string and non-string values are passed through, so combine
it with notEmpty / string::* when those matter. The error message key is query, the default
message is [[Invalid search query syntax.]].
The checker can also be used on its own, outside of the validator:
$checker = new ManticoreSearchChecker(); $checker->query('"bitcoin rate"~5 -scam', ['title', 'body']); // true $checker->query('bitcoin | -scam', ['title', 'body']); // false
What is validated
Structure only, never meaning:
- quotes
"and parentheses()are balanced; - binary operators (
|,<<,NEAR/N,MAYBE,SENTENCE,PARAGRAPH) have an operand on both sides, and neither operand consists of negations only — Manticore rejectsa | -b,-a << b,a | (-b)as non-computable, but acceptsa -b | canda | (-b c); - unary NOT (
-/!) has an operand, and the query is not made of negations only; ~Nand/Nappear only right after a closing phrase quote,/Nalso as part ofNEAR/N— anywhere else~and/are a syntax error for Manticore (price/rate,http://foo.bar,word~2,"a b"~5~5);!is the NOT operator in any position, not just at the start of a term:word!andword!!are rejected (NOT without an operand),a!breads asawithoutb. A hyphen behaves differently —some-oneis an ordinary word;- the numeric argument of
~N//N/NEAR/N/@field[N]is a positive integer; @field/@(f1,f2)/@!field/@field[N]refers to a known field, when the field list is given;- a term made of positional markers only (
^,$,=,*) does not count as a positive term:^is rejected,word ^is accepted.
The reference implementation is Manticore's own parser: whatever the checker accepts, the daemon must
be able to parse without an error. The rules were calibrated by running a corpus of queries through
SELECT ... WHERE MATCH(?) against manticore 28.4.4; that corpus is the test suite.
In a few corner cases the checker is stricter than Manticore, where the daemon tolerates input
that cannot match anything: an empty group (), a lone @, @field[abc], a dangling \ at the end
of the string. Each of those is a plain typo, and a validation error is more useful than an empty
result set. See provideStricterThanManticore() in the test suite for the full list.
What the checker does not do: it does not check that terms exist in the index, does not evaluate
the query, and does not touch non-full-text parts of a SELECT (attribute filters, ORDER BY,
OPTION) — it only looks at what goes inside MATCH().
Testing
composer install composer tests composer csfix
Links
- Manticore Search: extended query syntax
- spiral/validator — documentation
- spiral/framework — documentation
License
MIT. See LICENSE.