sajad torkamani

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

StateDescription
NEWAn instance with no persistent identity, is not yet associated with an EntityManager and a UnitOfWork. Entities created with the new operator enter this state.
MANAGEDAn instance with a persistent identity that’s associated with an EntityManager and whose persistence is managed.
DETACHEDAn instance with a persistent identity is no longer associated with an EntityManager and a UnitOfWork (how do entities reach this state?)
REMOVEDAn 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

Tagged: Doctrine