ENUMs, User Preferences, and the MySQL SET Datatype
Scrollable Tables with Floating Header using CSS
Using PHP pspell Spell Check Functions with a Custom Dictionary
Visualising Website Performance with Flame Graphs
Native Linux Space Warfare: Freespace 2
Symfony 2 Crash Course

Principles of the Doctrine ORM in Symfony

Friday, 1 October 21, 9:12 am
I've been using Symfony professionally for a year now, and it's a framework which I really like. However some of the details of the usage of the Doctrine ORM were a bit unclear for me, so I took a moment to read through the documentation more thoroughly and I'm summarising the key principles here.

Managed Entities and the Identity Map

One thing to remember is that, as you work with entities retrieved from and saved into your database, Doctrine will maintain its own separate collection of entities. When you are handling a web request and fetch an entity from the database for the first time, Doctrine will add that entity to its own collection. The next time you ask for the same entity in the same request, Doctrine will not fetch a new copy but instead return a reference to the previously fetched entity. This internal collection is called the Identity Map, because it contains entities which are mapped to those in the database by their "identity" (i.e. their primary key). Entities that have been retrieved from the database and are now stored in the identity map are said to be managed entities.

Saving to the Database: flush and persist

So Doctrine manages the instances of entities which have been fetched from the database along with all the changes made to them. Changes are not saved to the database until you explicitly tell Doctrine to do so, by calling the flush() method on the entity manager.

It is important to realise that new entities which you create are not managed, because they have not been fetched from the database and they do not have an identity mapping with entities in the database. Therefore you must tell Doctrine that you want any new entity to be saved to the database, which you do by calling persist($newEntity) on the entity manager.

By default, Doctrine is set up to use the Deferred Implicit Policy for committing changes to the database. With this setting, changes to all entities in the identity map (i.e. changes to managed entities) will be saved when you call flush(). Note that only changed entities will be saved - when you call flush(), Doctrine will look at all managed entities and compare the value of each property with the original to determine which actually need to be saved.

Alternatively, you could use the Deferred Explicit Policy, in which case you must explicitly tell Doctrine which entities are to be saved when flush() is called, by calling persist($entity) on the entity manager. This could save a bit of processing time if you have retrieved a lot of entities but have not updated many of them, as Doctrine only needs to check those which have been explicitly persisted for changes.

Clearing the Identity Map

You can tell Doctrine to drop its internal collection by calling the entity manager's clear() method. If you do not pass any argument to this method, everything will be dropped. Alternatively, you can pass the class name of just one specific entity in order to just drop entities of that type.

This could be useful when you are working with large numbers of entities and wish to recover resources after you've finished with some of them, or if you've retrieved a lot of entities which do not need to be saved and so do not need to be checked for changes on flush().

Implicit Database Transactions

When you're using one of the deferred update policies (either the Deferred Implicit Policy or the Deferred Explicit Policy), all database operations are queued up and performed at once when you call flush(). This means that Doctrine can and will package them all up into a single transaction itself, so there is normally no need for the developer to concern themselves with explicitly starting and committing transactions. You do need to be aware though that Doctrine will automatically rollback the transaction if there is an exception, so if you have made changes to a lot of entities and there is an exception when you call flush(), none of those changes will be saved. However, the PHP entities themselves are not rolled back, and will still have the changes that were present when flush() was called. They are however no longer managed by the entity manager - they are said to be detached. Also note that the entity manager will be closed after such an exception, and you will need to recreate it if you want to use it again in that request.

Cascading Operations to Child Entities

Another thing worth mentioning here is the cascade notation that can be used when defining relationships between entities in your code (whether you do this via annotations, YAML or XML). Available values are remove, persist, merge, detach, refresh and all. When you have an entity with a child entity or a set of child entities (i.e. another entity or collection of entities as a property), you can use cascade to tell Doctrine which operations, if any, should implicitly be performed on the parent and/or child.

Say for instance you have a Customer entity which contains a collection of Address entities, you can set cascade: remove on the relationship on the parent to have all the child addresses deleted when you delete the customer. Without this, you would have to first fetch all the addresses and delete them individually before you can delete the customer. Use this feature carefully though - in this case, you probably would not want to set cascade: remove on the Address relationship to Customer, as that would delete the customer when all the addresses are deleted.

The persist cascade notation is useful when adding new items to a child collection. When you have cascade: persist set on the relationship from an entity to a child collection, you can add new elements to the collection of a managed instance of that entity and they will be automatically persisted to the database on flush(). Without this, you would need to call persist() on each new child element.

Manuel

8:34 am, Tuesday, 19 December 23

Thanks for this. It's a very well written summary on Doctrine and it actually cleared a few concepts I couldn't find (explicitly) in the documentation.

Please enter your comment in the box below. Comments will be moderated before going live. Thanks for your feedback!

Cancel Post

/xkcd/ Arizona Chess