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
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.
$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.
$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.
Relationships are paths: via p.categories expresses traversal, not SQL join mechanics.
ObjectQuel analyzes ranges and constraints, then generates optimized execution steps automatically.
Based on tuple relational calculus — QUEL’s declarative model for expressing relational intent.
Simple queries are easy in almost every ORM. The difference shows up when the query gets harder — not longer, harder.
$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.
$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.
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) |
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.
Write where p.name = /^widget/i or p.sku = "ABC*XYZ" natively. No whereRaw, no database-specific functions.
search(p.description, "banana +pear -apple") compiles to the correct full-text implementation for your database — no raw SQL required.
Join a SQL table and a JSON file in a single query. The engine handles cross-source matching automatically.
via p.category is all you write. No join conditions, no foreign key configuration in the query itself.
Complex queries are split into optimized subtasks automatically. You describe the data; the engine determines execution.
Works alongside existing database code. Introduce ObjectQuel queries one at a time — no big-bang rewrite required.
From query to hydrated entities
ObjectQuel handles optimization automatically, so you focus on describing data, not execution mechanics.
ObjectQuel handles SQL and JSON sources, then hydrates results into objects
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
Install ObjectQuel via Composer and start querying with objects immediately
# Install via Composer
composer require quellabs/objectquel
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
A family of open-source tools built around the same philosophy.
Join developers who've already simplified their database code with ObjectQuel