Overview
I recently completed a major refactor of my custom ORM, evolving it from a hybrid Active Record structure into a clean, layered, driver-aware architecture.
The new design removes hidden “magic”, improves type-safety, and gives each component a precise, single responsibility.
1. Model Layer
Models now define only structure and metadata.
They no longer execute queries or manage persistence.
Instead, each Model delegates all operations to a dedicated ActiveQuery pipeline, ensuring:
- deterministic behavior
- zero hidden state
- strict separation of concerns
In future updates, I will remove the "fillable" property entirely and introduce true encapsulated domain fields, powered by Value Objects.
2. ORM Runtime
The ORM runtime stores only two global resources:
- the active PDO instance
- the SQL driver currently in use
No additional state is leaked across queries.
This makes the system predictable, stable, and fully driver-agnostic.
3. ActiveQueryFactory
This factory assembles the complete ORM pipeline for every Model:
- loads model metadata
- creates the correct QueryBuilder based on the driver
- instantiates the QueryExecutor
- instantiates the ModelHydrator
- returns a clean, isolated ActiveQuery instance
Every query begins with a fresh builder and clean state.
4. Driver-Specific Query Builders
The QueryBuilder is now split into two layers:
AbstractBuilder
Contains all shared SQL logic:
- SELECT, WHERE, JOIN
- GROUP BY, HAVING, ORDER BY
- LIMIT, OFFSET
- binding system
- SQL composition
Per-Driver Builders (e.g., MySQL, PostgreSQL)
Override only what is truly different:
- syntax differences
- identifier quoting
- RETURNING clauses
- last inserted ID behavior
- Postgres vs MySQL parameterization nuances
This dramatically simplifies maintenance and allows the ORM to support new databases easily.
5. ActiveQuery
ActiveQuery is the central point of the ORM:
- receives the QueryBuilder
- executes the SQL through QueryExecutor
- hydrates results with ModelHydrator
- returns model instances, collections, or primitives
Its API mirrors the simplicity of modern ORMs but keeps everything explicit and controlled.
6. Hydration Layer
ModelHydrator takes care of:
- instantiating model objects
- assigning attributes safely
- handling single results vs collections
In the future, this layer will also handle:
- typed model properties
- value objects
- domain transformations
Conclusion
This architecture brings my ORM very close to the clarity of Doctrine’s design while preserving the approachability of Active Record.
It is driver-aware, predictable, cleanly separated, and ready for future features like typed fields and full domain-driven modeling.
This work represents an important evolution toward a modern, stable, framework-quality ORM written entirely in PHP.
GitHub repo GitHub

Top comments (0)