1. Doctrine DBAL
DBAL gives low-level SQL access.
You write SQL or use the QueryBuilder manually.
You get arrays, not entities.
$user = QueryBuilder()
->select('*')
->from('users')
->where('id = :id')
->setParameter('id', $id)
->executeQuery()
->fetchAssociative();
Return type: associative array of raw database values
Pros: Fast, direct SQL, no entity overhead
Cons: No hydration (Turning SQL result sets into entity objects)
When you fetch rows using DBAL (for example with fetchAssociative()), you get raw values exactly as they come from the database driver.
That means:
- Numeric fields are returned as strings (for example
"123", not123) - Boolean fields are returned as
"0"or"1" - Datetime fields are returned as strings like
"2025-10-22 14:30:00" - JSON fields are returned as strings containing JSON text
- If you use Field Encryption extension, the field value is encrypted
- There is no automatic conversion to PHP objects,
DateTimeinstances, or other types
2. Doctrine ORM
ORM works with entities (classes mapped to database tables).
You get objects, not arrays.
// User table is from the main database so you can use EntityManager() without argument
$user = EntityManager()->getRepository(Db\Entity\User::class)->find($userId);
// Or you can simply use GetRepository()
$user = GetRepository(Db\Entity\User::class)->find($userId);
// The user table is a special table, you can also use UserRepository()
$user = UserRepository()->find($userId);
// You can also select a record by criteria
$user = UserRepository()->findOneBy(['email' => 'test@example.com']); // 'email' is property name, not column name
Return type: User|null
Pros: Automatic hydration, lifecycle callbacks
Cons: Slower, heavier, more memory usage
When using ORM, fields are mapped to PHP types via metadata. A conversion layer exists, for example, Doctrine knows that createdAt is a datetime type.
#[Column(type: 'datetime')]
private ?DateTimeInterface $createdAt;
So when you do:
$user = UserRepository()->find(1);
$createAt = $user->getCreatedAt();
Doctrine automatically converts "2025-10-22 14:30:00" into a DateTime object.
If you use Field Encryption, the field value is decrypted.
This is called hydration or type conversion, handled by Doctrine’s Type system.
How to know the entity class name of a table (for getting the repository of the table) and the field property names? See Database, Table and Field Variable Names.