Comparison with Doctrine
Objectiphy v Doctrine
Doctrine is an amazing project, with an extensive set of features, and, if used correctly, is suitable for large-scale enterprise applications. It is very efficient when configured and used properly, and can handle almost any requirement you could expect of an ORM. I would never say that you should not use Doctrine - it is great, by all means, use it - I have great respect and admiration for the developers of Doctrine and their commitment to best practice. Nevertheless, as with any piece of software, it does have its downsides...
Disclaimer: I am not an expert on Doctrine, although I have used it on some projects, and taken inspiration from it when writing Objectiphy. It is quite likely that some things that I thought were not possible in Doctrine are possible, or that I have misunderstood its capabilities in some way, or that certain issues have been resolved in later releases, but the following information is given in good faith based on my current understanding and past experience.
Learning Curve
Doctrine is not easy to learn. It is a massive project, with many features, and requires you to understand a number of fundamental concepts that underlie the system, but that are not always entirely intuitive (entity manager, unit of work, database abstraction layer, repositories etc.). In contrast, Objectiphy tries to keep things as simple as possible - everything is done via repositories, which can load and save data in a simple and intuitive way.
Intuitiveness
This is a somewhat subjective topic, but Objectiphy tries to follow the principle of least astonishment - it does what you might reasonably expect it to do. This does not always seem to be the case with Doctrine (at least to me!), and although there are often good reasons for that, those reasons are not always immediately obvious.
For example, if you load an entity using a repository, then make some changes to it, you might expect to be able to save it using that same repository - but in Doctrine you can't - you have to use the entity manager to save it. This means you must either inject the entity manager into your services and use it to retrieve the repository (suspiciously similar to the service locator anti-pattern), inject two separate objects into your service (one to load from and one to save to), or write your own plumbing code, perhaps a base repository class you always extend from, which gives access to the entity manager.
Doctrine's Entity Manager keeps track of all changes to all entities under its purview, and saves updates to them all (the 'unit of work') when you call the flush
method. To tell it to manage a new entity (ie. to insert) you must call the persist
method first, then flush
. This often confuses developers, because it intuitively seems as though a method called persist
should persist the data - but it doesn't - only flush
actually persists the data. Developers therefore often call both persist
and flush
every time they want to save an entity - even if they are only updating an existing entity (in which case, persist
is unnecessary).
Having an entity manager which is independent of repositories can lead to code that is not as intuitive as it could be. For example, consider a method like this:
It seems as though this method loads some contacts, sets a property on them, and then just forgets about them - somebody reviewing this code might conclude that the method is completely pointless, because there is no indication that the change made will be saved to the database. However, another method in the same class - or even in a different class - could call the flush
method on the entity manager (not the repository!), and those changes would then be saved. There are some advantages to using the 'unit of work' design pattern that Doctrine uses, but for very common and simple use cases, the added complexity and opaqueness of how it is working may end up making it not worthwhile.
In Objectiphy, you save an entity by calling saveEntity
(or multiple entities by calling saveEntities
) - whether inserting or updating, and they are saved to the database at that point (but you can still separately manage database transactions if you want to be able to roll back multiple saves in case of a problem). This means that you must have the entity on hand when you come to save it - which makes the code easier to follow, as you have visibility of what is happening to the entity, unlike the example above.
Also, DQL and the Doctrine query builder appear to have the aim of following similar syntax to SQL, but at times diverge from that for no apparent reason (eg. using WITH to specify join criteria - I'm sure there must be a reason, but it is not apparent!). For reasons of simplicity and intuitiveness for those familiar with SQL, Objectiphy does not support an extensive hybrid query language like DQL - it simply uses delimited queries with string replacement to allow class and property names to be swapped out with table and column names in SQL, thus not requiring a new query language. Also, the query builder follows SQL syntax very closely, but in an object oriented way (although it does not support sub-queries like Doctrine does).
QueryBuilder Syntax
For more complex nested queries, the object-oriented query builder in Doctrine uses some fairly ugly syntax which makes it unwieldy (and the project recommends you use DQL instead, as it is easier to read). Objectiphy's query builder uses much more intuitive syntax, which closely resembles SQL, and is much easier to read (but does not support a separate string-based query language like DQL).
Here are examples of equivalent queries in Doctrine and Objectiphy to illustrate. Both queries shown below would yield SQL that looks something like this (all 3 of the following snippets are functionally equivalent, albeit not identical):
Using Doctrine:
Using Objectiphy:
Consistency
Following on from intuitiveness, there are times when Doctrine does not behave in a consistent way, and again, although there may be good reasons for this, those reasons are not always obvious.
For example, if you define a one-to-one relationship between two entities, and eager load the child, both Doctrine and Objectiphy will use a left join by default - thus, if the child does not exist, it will return null. If you change nothing about that relationship except to make it lazy load instead of eager load, Objectiphy will behave in exactly the same way when the child is loaded (ie. it will return null) - but Doctrine will throw an exception because the child entity does not exist. The principle of least astonishment applies again here because although we expect a change in behaviour when switching from eager to lazy loading, we do not expect to get different results.
Flexibility
In some ways, Doctrine is quite opinionated about how your entities and databases should be structured. This is not necessarily a bad thing - using Doctrine encourages you to adopt good practices in your database design - however, there are times when you need to be able to integrate with an existing legacy database, and you don't have the luxury of being able to change its structure. This can mean that you have to use painful workarounds, or spend a long time trying to find ways of getting it to work.
For example, in Doctrine you cannot join to another table using a non-primary key, while Objectiphy does not have this limitation. Also, if you want to grab a single scalar value from another table without creating a child entity, you cannot specify the join information for that in Doctrine annotations - you would either need to create a database view, write custom DQL and populate the field in code, or create a 'dummy' child object and use getters and setters to make it appear as though the value exists on the parent entity - whereas Objectiphy will allow you to define a scalar join. In Objectiphy, you can map several tables to a single entity by overriding the mapping information, but this is not currently possible in Doctrine.
Speed
If you are very concerned that your ORM responds as quickly as possible, and need to fine-tune its efficiency to deal with heavy use, you might find that you can squeeze more speed out of Doctrine than Objectiphy. Doctrine has been carefully honed over the years to maximise performance (when correctly configured and used), with extensive caching capabilities, whilst Objectiphy has somewhat limited caching abilities (partly for security reasons, to prevent data and query criteria from being persisted and potentially discovered in the cache). Nevertheless, for the vast majority of use cases, Objectiphy performs very well, and can handle complex and recursive hierarchies of objects without a problem. Note that Objectiphy performs better when not in development mode, and there are various things you can do to improve performance, depending on your requirements.
When you should/shouldn't use Objectiphy
Objectiphy is a good choice for anyone who is new to ORMs and doesn't want to spend a long time learning about them, or who feels that Doctrine is too unwieldy for their requirements (using a sledgehammer to crack a nut), or for anyone trying to modernise a legacy codebase and wants to integrate with a legacy database structure, or for anyone who has found using Doctrine painful and wants something simpler.
You might want to use a different solution (like Doctrine!) if you are building a large system with complicated requirements that needs to handle a lot of traffic with a high level of performance, and are willing and able to spend time on getting it set up correctly. And of course, there are lots of features in Doctrine that are not available in Objectiphy such as an event system, and the ability to manage your database structure directly (although you could use both Doctrine and Objectiphy if you want).
Last updated