Understanding the concepts behind Doctrine
Doctrine ORM implements Data Mapper and Unit of Work design patterns.
The Data Mapper is a layer designed to synchronize data stored in database with their related objects of the domain layer. In other words, it does the following:
Inserts and updates rows in the database from data held by object properties
Deletes rows in the database when related entities are marked for deletion
Hydrates in-memory objects with data retrieved from the database
In the Doctrine terminology, a Data Mapper is called an
Entity Manager. Entities are plain old PHP objects of the domain layer.
Thanks to the Entity Manager, they don't have to be aware that they will be stored in a database. In fact, they don't need to be aware of the existence of the Entity Manager itself. This design pattern allows reusing entity classes regardless of the persistence system.
For performance and data consistency, the Entity Manager does not sync entities with the database each time they are modified. The Unit of Work design pattern is used to keep the states of objects managed by the Data Mapper. Database synchronization happens only when requested by a call to the flush()
method of the Entity Manager and is done in a transaction (if something goes wrong while synchronizing entities to the database, the database will be rolled back to its state prior to the synchronization attempt).
Imagine an entity with a public $name
property. Imagine the following code being executed:
Thanks to the implementation of the Unit of Work design pattern, only one SQL query similar to the following will be issued by Doctrine:
Note
The query is similar because, for performance reasons, Doctrine uses prepared statements.
We will finish the theory part with a short overview of the Entity Manager methods and their related entity states.
The following is an extract of a class diagram representing an entity and its Entity Manager:
The find()
method hydrates and returns an entity of the type passed in the first parameter having the second parameter as an identifier. Data is retrieved from the database through a SELECT
query. The state of this returned entity is managed. It means that when the flush()
method is called, changes made to it will be synced to the database. The find()
method is a convenience method that internally uses an entity repository
to retrieve data from the database and hydrate the entity. The state of the managed entities can be changed to detached by calling the detach()
method. Modifications made to the detached entity will not be synced to the database (even when the flush()
method is called) until its state is set back to managed with a call to the merge()
method.
Note
The start of Chapter 3, Associations, will be dedicated to entity repositories.
The persist()
method tells Doctrine to set the state of the entity passed in parameter as managed. This is only useful for entities that have not been synced at least one time to the database (the default state of a newly created object is new) because entities hydrated from existing data automatically have the managed state.
The remove()
method sets the state of the passed in entity to removed. Data related to this entity will be effectively removed from the database with a DELETE
SQL query the next time the flush()
method is called.
The flush()
method syncs data of entities with managed and removed states to the database. Doctrine will issue INSERT
, UPDATE
, and DELETE
SQL queries for the sync. Before that call, all changes are only in-memory and are never synchronized to the database.
This is abstract for now, but we will understand better how the Entity Manager works with numerous examples throughout the book.