2019/07/05/value-objects-doctrine-and-symfony-forms
Opened this issue · 16 comments
Please comment below!
Comments will automatically be published on the blog, too.
Hi @romaricdrigon ,
How are we going to do with latitude and longitude? With ValueObject? With Embeddable?
Thanks
Hello @seb-jean, a value object, stored as a Doctrine embeddable, is usually a very good solution.
Especially if you also want to store altitude, or a precision, etc.
I use ValueObject and Doctrine Embeddable ?
That is to say ? Can you give an example please?
Sure:
/** @ORM\Embeddable */
class Coords
{
/** @ORM\Column(type="string") */
private $latitude;
/** @ORM\Column(type="string") */
private $longitude;
// Below and after I will just put the methods signature
public function __construct(string $latitude, string $longitude)
// As a bonus, you can add more methods to your value object
// For instance, converting to another format (for instance, CH1903 which is popular here)
public function getInCh1903(): array
}
/** @ORM\Entity */
class Place
{
/** @ORM\Embedded(class="Coords") */
private $coords;
// ...
public function setCoords(Coords $coords): void
public function getCoords(): Coords;
}
Thank you.
It may be off-topic, but I saw that latitude and longitude could be stored in a POINT datatype (https://dev.mysql.com/doc/refman/8.0/en/gis-class-point.html)
Using a Value Object is Required Or Embeddable?
Having a value object is something different than how data is stored. Value object are exclusively PHP-side, and should not be concerned by how Doctrine will handle them.
So value object !== embeddable, embeddable are merely a way to store a value object.
In your case, sure, you can use a POINT
MySQL side. Then you will need a custom Doctrine mapping type (https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/cookbook/custom-mapping-types.html), and you can transform it to a PHP object. Which can be a value object.
Thanks @romaricdrigon
Hello! Good article, thank you.
Do you know of the performance impact of using Embeddable with Doctrine ? I suppose that created and hydrating those value objects can create some overhead, especially when querying and displaying a lot of entities in a list. I'm wondering the same question for json serialized data in Doctrine.
Hello, thank you.
I never had performance issues with Embeddables, so I never quite dig into that subject. I would presume Embeddables hydratation to be pretty lightweight, entity hydratation becomes really expensive and complex because entities are tracked.
Regarding memory usage, PHP objects are lighter than arrays (in PHP7), so it would likely be better than JSON serialized data which is unpacked to associative arrays.
Hello :)
I feel that this covers only part of lifecycle of VO. What about optional Value object? How you implement it?
When you have BuildingNumber|null
situation? To advance problem, what you do with strict type and return type? In DDD, you should move with that approach. With embeddable, you can't. You are forced to have buildingNumber(): BuildingNumber
and BuildingNumber::getValue(): ?string
as solution. What do you think about this?
How you consider this problem with mentioned points First thoughts
, Embeddables
and As Doctrine entities
☮️ 👋
"As Doctrine entities" was a bit confusing to me. It's possibly a combination of me not being a native English speaker and not being so familiar with the terms used here. I have a few questions.
Here's my setup
I have an entity named User, and it must be allowed to own multiple addresses. I create an entity named Address with a many-to-one relation to the User.
It is tempting to want to store all those in the same address table, so that would be an Address entity for Doctrine.
When you said "it's tempting to..." it sounded like it would be followed by "BUT... don't do it". But in some cases, like mine, it is unavoidable to make Address an entity, right? Or maybe that's not what you were talking about here?
So as long as you never expose an Address ID in your domain code
What does this mean, exactly? Expose the id
of an Address? Domain code?
If a customer edits an address, should his past order address be updated?
I was thinking... maybe I can use an embeddable named Address in the Order entity to store the Address without relations, which would prevent it from changing in case the user makes changes to their addresses, right? The user would still have a collection of Address entities in relation, but the Order would store just an immutable version of the selected Address as an embeddable. I have yet to try this.
I could not find any documentation regarding embeddables in the Symfony Docs. Your examples of integration with Symfony are really helpful for beginners to get the hang of it. The Doctrine documentation on embeddables only goes so far.
Hello all,
Sorry for the long answer, I have been quite busy past weeks. Now I'm catching on.
About nullable value objects
@timiTao you raise a good point.
Doctrine types and entities are good options for this scenario. Personally I think I would pick entities.
With embeddable, you can hack your way around, but it feels suboptimal:
public function getPrice(): ?Money
{
return $this->price->getValue() ? $this->price : null;
}
Though it can be quite adapted if you want to use null object pattern:
public function getPrice(): MoneyInterface
{
return $this->price->getValue() ? $this->price : new NullMoney();
}
About Doctrine entities
@o-alquimista thank you for your feedback, I will consider rephrasing that paragraph.
My point is that while Address
is not an entity as per Domain-Driven Design recommendations, it can be a Doctrine entity. Doctrine use a slightly different terminology.
In your scenario, using a Doctrine entity feels right.
You don't want to expose Address::id
, as it does not quite make any sense domain-wise. If you want to compare than 2 addresses are "equal", you have to compare every of their fields, so you may want to implement a method for that.
Thanks. So, in my example, Address is a Doctrine entity, but it is also a Value Object. If you want to compare two Address instances, you have to leave the id
out, since it's always unique for each of them (it gives an "identity", which violates the Value Object pattern).
Exactly!
I have another question about Embeddables. I couldn't find the answer anywhere else, so I have to ask here. It may help others with the same question.
Can I put multiple embeddables in something like an ArrayCollection?
For example, I have an Order
entity, and I need to store all the products involved in that order without making relations (because products can be deleted or modified).
-
Since the number of products involved in an order is indefinite, I have to be able to add any number of embeddables I need. Is an ArrayCollection a valid solution for this?
-
I don't think I will need to store the
id
of each product (or anything that gives them unique identities). When the user decides to consult their past orders, I want them to see at least a picture, the name, the quantity and the price paid for each product.
Now that I come to think more about it, I no longer believe that what I asked is possible. Embeddables map to specific columns, and these columns must exist on the database. It won't add more columns dynamically.
I will probably have to create an OrderItem
entity with a ManyToOne relation to Order
and use it to keep product information. It will be built with the data from the actual products involved in the purchase.
If you have any better ideas to suggest, I appreciate it.
Hello,
In short, no, embeddables are single objects.
You exactly got the reason why: embeddables properties are flattened to columns in the entity table. So the schema tool can't handle a potentially unlimited number of embeddables.
An OrderItem
entity sounds indeed as the way to go indeed :)