What is Doctrine ORM?
In a nutshell
Doctrine ORM is an object-relational mapper (ORM) for PHP. It uses the Data Mapper pattern to help you focus on your domain logic and to worry about persistence concerns in the underlying relational database (e.g., MySQL or Postgres) only as a secondary concern.
Architecture
The EntityManager
class is the central access point for most of the functionality provided by Doctrine ORM. You use the EntityManager
to manage the persistence of your objects and to query for persistent objects.
Internally, the EntityManager
uses a UnitOfWork
to keep track of all changes to commit in the next flush
.
How Doctrine detects changes
When you fetch an object from the database, Doctrine will keep a copy of all the properties and associations inside its UnitOfWork. When you call EntityManager#flush()
, Doctrine will iterate over the Identity Map and for each object, compare the original property and association values with the values that are currently set on the object.
If any changes are detected, then the object is queued for a SQL Update
operation. Only the fields that actually changed will be updated. This process can cause some performance issues if the size of a UnitOfWork is large because the change computation will take time. You can do various things to optimize the process.
How Doctrine uses the Identity Map pattern to perform optimizations
Doctrine uses the Identity Map pattern to track objects. When you fetch an object from the database, Doctrine will keep a reference to it inside its UnitOfWork
. The entity references are stored in an array that’s two-levels deep, with the keys being the root entity name and the entity ID.
This pattern lets Doctrine perform some optimizations. For example, if you use the EntityManager
to ask for an entity with a specific ID twice, Doctrine will execute only one SELECT
query. In the second EntityManager#find()
call, Doctrine will first check the identity map and see that it already has a reference to the entity with the given ID.
public function testIdentityMap()
{
$objectA = $this->entityManager->find('EntityName', 1);
$objectB = $this->entityManager->find('EntityName', 1);
$this->assertSame($objectA, $objectB)
}
Doctrine ORM packages
Doctrine ORM is divided into three main packages.
The Common package
Contains highly reusable components that have no other dependencies.
The DBAL package
Provides an enhanced database abstraction layer on top of PDO. that bridges most of the differences between different database vendors.
The ORM package
Provides the ORM toolkit to help you map PHP objects into database rows.
What is an Entity?
An entity is a PHP domain object containing persistable properties that are saved into and retrieved from the database by Doctrine’s data mapping capabilities. An entity can be any PHP class so long as it observes various restrictions (e.g., all persistent properties are private / protected, doesn’t contain any final methods, doesn’t implement _clone
, etc.).
Here’s an example entity:
<?php
// src/Product.php
class Product
{
/**
* @var int
*/
private $id;
/**
* @var string
*/
private $name;
}
Entity states
State | Description |
NEW | An instance with no persistent identity, is not yet associated with an EntityManager and a UnitOfWork. Entities created with the new operator enter this state. |
MANAGED | An instance with a persistent identity that’s associated with an EntityManager and whose persistence is managed. |
DETACHED | An instance with a persistent identity is no longer associated with an EntityManager and a UnitOfWork (how do entities reach this state?) |
REMOVED | An instance with a persistent identity, associated with an EntityManager, that’ll be removed from the database on flush / transaction commit. |
Anemic vs rich entities
Anemic entities: Getters and setters
In Anemic entities, you create two kinds of methods to read (getter) and update (setter) the object’s properties. With anemic entities, your entities are behaviour-less and you write all / most of your business logic in service classes. Here’s an example:
<?php
class User
{
private $username;
private $passwordHash;
private $bans;
public function getUsername(): string
{
return $this->username;
}
public function setUsername(string $username): void
{
$this->username = $username;
}
public function getPasswordHash(): string
{
return $this->passwordHash;
}
public function setPasswordHash(string $passwordHash): void
{
$this->passwordHash = $passwordHash;
}
public function getBans(): array
{
return $this->bans;
}
public function addBan(Ban $ban): void
{
$this->bans[] = $ban;
}
}
Rich entities: Mutators and DTOs
In rich entities, you implement entity methods that represent the behavior of your domain. These methods take care of ensuring that the entity’s state is always valid (e.g., by accepting only valid DTOs when updating the entity).
<?php
class User
{
private $banned;
private $username;
private $passwordHash;
private $bans;
public function toNickname(): string
{
return $this->username;
}
public function authenticate(string $password, callable $checkHash): bool
{
return $checkHash($password, $this->passwordHash) && ! $this->hasActiveBans();
}
public function changePassword(string $password, callable $hash): void
{
$this->passwordHash = $hash($password);
}
public function ban(\DateInterval $duration): void
{
assert($duration->invert !== 1);
$this->bans[] = new Ban($this);
}
}
Sources
Thanks for your comment 🙏. Once it's approved, it will appear here.
Leave a comment