Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2019/07/05/value-objects-doctrine-and-symfony-forms #6

Open
romaricdrigon opened this issue Jul 5, 2019 · 16 comments
Open

2019/07/05/value-objects-doctrine-and-symfony-forms #6

romaricdrigon opened this issue Jul 5, 2019 · 16 comments

Comments

@romaricdrigon
Copy link
Owner

Please comment below!
Comments will automatically be published on the blog, too.

@seb-jean
Copy link

seb-jean commented Jul 8, 2019

Hi @romaricdrigon ,
How are we going to do with latitude and longitude? With ValueObject? With Embeddable?
Thanks

@romaricdrigon
Copy link
Owner Author

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.

@seb-jean
Copy link

seb-jean commented Jul 8, 2019

I use ValueObject and Doctrine Embeddable ?
That is to say ? Can you give an example please?

@romaricdrigon
Copy link
Owner Author

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;
}

@seb-jean
Copy link

seb-jean commented Jul 8, 2019

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?

@romaricdrigon
Copy link
Owner Author

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.

@seb-jean
Copy link

seb-jean commented Jul 8, 2019

Thanks @romaricdrigon

@danakil
Copy link

danakil commented Jul 10, 2019

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.

@romaricdrigon
Copy link
Owner Author

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.

@timiTao
Copy link

timiTao commented Jan 13, 2020

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

☮️ 👋

@o-alquimista
Copy link

"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.

@romaricdrigon
Copy link
Owner Author

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.

@o-alquimista
Copy link

o-alquimista commented Feb 20, 2020

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).

@romaricdrigon
Copy link
Owner Author

Exactly!

@o-alquimista
Copy link

o-alquimista commented Feb 24, 2020

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.

@romaricdrigon
Copy link
Owner Author

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 :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants