I was thinking about some entities I need, when I came across a very interesting concept: Immutable Objects.
The Immutable Objects are very handy to manage the so called Value Objects.
In the Domain Driven Design (DDD) properties of an Entity
can have two different values: other Entities (Order->getProduct()
– here Product
is an Entity
and its properties can change) or simple values (Product->getPrice()
).
Price
, in our example, is a simple value, not conceptually an Entity
. Until now, it probably is a simple property in your Entities (as it was in mine!).
But, in this second case, the Value Objects play their parts: they represent those values in an immutable state.
In fact, if you reflect about the data Price
, you realize that a Price
is something like “100 Eur”. If you put your attention on the value, you can recognize more than one smaller parts in it: a numeric value – 100 – and a string value – Eur.
A Price
is a Price
only if we indicate both the numeric part of it (100) and the string part of it (Eur): If we indicate only one of the two, it is not a Price
because we don’t know how much money does it represents or in which currency the price is expressed.
So we need both the “amount” and the “currency” to have a real value. Think it as a “composite value”.
And the important thing to consider to understand what Immutable stands for: if in the Price
we change the amount, we have a different price. If in the Price
we change the currency, we have, again, a different price.
But if in a Product
we change the Price
, we don’t have a different Product
but the same one with only a different Price
.
What an Immutable Object is?
Using some words written by others:
A value object is a class (usually small, with just a few properties, and frequently used), that represents a value which has no identity separate from its properties. If any one of its properties change, it is no longer the same value.
An example of a value object is an address. If you change any part of an address, it becomes a different address. Another example would be a price – it might have properties for currency, amount, and payment frequency, but changing any of those would yield a new price.
As such, value objects are immutable – their properties cannot change ( it is normal to use getters but not setters for the properties of value objects). If two value objects have exactly the same properties, they can be regarded as equal.
Value objects can have methods, but the behaviour of a method doesn’t ever change the object’s state. If a method needs to change a property, it will return a brand new instance of the value object instead of modifying the existing one. Methods on a value object can also return other types of value of course (eg. a boolean return value would be typical for a validate method).
So, if a Product
with a different Price
is ever the same Product
(read: THE SAME Entity
Object, only with a different Price
. PHP will recognize the different Price
as a modification of the original Product
PHP Object), then a different Price
is something completely different from the previous Price
(read: it is A DIFFERENT Value Object and for PHP the two Prices are two different and separate objects).
Those behaviors are obtained writing the code of the two kind of classes (Value Object or Entity
Object) in different ways and implementing methods with different behaviors when we interact with the Objects.
So, knowing the conceptual differences between a pure Entity
Object class and a Value Object class permits us to write more solid code and better organize our business logic.
A simple example of an Immutable Object
So, as you can see from the example, this small class respect all the constraints of the definition of “Immutable Object”:
- Represents a value which has no identity separate from its properties;
- Its values can be passed only through the
__construct()
and so it hasn’t setter methods ($this->__set()
is even overridden and with no body to prevent accidental modification to the properties); - It returns a brand new instance of
Email
if a change is required.
And it is also really small as class!
How to manage Value Objects using Doctrine
Starting since the version 2.5, Doctrine supports the use of Value Objects, calling them Embeddables.
Embeddables are classes which are not entities themself, but are embedded in entities and can also be queried in DQL. You’ll mostly want to use them to reduce duplication or separating concerns. Value objects such as date range or address are the primary use case for this feature. Embeddables can only contain properties with basic @Column mapping.
This is a simple example of an AddressEmbeddable
implemented in a dummy User Entity
:
<?php
/** @Entity */
class User
{
/** @Embedded(class = "Address") */
private $address;
}
/** @Embeddable */
class Address
{
/** @Column(type = "string") */
private $street;
/** @Column(type = "string") */
private $postalCode;
/** @Column(type = "string") */
private $city;
/** @Column(type = "string") */
private $country;
}
The advantages
Before Embeddable, we could only do two things to manage a Value Object (as the AddressEmbeddable
is):
- Create an
Entity
Address
and use the relationOneToOne
orManyToOne
/OneToMany
(OneAddress
for oneEntity
or, alternatively, oneEntity
with manyAddresses
) to associate the two values; - Create the required fields directly as property of the
Entity
.
No other ways. But those two ones have some drawbacks:
The first alternative, create a new Entity
, means a new table is created in the database (to contain Addresses
), so the resulting schema is something like this:
|---------------------| |--------------------------------------------------|
| TABLE USERS | | TABLE ADDRESSES |
|---------------------| |--------------------------------------------------|
| id | name | Address | | id | street | postal_code | city | country |
|---------------------| |--------------------------------------------------|
| 1 | John | 1 | | 1 | Via di Qui | 12345 | Naples | Italy |
|---------------------| |--------------------------------------------------|
Two separate tables linked each other by a foreign key. Each query needs a JOIN
to get in the same resultset both the User Entity’s data and the email.
The second alternative, create the required fields in each Entity
, on the other hand, is a solution that doesn’t permit to perform some checks, for example about the validity of passed data (see the example above of the Email
: when instantiated, the Email (Value) Object checks that the passed data are really an e-mail).
If you’d like to do this type of checks, you need to repeat the same code in more Entities, and, if you make a change, you have to update the code in more places (not speaking about unit tests!). Not so useful, not so practical, not so intelligent, not so easy.
Using an Embeddable object, instead, permits you to take advantage of only the benefits of the two methods.
Embedding the AddressEmbeddable
in your Entities, will produce a table like this:
|-----------------------------------------------------------------------------------|
| TABLE USERS |
|-----------------------------------------------------------------------------------|
| id | name | address_street | address_postal_code | address_city | address_country |
|-----------------------------------------------------------------------------------|
| 1 | John | Via di Qui | 12345 | Naples | Italy |
|-----------------------------------------------------------------------------------|
Very useful!
NOTE: The default behavior is to call the column [embeddale]_[property]
([address]_[street]
), but you can change it.
The last essential question: Which is the best way to represent a domain: an Entity
or an Embeddable/Value Object?
So, here we come to the very complex question: what kind of class should have I to choose when dealing with such a decision?
Also if the question seems complex, the answer is really simple: it depends
A value object is a class (usually small, with just a few properties, and frequently used), that represents a value which has no identity separate from its properties […]
So, an Address
without the postal code has no sense, but a Company
without an Address
has.
A Price
has no sense without a Currency
, but a Product
without a Price
has.
So, use Value Objects/Embeddable each time you have to represent a value composed of more than one information and only the sum of these information has a sense and “creates” the meaning of the value. You can also use Embeddables in other Embeddables, for example you can use a PostalCodeEmbeddable inside the AddressEmbeddable
(not tested, but I think it’ll work fine).
Each time you’ll find those kind of objects, you’ll know they cannot be modified in any way and that, if you edit them, you have a new different Object, not the same edited.
The problem: and if, for example, a Customer
can have more than one Address
(for example, one time it chose to ship to Italy and the second one it chose to ship to UK)?
The problem arises when the Embeddable is melted into the Entity
table: only one Address
is allowed.
The Real Astonishing Truth: Entities and Value Objects/Embeddable can be used together!
Yes, it is possible to use together Entities and Embeddables.
So, if a Customer
can have more than one Address
, and as you can use Embeddables/Value Objects together with Entities, you’ll have an AddressEmbeddable
that represents the current state of the value and an AddressEntity
that represents the collection of those values.
Something like this:
<?php
namespace AppBundle\Entity;
/** @Entity */
class User
{
/**
* @ORM\OneToMany(targetEntity="Address", mappedBy="ofUser")
*/
private $addresses;
}
<?php
namespace AppBundle\Entity;
/** @Entity */
class Address
{
/**
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* @ORM\ManyToOne(targetEntity="User", inversedBy="addresses")
* @ORM\JoinColumn(name="of_user", referencedColumnName="id")
*/
private $ofUser;
/**
* @Embedded(class = "AddressEmbeddable")
*/
private $address;
}
<?php
namespace AppBundle\Embeddable;
/** @Embeddable */
class AddressEmbeddable
{
/** @Column(type = "string") */
private $street;
/** @Column(type = "string") */
private $postalCode;
/** @Column(type = "string") */
private $city;
/** @Column(type = "string") */
private $country;
}
This way you’ll have again a table dedicated to addresses, but each Address Entity
will return an Immutable Object, one that returns a new object if edited (IS UP TO YOU TO IMPLEMENT THE AddressEmbeddable->setNewAddress()
method in such a way it returns a brand new copy of the AddressEmbeddable
class. See the Email
example above to better understand this).
Someone terms “Fake” this kind of Address Entity that embeds an EmbeddableObject because its unique purpose is to manage an Embeddable.
If seen only in the context of Doctrine, this could also be considered true, but, if considered in the bigger picture of the Domain Driven Design, this is the only correct way of doing things.
The real purpose of a Value Object is to represent an Immutable Value. The fact that Doctrine calls them Embeddables and treats them in some ways persisting them into the database is something that does not interfere with the real purpose.
So, the Entity
is a container of values and a value formed by more values has to be a Value Object instead of a simple property.
Using Doctrine, those Value Objects are called Embeddables and are persisted creating columns directly in the table of the Entity
that embeds.
But the concept of Immutability applied to Value Objects still is valid and has to be used, independently of how the ORMs manage their persisting in the databases.
Some links to go deeper
- http://stackoverflow.com/questions/31183954/why-to-use-getter-setters-for-doctrine2-classes
- http://blog.sznapka.pl/immutable-value-objects-in-php/
- http://welcometothebundle.com/price-more-then-money-php-library/
About Doctrine and Embeddable/Value Objects:
- http://welcometothebundle.com/persist-the-money-doctrine-value-object/
- http://doctrine-orm.readthedocs.org/en/latest/tutorials/embeddables.html
- http://rosstuck.com/persisting-value-objects-in-doctrine/
- http://russellscottwalker.blogspot.it/2013/11/entities-vs-value-objects-and-doctrine-2.html
- http://stackoverflow.com/questions/7375334/immutable-collections-in-doctrine-2
Remember to “Make. Ideas. Happen.”.
I wish you flocking users, see you soon!
L'articolo Value Objects, Immutable objects and Doctrine Embeddables proviene da ÐΞV Experiences by Serendipity HQ.
Top comments (0)