Expressive Path-Based Query Builder for PHP

GitHub last commit CI Workflow GitHub code size in bytes GitHub Issues Total Downloads Monthly Downloads

derafu/query is a PHP library for building and filtering SQL queries using a compact, URL-safe string syntax. A single expression like customers[alias:c]__invoices[on:id=customer_id,alias:i]__total?>1000 carries the full join path, join conditions, and filter in one string — no separate join calls required.

Why Derafu\Query?

Traditional query builders require explicit join definitions, verbose relationship navigation, and different filter syntaxes across engines. derafu/query replaces all of that with:

  • A single expression format that encodes path, joins, and filter in one string.
  • Automatic join generation from path segments — no manual join() calls when using path syntax.
  • 50+ operators covering comparisons, patterns, lists, ranges, dates, NULL, regex, bitwise, and subqueries.
  • Database-specific SQL generated from the same expression — PostgreSQL, MySQL, and SQLite without code changes.
  • Framework bridges for Doctrine DBAL, Doctrine ORM, Laravel’s Illuminate query builder, and API Platform.
Feature Derafu\Query Traditional Query Builders
Path-Based Relationships
Automatic Join Resolution
Configurable Operators
Framework Agnostic Core ⚠️
Unified Filter Syntax ⚠️
Multi-DB SQL Generation ⚠️

Installation

composer require derafu/query

Quick Start

With SqlQueryBuilder (standalone)

use Derafu\Query\Builder\SqlQueryBuilder;
use Derafu\Query\Engine\PdoEngine;
use Derafu\Query\Filter\ExpressionParser;
use Derafu\Query\Filter\PathParser;
use Derafu\Query\Filter\FilterParser;
use Derafu\Query\Filter\CompositeExpressionParser;
use Derafu\Query\Operator\OperatorLoader;
use Derafu\Query\Operator\OperatorManager;

$pdo    = new PDO('sqlite:/path/to/db.sqlite');
$engine = new PdoEngine($pdo);

$loader  = new OperatorLoader();
$manager = new OperatorManager($loader->loadFromFile('vendor/derafu/query/resources/operators.yaml'));
$parser  = new CompositeExpressionParser(
    new ExpressionParser(new PathParser(), new FilterParser($manager))
);

$qb = new SqlQueryBuilder($engine, $parser);

// Simple filter.
$rows = $qb->table('products')->where('price?>1000')->execute();

// Multi-table path (auto-generates the JOIN).
$rows = $qb
    ->select('c.name AS customer_name, i.number, i.total')
    ->where('customers[alias:c]__invoices[on:id=customer_id,alias:i]__total?>1000')
    ->execute();

With a Framework Bridge

// Doctrine DBAL.
use Derafu\Query\Bridge\DoctrineDBALQueryBuilderConditionApplier;
use Derafu\Query\Filter\CompositeExpressionParser;

$applier = new DoctrineDBALQueryBuilderConditionApplier();
$condition = $parser->parse('status?=active&&total?>1000');
$applier->apply($dbalQueryBuilder, $condition);

The Expression Format

Every filter is a string of the form:

path?filter

The ? separates the path (which column or relationship to target) from the filter (which operator and value to apply).

price?>1000                         ← column "price", operator ">", value "1000"
status?in:paid,issued               ← column "status", operator "in:", value "paid,issued"
created_at?date:20240301            ← column "created_at", operator "date:", value "20240301"
deleted_at?is:null                  ← column "deleted_at", operator "is:null", no value

Multi-segment paths navigate relationships and generate joins automatically:

customers[alias:c]__invoices[on:id=customer_id,alias:i]__total?>1000

Subquery paths starting with ___ generate correlated EXISTS / aggregate subqueries:

___payments[on:id=invoice_id]?is:empty          ← NOT EXISTS (payments)
___payments[on:id=invoice_id]__status?=pending  ← EXISTS with column filter
___payments[on:id=invoice_id]__SUM(amount)?>=500← aggregate scalar subquery

Multiple conditions can be combined with && (AND), || (OR), and () (grouping):

status?=active&&total?>1000
category?=electronics||(category?=software&&price?<500)

Operator Overview

derafu/query ships with over 50 operators across 10 types:

Type Examples SQL produced
Standard =, !=, >, <, >=, <= col = :p, col > :p
AutoLike ^, $, ~~, ~~*, !~~ col LIKE :p with auto %
Like like:, ilike:, notlike: col LIKE :p, col ILIKE :p
List in:, notin: col IN (:p1, :p2, …)
Range between:, notbetween: col BETWEEN :p1 AND :p2
Date date:, month:, year:, period: DATE(col) = :p, etc.
NULL is:null, isnot:null, <=> col IS NULL
Subquery is:empty, isnot:empty NOT EXISTS (…), EXISTS (…)
RegExp ~, ~*, !~, similarto: col ~ :p, col SIMILAR TO :p
Binary b&, b|, b^, b<<, b>> col & :p, etc.

What This Documentation Covers

Page Content
Architecture Layer structure, class responsibilities, data flow
Expression Syntax path?filter format, composite &&/||/()
Path Syntax Segments, options, join paths, subquery paths
Operators Reference All operators with SQL templates, validation, casting
Query Builder Fluent API and declarative QueryConfig
Framework Bridges Doctrine DBAL/ORM, Illuminate, API Platform
Security Guide SQL sanitization and safe usage patterns
On this page

Last updated on 05/05/2026 by Anonymous