Stay in Your Domain Model.
Even When Queries Get Complex.
A PHP ORM and query language inspired by QUEL. ObjectQuel lets you express complex data access through entities and relationships — not tables, joins, or database-specific SQL. Write database-independent domain queries; ObjectQuel generates optimized SQL for your database.
// 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
You declare data ranges and relationships — like via p.categories — instead of specifying joins, conditions, and execution order. ObjectQuel's planner figures out the rest.
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.
When Other ORMs Run Out of Abstraction
Simple queries are easy in almost every ORM. The difference shows up when the query gets harder — not longer, harder.
Doctrine
Drops to native SQL$sql = "
SELECT u.name, o.total,
RANK() OVER (
PARTITION BY u.id ORDER BY o.total DESC
) AS rnk
FROM users u
JOIN orders o ON o.user_id = u.id
JOIN products p ON p.id = o.product_id
JOIN categories c ON c.id = p.category_id
WHERE o.status = 'completed' AND
c.name = 'Electronics'
";
$rows = $em->getConnection()
->executeQuery($sql)
->fetchAllAssociative();
The entity manager steps aside. Results come back as arrays, not entities — the abstraction ends here.
ObjectQuel
Still QUEL$results = $em->executeQuery("
range of u is User
range of o is Order via u.orders
range of p is Product via o.products
range of c is Category via p.category
retrieve (
u.name, o.total,
rnk = rank(by u.id sort by o.total desc)
)
where o.status = 'completed'
and c.name = 'Electronics'
");
Still entities, still ranges, still hydrated objects. The window function is just another retrieve expression.
Try It Live
Write an ObjectQuel query against a live database and see the generated SQL instantly.
{{currentSql}}
{{json}}
| id | int |
| name | string |
| string |
| id | int |
| street | string |
| houseNumber | string |
| customerId | int (FK) |
One Abstraction, All the Way Down
Regex, full-text search, hybrid sources — these are the cases that normally punch a hole through an ORM's abstraction and send you to raw SQL. ObjectQuel keeps them inside the same domain-level language instead.
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.
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
ObjectQuel handles SQL and JSON sources, then hydrates results into objects
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
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);
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