Creating more cohesive Doctrine Entities using Embeddables

Photo by Ben on Unsplash

Doctrine Embeddable is really handy. Using it we can create better and more cohesive Domain Model.

What is a Doctrine Embeddable?

Embeddable let’s you aggregate some properties, that are linked together in some way, into one cohesive class. You may say — well, I can create another entity and create a relation, what is the difference? The difference is, that an Embeddable does not have identity, so it is not an entity. It’s kind of a logical wrapper around properties that together create some concept. At the infrastructure layer these properties end up in the same table row as other entity properties. Also, less joins = better world.

Give me some example

Let’s assume you have to create a model of some Offer concept. For the sake of simplicity, this particular Model contains only two properties — Value of the offer, and it’s description. Value is expressed using money. Classically, you would add two fields — currency and amount. But if you implement it directly into an Offer Entity, you will pollute it. And probably Money concept will be used in many places in your system, so you would need to either duplicate the code or create some trait. Both of these ideas are not a good way of solving this problem. Here come Value Objects implemented as Doctrine Embeddables.


Money Value Object

Technical Aspects:

  • Class must be Annotated with @ORM\Embeddable
  • Columns need to be defined
  • On the Entity side, the property must be annotated with @ORM\Embedded(class="Fully\Qualified\Classname", prefix="{you can add table column prefix here}")

Of course, you do not have to use annotations, you can use XML, YAML or anything available you want to register the classes.

Money class is highly cohesive. It’s concerns are well defined. Also, it’s encapsulated and immutable. If you want subtract or add money, it creates a new instance without changing state, which is very safe. If any Entity decides that the amount of money it holds can change, it will have the method of changing it implemented, which will let you replace the value (and check some invariants probably).

Also, implementing money using integer is better than using float. Float types have rounding issues when you operate on them. Using integer, you just hold the value in the lowest face value available in the currency (eg. 1 cent).

Offer implementation

The power of immutability

As you can see above, Offer allows changing offer amount, but it also has information about the initial offer, which is set on the object construction and cannot be changed during the lifetime of an object. Thanks to immutability, we can assign the same Money object into both of these properties without worrying that they will be mutated. Using Immutable Objects will save us RAM (well, in this particular example probably we won’t save much). When one object can be shared between many properties, this is very helpful. Only the properties that had to be mutated in some way will point to another object somewhere else in memory.

Extra: Layer Supertype

As you can see above, Offer class extends Entity class.

Entity class — Layer Supertype

It’s an abstract class that contains code that would need to be duplicated in every Doctrine Entity that is created. It’s a good practive not to duplicate that code. If you will need to implement identity in some class in other way, you will just not extend this one. Remember that kind of a class needs to have @ORM\MappedSuperclass() annotation.