hosmelq / search-syntax-parser
Requires
- php: ^8.2
- archtechx/enums: ^1.1
- doctrine/lexer: ^3.0
- thecodingmachine/safe: ^3.3
Requires (Dev)
- ergebnis/composer-normalize: ^2.47
- laravel/pint: ^1.24
- pestphp/pest: ^3.8
- phpstan/extension-installer: ^1.4
- phpstan/phpstan: ^2.1
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-strict-rules: ^2.0
- rector/rector: ^2.1
- rector/type-perfect: ^2.1
- shipmonk/composer-dependency-analyser: ^1.8
- spaze/phpstan-disallowed-calls: ^4.6
- thecodingmachine/phpstan-safe-rule: ^1.4
- ticketswap/phpstan-error-formatter: ^1.1
- tomasvotruba/type-coverage: ^2.0
README
Parse search queries into structured data with support for field searches, boolean logic, range comparisons, and multiple output formats.
Introduction
Use a concise, expressive query language to build structured queries. It supports field-specific searches, boolean operators, ranges, existence checks, and multi-value lists, and outputs to multiple formats via adapters.
use HosmelQ\SearchSyntaxParser\SearchParser; $result = SearchParser::query('age:>25 AND name:john')->build();
This returns a structured representation of the query that can be adapted to different backends (arrays, ORMs, APIs).
Requirements
- PHP 8.2+
Installation & setup
Install the package via composer:
composer require hosmelq/search-syntax-parser
Basic usage
Getting started
Create a parser from a query string and build it using the default array adapter:
use HosmelQ\SearchSyntaxParser\SearchParser; $result = SearchParser::query('title:Coffee AND price:<10')->build();
Default adapter (array)
When no adapter is provided, build()
uses the array adapter and returns a structured PHP array.
use HosmelQ\SearchSyntaxParser\SearchParser; $result = SearchParser::query('title:Coffee')->build(); // Array output /* [ 'field' => 'title', 'operator' => '=', 'type' => 'comparison', 'value' => 'Coffee', ] */
Query types
Connectives (AND/OR)
Combine multiple terms with logical operators. When no connective is specified, AND is implied.
SearchParser::query('title:Coffee AND price:<10')->build(); // Explicit AND SearchParser::query('title:Coffee OR title:Tea')->build(); // OR operator SearchParser::query('title:Coffee price:<10')->build(); // Implicit AND
Comparators
Use field comparison operators to define the relationship between a field and its value:
SearchParser::query('price:>10')->build(); // Greater than SearchParser::query('price:>=10')->build(); // Greater than or equal SearchParser::query('price:<50')->build(); // Less than SearchParser::query('price:<=50')->build(); // Less than or equal SearchParser::query('status:!=sold')->build(); // Not equal
Comma-separated values
Use multi-value field searches as syntactic sugar for OR operations:
// Single field, multiple values SearchParser::query('status:ACTIVE,DRAFT,PENDING')->build(); // Equivalent to: status:ACTIVE OR status:DRAFT OR status:PENDING // Works with any operator SearchParser::query('status:!=SOLD,EXPIRED')->build(); // Equivalent to: status:!=SOLD OR status:!=EXPIRED // Can be combined with boolean logic SearchParser::query('status:ACTIVE,DRAFT AND price:>100')->build(); // Supports quoted values SearchParser::query('category:"Home & Garden","Sports & Outdoors"')->build();
Exists queries
Search for documents with non-null values in specified fields using wildcard syntax:
SearchParser::query('category:*')->build(); // Field has any value SearchParser::query('NOT discount:*')->build(); // Field doesn't exist
Range queries
Search within value ranges using boundary operators:
SearchParser::query('price:[10 TO 50]')->build(); // Numeric range SearchParser::query('date:[2025-01-01 TO 2025-12-31]')->build(); // Date range
Terms
Search using basic terms that match default searchable fields:
SearchParser::query('coffee')->build();
Modifiers (NOT)
Negate terms or subqueries using -
or NOT
:
SearchParser::query('NOT title:Coffee')->build(); // NOT modifier SearchParser::query('-title:Coffee')->build(); // - modifier (equivalent)
Field validation
Restrict which fields can be used and validate their values using AllowedField
helpers:
use HosmelQ\SearchSyntaxParser\SearchParser; use HosmelQ\SearchSyntaxParser\Validation\AllowedField; $parser = SearchParser::query('age:25 AND status:ACTIVE')->allowedFields([ AllowedField::integer('age')->min(0), AllowedField::in('status', ['ACTIVE', 'DRAFT', 'PENDING']), AllowedField::string('name')->size(2), ]); $result = $parser->build(); // throws if any value is invalid or a field is not allowed
You can also map external field names to internal ones (useful for adapters):
$parser = SearchParser::query('age:10')->allowedFields([ AllowedField::integer('age', 'user_age'), ]); $result = $parser->build(); // The array adapter will output the internal name "user_age" for the field
Custom adapters
Create custom output formats by implementing the adapter interface:
use HosmelQ\SearchSyntaxParser\Adapter\QueryAdapterInterface; use HosmelQ\SearchSyntaxParser\AST\Node\NodeInterface; use HosmelQ\SearchSyntaxParser\SearchParser; class EloquentAdapter implements QueryAdapterInterface { public function build(NodeInterface $ast): mixed { // Convert the AST to your preferred format return ['where' => ['name', 'john']]; } } $parser = SearchParser::query('name:john'); $parser->extend('eloquent', fn () => new EloquentAdapter()); $result = $parser->build('eloquent');
Error handling
Handle parsing errors with ParseException
:
use HosmelQ\SearchSyntaxParser\Exception\ParseException; use HosmelQ\SearchSyntaxParser\SearchParser; try { SearchParser::query('invalid:syntax:here')->build(); } catch (ParseException $e) { echo "Parse error: " . $e->getMessage(); }
Testing
composer test
Changelog
Please see CHANGELOG.md for more information on what has changed recently.
Credits
License
The MIT License (MIT). Please see License File for more information.