ObjectQuel ORM

ObjectQuel is a domain-level query engine with deep ORM integration — a purpose-built query language with its own parser and execution engine, operating on entities and relationships rather than tables and joins. Querying and object mapping are designed together from the ground up, producing a model where each makes the other more capable.

A Different Mental Model

ObjectQuel's query language is not a query builder, a DSL, or SQL with class names substituted in. It has its own grammar, its own execution pipeline, and full awareness of your entity model at parse time. If you've spent time fighting Doctrine's QueryBuilder or working around DQL's structural limitations, the difference becomes clear quickly.

🎯 First-Class Query Language

ObjectQuel's range/retrieve syntax expresses queries in terms of your domain model directly — no query builder chaining, no DQL string assembly, no fluent interface ceremony. The language is the API.

🏗️ Data Mapper

Entities carry no persistence knowledge. No base classes, no magic methods, no repository leakage into your domain. The mapper is a runtime concern, not an inheritance constraint.

⚡ Performance By Design

Query decomposition, lazy loading via proxies, and metadata caching are built into the execution pipeline.

🌐 Heterogeneous Data Sources

Query across relational tables and external JSON sources in a single statement. The engine handles source resolution; your query stays uniform.

🔗 Relationship Traversal

Navigate OneToOne, OneToMany, ManyToOne, and ManyToMany relationships directly in query expressions via via clauses — no manual join conditions.

🛠️ Sculpt CLI

Entity generation, schema migrations, and reverse engineering from existing tables. Designed for gradual adoption into legacy codebases.

The Query Language

Ranges declare what entities you're working with. Retrieval specifies what you want back. The engine resolves joins, hydration, and source routing:

<?php
// Declare entity ranges, traverse relationships with `via`, filter and sort
$results = $entityManager->executeQuery("
    range of p is App\\Entity\\ProductEntity
    range of c is App\\Entity\\CategoryEntity via p.categories
    retrieve (p, c.name) where p.price < :maxPrice
    sort by p.name asc
", [
    'maxPrice' => 50.00
]);

foreach ($results as $row) {
    $product = $row['p'];           // Fully hydrated ProductEntity
    $categoryName = $row['c.name']; // Scalar projection — only what's needed

    echo $product->getName() . ' in ' . $categoryName;
}