hosmelq/search-syntax-parser

v0.1.0 2025-08-12 04:24 UTC

This package is auto-updated.

Last update: 2025-09-03 03:21:02 UTC


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.