PHP ORM • QUEL-Inspired • Data Mapper

Define Ranges.
Not Joins.

A PHP ORM powered by QUEL's tuple calculus approach. No manual joins. No method chaining. No query builder verbosity. Just declare what data ranges you need and how they relate.

// Define your data ranges
range of p is Product
range of c is Category via p.categories

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

Why Ranges Beat Joins

Compare how different ORMs express the same query

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 specify the join strategy, manually chain conditions, manage the execution order.

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 determines optimal joins and execution order.

Navigation, Not Configuration

Relationships are paths: via p.categories expresses traversal, not LEFT JOIN mechanics.

Smart Query Decomposition

ObjectQuel analyzes your ranges and generates optimized SQL - you never write JOIN clauses.

Composable by Design

Based on tuple relational calculus - QUEL's approach that Codd considered truer to relational algebra.

Choose Your Approach

Simple methods for everyday queries and the query language for complex scenarios

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

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

Perfect for straightforward queries with simple criteria

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]);

Powerful for complex relationships and advanced filtering

When ObjectQuel Shines

Where QUEL's declarative approach delivers the most value

Complex Relationship Navigation

Traverse multiple relationship levels without manual join configuration.

Legacy System Modernization

Adopt gradually alongside existing database code - no big-bang rewrite required.

Hybrid Data Sources

Combine SQL databases and JSON APIs in unified queries.

Secure by Default

All queries use parameterized statements automatically - zero SQL injection risk.

Performance Optimization

Query decomposer analyzes and optimizes execution paths automatically.

Canvas Framework Integration

Native integration with Canvas PHP - zero configuration needed when using both together.

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 what you need, not how to fetch it.

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
$config = new Configuration();
$config->setDsn('mysql://user:pass@localhost/db');
$config->setEntityPath(__DIR__ . '/Entities/');

$entityManager = new EntityManager($config);

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

Open source and MIT licensed

Ready to Modernize Your Data Layer?

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

GitHub Page Get Started Now