Declarative Query Engine • Data Mapper • PHP

Describe Data.
Not Execution.

A query language for PHP, one level above SQL. Inspired by QUEL — the relational query language that predated SQL. Declare data ranges, traverse relationships, filter with regex. ObjectQuel compiles to optimized SQL so you never have to write it.

Mascotte
// Declare data ranges
range of p is Product
range of c is Category via p.categories

// Describe what you want
retrieve (p, category_name=c.name)
where p.price < 100
  and c.active = true

Why Ranges Beat Joins

Compare how different approaches express the same data intent.

Doctrine QueryBuilder
Procedural
$qb = $em->createQueryBuilder();
$qb->select('p', 'c.name as category_name')
   ->from(Product::class, 'p')
   ->leftJoin('p.categories', 'c')
   ->where('p.price < :maxPrice')
   ->andWhere('c.active = :active')
   ->setParameter('maxPrice', 100)
   ->setParameter('active', true);

$results = $qb->getQuery()->getResult();

You explicitly define joins, chain conditions, and control execution details.

ObjectQuel
Declarative
$results = $em->executeQuery("
    range of p is Product
    range of c is Category via p.categories
    retrieve (p, category_name=c.name)
    where p.price < :maxPrice
      and c.active = true
", [
    'maxPrice' => 100
]);
    

You declare data ranges and constraints. ObjectQuel analyzes the query and determines execution.

Navigation, Not Configuration

Relationships are paths: via p.categories expresses traversal, not SQL join mechanics.

Smart Query Decomposition

ObjectQuel analyzes ranges and constraints, then generates optimized execution steps automatically.

Composable by Design

Based on tuple relational calculus — QUEL’s declarative model for expressing relational intent.

Try It Live

Write an ObjectQuel query against a live database and see the generated SQL instantly.

Examples
ObjectQuel Ctrl + Enter to run
{{error}}
Generated SQL
Run a query to see the generated SQL
{{currentSql}}
Results
Run a query to see results
{{json}}
Customer
idint
namestring
emailstring
Address
idint
streetstring
houseNumberstring
customerIdint (FK)

Choose Your Approach

Simple methods for everyday use, and a query language when relationships and logic grow complex

Simple Methods
// Find by primary key
$user = $em->find(User::class, $id);

// Find by criteria
$posts = $em->findBy(
    Post::class,
    [
        'published' => true,
        'authorId' => 5
    ]
);

Ideal for straightforward lookups and simple filtering

Query Language for Complexity
// Complex relationships and filtering
$results = $em->executeQuery("
    range of p is Product
    range of c is Category via p.categories
    retrieve (p, category_name=c.name)
    where p.price < :maxPrice
      and c.active = true
      and p.name = \"widget*\"
    sort by p.name asc
", ['maxPrice' => 100.00]);

Designed for complex relationships, multistep filtering, and non-trivial queries

What ObjectQuel Can Do That Others Can't

Capabilities that aren't possible in DQL, Eloquent, or standard query builders

Regex & Wildcard Filters

Write where p.name = /^widget/i or p.sku = "ABC*XYZ" natively. No whereRaw, no database-specific functions.

Full-Text Search

search(p.description, "banana +pear -apple") compiles to the correct full-text implementation for your database — no raw SQL required.

Hybrid SQL + JSON Queries

Join a SQL table and a JSON file in a single query. The engine handles cross-source matching automatically.

Relationship Traversal

via p.category is all you write. No join conditions, no foreign key configuration in the query itself.

Automatic Query Decomposition

Complex queries are split into optimized subtasks automatically. You describe the data; the engine determines execution.

Gradual Adoption

Works alongside existing database code. Introduce ObjectQuel queries one at a time — no big-bang rewrite required.

Why ObjectQuel Exists

After 25 years writing database queries, I got tired of fighting ORMs that force you to think in SQL joins or drown in method chaining. ObjectQuel brings QUEL’s declarative approach to PHP — declare data ranges and constraints, and let the engine determine execution.

— Floris, Quellabs

How ObjectQuel Works

From query to hydrated entities

Query Processing Pipeline
  • Parse: QUEL syntax into abstract syntax tree
  • Decompose: Break complex queries into optimized subtasks
  • Execute: Fetch from SQL databases and JSON sources
  • Hydrate: Join results and create entity objects

ObjectQuel handles optimization automatically, so you focus on describing data, not execution mechanics.

ObjectQuel Query Flow
QUEL Query range of p is... Parse AST Abstract Syntax Tree Decompose Query Decomposer Optimize & Plan SQL JSON SQL Queries Database Access JSON Data External Sources Join Results Merge Data Hydrate Entity Objects Ready to Use

ObjectQuel handles SQL and JSON sources, then hydrates results into objects

🚀

Get Started in Minutes

Install ObjectQuel via Composer and start querying with objects immediately

# Install via Composer
composer require quellabs/objectquel
Quick Start Example
use Cake\Database\Connection;

// Configure ObjectQuel
$config = new Configuration();
$config->setEntityPath(__DIR__ . '/Entities/');
$config->setProxyDir(__DIR__ . '/cache/proxies/');
$config->setMetadataCachePath(__DIR__ . '/cache/metadata/');

// Create CakePHP database connection
$connection = new Connection([
    'driver' => 'Cake\Database\Driver\Mysql',
    'host' => 'localhost',
    'username' => 'user',
    'password' => 'pass',
    'database' => 'db'
]);

// Initialize EntityManager
$entityManager = new EntityManager($config, $connection);

// Query immediately
$user = $entityManager->find(User::class, 10);
View on GitHub
Repository
View on Packagist
Package
Documentation
Read Docs

Open source and MIT licensed

The Quellabs Ecosystem

A family of open-source tools built around the same philosophy.

Ready to Modernize Your Data Layer?

Join developers who've already simplified their database code with ObjectQuel

GitHub Page Read Docs Sponsor on GitHub