Doctrine is certainly the most popular ORM (Object Relational Mapper) for PHP. It might seem a good choice for a new project, unfortunately all it’s advantages come at a price. Obviously you wont find the limitations highlighted in bold on the first page of the documentation, and it may take a lot effort to switch back once you start developing with Doctrine and the problems start showing.
The biggest issue is performance when working with large amounts of results. Doctrine manages objects representing database rows. By ‘management’ it means that all data received form the database is stored in memory, every row is represented by a single object, all references to other objects are also maintained. This approach gives some data consistency, ensuring that a row pulled by different queries in multiple places remains the same object, unfortunately it also costs a lot of memory. If processing large amounts of data (large imports / exports / generating data feeds etc) is what your application will do then using Doctrine (and likely many other ORMs) will result in very high memory requirements and really poor overall performance. If the batch data processing that you want to do does not involve handling a network of complex table relationships, then almost certainly using Doctrine is not the right decision.
Another case is when the data objects that you want to work with aren’t exactly represented by database rows. When persistence gets a bit more complex and you don’t want to store the objects that your code interacts with, any ORM will fail. It is a rare case but I have hit this limitation at least once on a scenario where only a delta of changes is persisted. More commonly you might not need a full object with all fields representing the table row – Doctrine will still need to instantiate objects for your results even when all you need is a single column.
Having complex table joins is also not the place where Doctrine excels – you can’t have joins on multiple keys, you can’t even have joins on non primary keys – this is something I was really surprised to see.
DQL – Doctrine Query Language is an SQL-like language which allows you to build queries. DQL works with multiple storage engines such a MySQL, Sqlite or PostgreSQL. This means that the language and schema building tools are by default limited to the set of features supported by all of the engines. If you have been using MySQL so far you will find that there is no ENUM or TINYINT support by default. Want to do a join on a subquery? – nope, not with DQL. There seem to be quite a few limitations regarding query building with DQL. What you still can do is switch to native SQL queries and fetch your results with these. Unlike DQL Using native SQL queries means that you are binding yourself to the storage engine. The often mentioned argument that using Doctrine (without native queries) will let you switch the storage type if needed makes little sense to me – I have never yet needed to switch the database type on an existing project.
So what are the alternatives?
Doctrine ORM is built on top of a DBAL (Database Abstraction Layer) which will allow you to handle most situations where the ORM doesn’t do well. You can always do a few raw queries in the spots where it’s a better choice – this isn’t very far from going with PDO or mysqli functions, but the available DBAL query builder should make things a bit easier.
Since entities are pure PHP objects with some getters and setters it may be tempting to use them in some cases for handling data fetched through the DBAL.One thing to watch out for in such case is that they are not managed by default. An entity instance created outside Doctrine will be treated as a ‘new’ object and inserted if persisted using the ORM, this may result in inserts in places where you would expect an update. Basically some separation between data accessed through the DBAL and Doctrine needs to be maintained – these are different systems.