Value Objects

Value Objects are a fundamental building block of Domain-Driven Design, and they’re used to model concepts of your Ubiquitous Language in code. A Value Object is not just a thing in your Domain — it measures, quantifies, or describes something. Value Objects can be seen as small, simple objects — such as money or a date range — whose equality is not based on identity, but instead on the content held.

For example, a product price could be modeled using a Value Object. In this case, it’s not representing a thing, but rather a value that allows us to measure how much money a product is worth. The memory footprint for these objects is trivial to determine (calculated by their constituent parts) and there’s very little overhead. As a result, new instance creation is favored over reference reuse, even when being used to represent the same value. Equality is then checked based on the comparability of the fields of both instances.

Definition

Ward Cunningham defines a Value Object as:

a measure or description of something. Examples of value objects are things like numbers, dates, monies and strings. Usually, they are small objects which are used quite widely. Their identity is based on their state rather than on their object identity. This way, you can have multiple copies of the same conceptual value object. Every $5 note has its own identity (thanks to its serial number), but the cash economy relies on every $5 note having the same value as every other $5 note.

Martin Fowler defines a Value Object as:

a small object such as a Money or date range object. Their key property is that they follow value semantics rather than reference semantics. You can usually tell them because their notion of equality isn’t based on identity, instead two value objects are equal if all their fields are equal. Although all fields are equal, you don’t need to compare all fields if a subset is unique - for example currency codes for currency objects are enough to test equality. A general heuristic is that value objects should be entirely immutable. If you want to change a value object you should replace the object with a new one and not be allowed to update the values of the value object itself - updatable value objects lead to aliasing problems.

Examples of Value Objects are numbers, text strings, dates, times, a person’s full name (composed of first name, middle name, last name, and title), currencies, colors, phone numbers, and postal addresses.

Value Object vs. Entity

Consider the following examples from Wikipedia, in order to better understand the difference between Value Objects and Entities.

Value Object:

When people exchange dollar bills, they generally do not distinguish between each unique bill; they only are concerned about the face value of the dollar bill. In this context, dollar bills are value objects. However, the Federal Reserve may be concerned about each unique bill; in this context each bill would be an entity.

Entity:

Most airlines distinguish each seat uniquely on every flight. Each seat is an entity in this context. However, Southwest Airlines, EasyJet and Ryanair do not distinguish between every seat; all seats are the same. In this context, a seat is actually a value object.

Currency and Money Example

Currency and Money Value Objects are probably the most used examples for explaining Value Objects, thanks to the Money pattern. This design pattern provides a solution for modeling a problem that avoids a floating-point rounding issue, which in turn allows for deterministic calculations to be performed.

In the real world, a currency describes monetary units in the same way that meters and yards describe distance units. Each currency is represented with a three-letter uppercase ISO code:

 1 class Currency
 2 {
 3     private string $isoCode;
 4 
 5     public function __construct(string $anIsoCode)
 6     {
 7         $this->setIsoCode($anIsoCode);
 8     }
 9 
10     private function setIsoCode(string $anIsoCode): void
11     {
12         if (!preg_match('/^[A-Z]{3}$/', $anIsoCode)) {
13             throw new \InvalidArgumentException(
14                 sprintf('"%s" is not a valid ISO code', $anIsoCode)
15             );
16         }
17 
18         $this->isoCode = $anIsoCode;
19     }
20 
21     public function isoCode(): string
22     {
23         return $this->isoCode;
24     }
25 }

One of the main goals of Value Objects is also the holy grail of Object-Oriented design: encapsulation. By following this pattern, you’ll end up with a dedicated location to put all the validation, comparison logic, and behavior for a given concept.

Money is used to measure a specific amount of currency. It’s modeled using an amount and a Currency. Amount, in the case of the Money pattern, is implemented using an integer representation of the currency’s least-valuable fraction — e.g. in the case of USD or EUR, cents.

As a bonus, you might also notice that we’re using self encapsulation to set the ISO code, which centralizes changes in the Value Object itself:

 1 class Money
 2 {
 3     private int $amount;
 4     private Currency $currency;
 5 
 6     public function __construct(int $anAmount, Currency $aCurrency)
 7     {
 8         $this->setAmount($anAmount);
 9         $this->setCurrency($aCurrency);
10     }
11 
12     private function setAmount($anAmount): void
13     {
14         $this->amount = (int) $anAmount;
15     }
16 
17     private function setCurrency(Currency $aCurrency): void
18     {
19         $this->currency = $aCurrency;
20     }
21 
22     public function amount(): int
23     {
24         return $this->amount;
25     }
26 
27     public function currency(): Currency
28     {
29         return $this->currency;
30     }
31 }

Now that you know the formal definition of Value Objects, let’s dive deeper into some of the powerful features they offer.

Characteristics

While modeling an Ubiquitous Language concept in code, you should always favor Value Objects over Entities. Value Objects are easier to create, test, use, and maintain.

Keeping this in mind, you can determine whether the concept in question can be modeled as a Value Object if:

  • It measures, quantifies, or describes a thing in the Domain.
  • It can be kept immutable.
  • It models a conceptual whole by composing related attributes as an integral unit.
  • It can be compared with others through value equality.
  • It is completely replaceable when the measurement or description changes.
  • It supplies its collaborators with side-effect-free behavior.

Measures, Quantifies, or Describes

As discussed before, a Value Object should not be considered just a thing in your Domain. As a value, it measures, quantifies, or describes a concept in the Domain.

In our example, the Currency object describes what type of money it is. The Money object measures or quantifies units of a given Currency.

Immutability

This is one of the most important aspects to grasp. Object values shouldn’t be able to be altered over their lifetime. Because of this immutability, Value Objects are easy to reason and test and are free of undesired/unexpected side effects.

As such, Value Objects should be created through their constructors. In order to build one, you usually pass the required primitive types or other Value Objects through this constructor. Value Objects are always in a valid state; that’s why we create them in a single atomic step. Empty constructors with multiple setters and getters move the creation responsibility to the client, resulting in the Anemic Domain Model, which is considered an anti-pattern.

It’s also good to point out that it’s not recommended to hold references to Entities in your Value Objects. Entities are mutable, and holding references to them could lead to undesirable side effects occurring in the Value Object.

In languages with method overloading, such as Java, you can create multiple constructors with the same name. Each of these constructors are provided with different options to build the same type of resulting object. In PHP, we’re able to provide a similar capability by way of factory methods. These specific factory methods are also known as semantic constructors. The main goal of fromMoney is to provide more contextual meaning than the plain constructor. More radical approaches propose to make the _construct method private and build every instance using a semantic constructor.

In our Money and Currency objects, we could add some useful factory methods like the following:

 1 class Currency
 2 {
 3     private string $isoCode;
 4 
 5     public static function fromValue(string $anIsoCode): self
 6     {
 7         return new self($anIsoCode);
 8     }
 9 
10     private function __construct(string $anIsoCode)
11     {
12         $this->setIsoCode($anIsoCode);
13     }
14 
15     // ...
16 }
 1 class Money
 2 {
 3     // ...
 4 
 5     public static function fromMoney(self $aMoney): self
 6     {
 7         return new self(
 8             $aMoney->amount(),
 9             $aMoney->currency()
10         );
11     }
12 
13     public static function fromCurrency(Currency $aCurrency): self
14     {
15         return new self(0, $aCurrency);
16     }
17 
18     public static function fromAmountAndCurrency(
19         int $anAmount,
20         Currency $aCurrency
21     ): self {
22         return new self(
23             $anAmount,
24             $aCurrency
25         );
26     }
27 }

By using the self keyword, we don’t couple the code with the class name. As such, a change to the class name or namespace won’t affect these factory methods. Be also aware of making the constructor private so using your factory methods is the only way to instantiate new objects. These small implementation details helps when refactoring the code at a later date.

Due to this immutability, we must consider how to handle mutable actions that are commonplace in a stateful context. If we require a state change, we now have to return a brand new Value Object representation with this change.

If we want to increase the amount of, for example, a Money Value Object, we’re required to instead return a new Money instance with the desired modifications. Fortunately, it’s relativity simple to abide by this rule, as shown in the example below:

 1 class Money
 2 {
 3     // ...
 4 
 5     public function increaseAmountBy(int $anAmount): self
 6     {
 7         return new self(
 8             $this->amount() + $anAmount,
 9             $this->currency()
10         );
11     }
12 }

The Money object returned by increaseAmountBy is different from the Money client object that received the method call. This can be observed in the example comparability checks below:

1 $aMoney = Money::fromAmountAndCurrency(100, Currency::fromValue('USD'));
2 $otherMoney = $aMoney->increaseAmountBy(100);
3 
4 var_dump($aMoney === $otherMoney);
5 // bool(false)
6 
7 $aMoney = $aMoney->increaseAmountBy(100);
8 var_dump($aMoney === $otherMoney);
9 // bool(false)

Conceptual Whole

So why not just implement something similar to the following example, avoiding the need for a new Value Object class altogether?

 1 class Product
 2 {
 3     private string $id;
 4     private string $name;
 5 
 6     private int $amount;
 7     private string $currency;
 8 
 9     // ...
10 }

This approach has some noticeable flaws, if say, for example, you want to validate the ISO. It doesn’t really make sense for the Product to be responsible for the currency’s ISO validation (thus violating the Single Responsibility Principle). This is highlighted even more so if you want to reuse the accompanying logic in other parts of your Domain (to abide by the DRY principle).

With these factors in mind, this use case is a perfect candidate for being abstracted out into a Value Object. Using this abstraction not only gives you the opportunity to group related properties together, but it also allows you to create higher-order concepts and a more concrete Ubiquitous Language.

Value Equality

As discussed at the beginning of the chapter, two Value Objects are equal if the content they measure, quantify, or describe is the same.

For example, imagine two Money objects representing 1 USD. Can we consider them equal? In the “real world,” are two bills of 1 USD valued the same? Of course they are. Directing our attention back to the code, the Value Objects in question refer to separate instances of Money. However, they both represent the same value, which makes them equal.

In regards to PHP, it’s commonplace to compare two Value Objects using the == operator. Examining the PHP Documentation definition of the operator highlights an interesting behavior:

When using the comparison operator (==), object variables are compared in a simple manner, namely: Two object instances are equal if they have the same attributes and values, and are instances of the same class.

This behavior works in agreement with our formal definition of a Value Object. However, as an exact class match predicate is present, you should be wary when handling subtyped Value Objects.

Keeping this in mind, the even stricter === operator doesn’t help us, unfortunately:

When using the identity operator (===), object variables are identical if and only if they refer to the same instance of the same class.

The following example should help confirm these subtle differences:

 1 $a = Currency::fromValue('USD');
 2 $b = Currency::fromValue('USD');
 3 
 4 var_dump($a == $b);  // bool(true)
 5 var_dump($a === $b); // bool(false)
 6 
 7 $c = Currency::fromValue('EUR');
 8 
 9 var_dump($a == $c);  // bool(false)
10 var_dump($a === $c); // bool(false)

A solution is to implement a conventional equals method in each Value Object. This method is tasked with checking the type and equality of its composite attributes. Abstract data type comparability is easy to implement using PHP’s built-in type hinting. You can also use the get_class() function to aid in the comparability check if necessary. The language, however, is unable to decipher what equality truly means in your Domain concept, meaning it’s your responsibility to provide the answer.

In order to compare Currency objects, we just need to confirm that both their associated ISO codes are the same. The === operator does the job pretty well in this case:

 1 class Currency
 2 {
 3     // ...
 4 
 5     public function equals(self $currency): bool
 6     {
 7         return $currency->isoCode() === $this->isoCode();
 8 
 9         // You could also access directly
10         // the $isoCode field if necessary
11         // return $currency->isoCode === $this->isoCode;
12     }
13 }

Because Money objects use Currency objects, the equals method needs to perform this comparability check, along with comparing the amounts:

 1 class Money
 2 {
 3     // ...
 4 
 5     public function equals(self $aMoney): bool
 6     {
 7         return
 8             $aMoney->currency()->equals($this->currency()) &&
 9             $aMoney->amount() === $this->amount();
10     }
11 }

Replaceability

Consider a Product Entity that contains a Money Value Object used to quantify its price. Additionally, consider two Product Entities with an identical price — for example 100 USD. This scenario could be modeled using two individual Money objects or two references pointing to a single Value Object.

Sharing the same Value Object can be risky; if one is altered, both will reflect the change. This behavior can be considered an unexpected side effect. For example, if Carlos was hired on February 20, and we know that Christian was also hired on the same day, we may set Christian’s hire date to be the same instance as Carlos’s. If Carlos then changes the month of his hire date to May, Christian’s hire date changes too. Whether it’s correct or not, it’s not what people expect.

Due to the problems highlighted in this example, when holding a reference to a Value Object, it’s recommended to replace the object as a whole rather than modifying its value:

1 $this->price = Money::fromAmountAndCurrency(100, Currency::fromValue('USD'));
2 // ...
3 $this->price = $this->price->increaseAmountBy(200);

This kind of behavior is similar to how basic types such as strings work in PHP. Consider the function strtolower. It returns a new string rather than modifying the original one. No reference is used; instead, a new value is returned.

Side-Effect-Free Behavior

If we want to include some additional behavior — like an add method — in our Money class, it feels natural to check that the input fits any preconditions and maintains any invariance. In our case, we only wish to add monies with the same currency:

 1 class Money
 2 {
 3     // ...
 4 
 5     public function add(self $aMoney): self
 6     {
 7         if (!$aMoney->currency()->equals($this->currency())) {
 8             throw new \InvalidArgumentException(
 9                 'Currencies do not match'
10             );
11         }
12 
13         $this->amount += $aMoney->amount();
14     }
15 }

If the two currencies don’t match, an exception is raised. Otherwise, the amounts are added. However, this code has some undesirable pitfalls. Now imagine we have a mysterious method call to otherMethod in our code:

 1 class Banking
 2 {
 3     public function doSomething(): void
 4     {
 5         $aMoney = Money::fromAmountAndCurrency(100, Currency::fromValue('USD'));
 6 
 7         $this->otherMethod($aMoney); //mysterious call
 8         // ...
 9     }
10 }

Everything is fine until, for some reason, we start seeing unexpected results when we’re returning or finished with otherMethod. Suddenly, $aMoney no longer contains 100 USD. What happened? And what happens if otherMethod internally uses our previously defined add method? Maybe you’re unaware that add mutates the state of the Money instance. This is what we call a side effect. You must avoid generating side effects. You must not mutate your arguments. If you do, the developer using your objects may experience strange behaviors. They’ll complain, and they’ll be correct.

So how can we fix this? Simple — by making sure that the Value Object remains immutable, we avoid this kind of unexpected problem. An easy solution could be returning a new instance for every potentially mutable operation, which the add method does:

 1 class Money
 2 {
 3     // ...
 4 
 5     public function add(self $aMoney): self
 6     {
 7         $this->guardSameCurrencies($aMoney);
 8 
 9         return new self(
10             $aMoney->amount() + $this->amount(),
11             $this->currency()
12         );
13     }
14 
15     private function guardSameCurrencies(self $aMoney): void
16     {
17         if (!$aMoney->currency()->equals($this->currency())) {
18             throw new \InvalidArgumentException(
19                 'Currencies do not match'
20             );
21         }
22     }
23 }

With this simple change, immutability is guaranteed. Each time two instances of Money are added together, a new resulting instance is returned. Other classes can perform any number of changes without affecting the original copy. Code free of side effects is easy to understand, easy to test, and less error prone.

Basic Types

Consider the following code snippet:

 1 $a = 10;
 2 $b = 10;
 3 var_dump($a == $b);
 4 // bool(true)
 5 var_dump($a === $b);
 6 // bool(true)
 7 $a = 20;
 8 var_dump($a);
 9 // int(20)
10 $a = $a + 30;
11 var_dump($a);
12 // int(50)

Although $a and $b are different variables stored in different memory locations, when compared, they’re the same. They hold the same value, so we consider them equal. You can change the value of $a from 10 to 20 at any time that you want, making the new value 20 and eliminating the 10. You can replace integer values as much as you want without consideration of the previous value because you’re not modifying it; you’re just replacing it. If you apply any operation — such as addition (i.e. $a + $b) — to these variables, you get another new value that can be assigned to another variable or a previously defined one. When you pass $a to another function, except when explicitly passed by reference, you’re passing a value. It doesn’t matter if $a gets modified within that function, because in your current code, you’ll still have the original copy. Value Objects behave as basic types.

Testing Value Objects

Value Objects are tested in the same way normal objects are. However, the immutability and side-effect-free behavior must be tested too. A solution is to create a copy of the Value Object you’re testing before performing any modifications. Assert both are equal using the implemented equality check. Perform the actions you want to test and assert the results. Finally, assert that the original object and copy are still equal.

Let’s put this into practice and test the side-effect-free implementation of our add method in the Money class:

 1 class MoneyTest extends TestCase
 2 {
 3     /**
 4      * @test
 5      */
 6     public function copiedMoneyShouldRepresentSameValue()
 7     {
 8         $aMoney = Money::fromAmountAndCurrency(100, Currency::fromValue('USD'));
 9 
10         $copiedMoney = Money::fromMoney($aMoney);
11 
12         $this->assertTrue($aMoney->equals($copiedMoney));
13     }
14 
15     /**
16      * @test
17      */
18     public function originalMoneyShouldNotBeModifiedOnAddition()
19     {
20         $aMoney = Money::fromAmountAndCurrency(100, Currency::fromValue('USD'));
21         $otherMoney = Money::fromAmountAndCurrency(20, Currency::fromValue('USD'));
22 
23         $aMoney->add($otherMoney);
24 
25         $this->assertEquals(100, $aMoney->amount());
26     }
27 
28     /**
29      * @test
30      */
31     public function moniesShouldBeAdded()
32     {
33         $aMoney = Money::fromAmountAndCurrency(100, Currency::fromValue('USD'));
34         $otherMoney = Money::fromAmountAndCurrency(20, Currency::fromValue('USD'));
35 
36         $newMoney = $aMoney->add($otherMoney);
37 
38         $this->assertEquals(120, $newMoney->amount());
39     }
40 
41     // ...
42 }

Persisting Value Objects

Value Objects are not persisted on their own; they’re typically persisted within an Aggregate. Value Objects shouldn’t be persisted as complete records, though that’s an option in some cases. Instead, it’s best to use Embedded Value or Serialize LOB patterns. Both patterns can be used when persisting your objects with an open source ORM such as Doctrine, or with a bespoke ORM. As Value Objects are small, Embedded Value is usually the best choice because it provides an easy way to query Entities by any of the attributes the Value Object has. However, if querying by those fields isn’t important to you, serialize strategies can be very easy to implement.

Consider the following Product Entity with string id, name, and price (Money Value Object) attributes. We’ve intentionally decided to simplify this example, with the id being a string and not a Value Object:

 1 class Product
 2 {
 3     private string $productId;
 4     private string $name;
 5     private Money $price;
 6 
 7     public static function create(
 8         string $aProductId,
 9         string $aName,
10         Money $aPrice
11     ): self {
12         return new self(
13             $aProductId,
14             $aName,
15             $aPrice
16         );
17     }
18 
19     protected function __construct(
20         string $aProductId,
21         string $aName,
22         Money $aPrice
23     ) {
24         $this->setProductId($aProductId);
25         $this->setName($aName);
26         $this->setPrice($aPrice);
27     }
28 
29     // ...
30 }

Assuming you have a Repository for persisting Product Entities, an implementation to create and persist a new Product could look like this:

1 $product = Product::create(
2     $productRepository->nextIdentity(),
3     'Domain-Driven Design in PHP',
4     Money::fromAmountAndCurrency(999, Currency::fromValue('USD'))
5 );
6 
7 $productRepository->add($product);

Now let’s look at both the ad hoc ORM and the Doctrine implementations that could be used to persist a Product Entity containing Value Objects. We’ll highlight the application of the Embedded Value and Serialized LOB patterns, along with the differences between persisting a single Value Object and a collection of them.

Persisting Single Value Objects

Many different options are available for persisting a single Value Object. These range from using Serialize LOB or Embedded Value as mapping strategies, to using an ad hoc ORM or an open source alternative, such as Doctrine. We consider an ad hoc ORM to be a custom-built ORM that your company may have developed in order to persist Entities in a database. In our scenario, the ad hoc ORM code is going to be implemented using the DBAL library. According to the official documentation, “The Doctrine database abstraction & access layer (DBAL) offers a lightweight and thin runtime layer around a PDO-like API and a lot of additional, horizontal features like database schema introspection and manipulation through an OO API.”

Embedded Value with an Ad Hoc ORM

If we’re dealing with an ad hoc ORM using the Embedded Value pattern, we need to create a field in the Entity table for each attribute in the Value Object. In this case, two extra columns are needed when persisting a Product Entity — one for the amount of the Value Object, and one for its currency ISO code:

1 CREATE TABLE products (
2     id INTEGER NOT NULL,
3     name VARCHAR(255) NOT NULL,
4     price_amount INT NOT NULL,
5     price_currency VARCHAR(3) NOT NULL
6 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

For persisting the Entity in the database, our Repository has to map each of the fields of the Entity and the ones from the Money Value Object. If you’re using an ad hoc ORM Repository based on DBAL – let’s call it DbalProductRepository – you must take care of creating the INSERT statement, binding the parameters, and executing the statement:

 1 class DbalProductRepository
 2     extends DbalRepository
 3     implements ProductRepository
 4 {
 5     public function add(Product $aProduct): void
 6     {
 7         $sql = 'INSERT INTO products VALUES (?, ?, ?, ?)';
 8         $stmt = $this->connection()->prepare($sql);
 9         $stmt->bindValue(1, $aProduct->id());
10         $stmt->bindValue(2, $aProduct->name());
11         $stmt->bindValue(3,
12             $aProduct->price()->amount()
13         );
14         $stmt->bindValue(4,
15             $aProduct->price()->currency()->isoCode()
16         );
17 
18         $stmt->execute();
19         // ...
20     }
21 }

After executing this snippet of code to create a Product Entity and persist it into the database, each column is filled with the desired information:

1 mysql> select * from products \G
2 *************************** 1. row ***************************
3             id: 1
4           name: Domain-Driven Design in PHP
5   price_amount: 999
6 price_currency: USD
7 1 row in set (0.00 sec)

As you can see, you can map your Value Objects and query parameters in an ad hoc manner in order to persist your Value Objects. However, everything is not as easy as it seems. Let’s try to fetch the persisted Product with its associated Money Value Object. A common approach would be to execute a SELECT statement and return a new Entity:

 1 class DbalProductRepository
 2     extends DbalRepository
 3     implements ProductRepository
 4 {
 5     public function productOfId(string $anId): Product
 6     {
 7         $sql = 'SELECT * FROM products WHERE id = ?';
 8         $stmt = $this->connection()->prepare($sql);
 9         $stmt->bindValue(1, $anId);
10         $res = $stmt->execute();
11         // ...
12 
13         return Product::create(
14             $row['id'],
15             $row['name'],
16             Money::fromAmountAndCurrency(
17                 $row['price_amount'],
18                 Currency::fromValue(
19                     $row['price_currency']
20                 )
21             )
22         );
23     }
24 }

There are some benefits to this approach. First, you can easily read, step by step, how the persistence and subsequent creations occur. Second, you can perform queries based on any of the attributes of the Value Object. Finally, the space required to persist the Entity is just what is required — no more and no less.

However, using the ad hoc ORM approach has its drawbacks. As explained in the Domain Events chapter, Entities (in Aggregate form) should fire an Event in the constructor if your Domain is interested in the Aggregate’s creation. If you use the new operator, you’ll be firing the Event as many times as the Aggregate is fetched from the database.

This is one of the reasons why Doctrine uses internal proxies and serialize and unserialize methods to reconstitute an object with its attributes in a specific state without using its constructor. An Entity should only be created with the new operator once in its lifetime:

If your intention is still to roll out your own ORM, be ready to solve some fundamental problems such as Events, different constructors, Value Objects, lazy load relations, etc. That’s why we recommend giving Doctrine a try for Domain-Driven Design applications.

Besides, in this instance, you need to create a DbalProduct Entity that extends from the Product Entity and is able to reconstitute the Entity from the database without using the new operator, instead using a static factory method.

Embedded Value (Embeddables) with Doctrine >= 2.5.*

The latest stable Doctrine release is currently version 2.5 and it comes with support for mapping Value Objects, thereby removing the need to do this yourself as in Doctrine 2.4. Since December 2015, Doctrine also has support for nested embeddables. The support is not 100 percent, but it’s high enough to give it a try. In case it doesn’t work for your scenario, take a look at the next section. For official documentation, check the Doctrine Embeddables reference. This option, if implemented correctly, is definitely the one we recommend most. It would be the simplest, most elegant solution, that also provides search support in your DQL queries.

Because Product, Money, and Currency classes have already been shown, the only thing remaining is to show the Doctrine mapping files:

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <doctrine-mapping
 3     xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
 4     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 5     xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
 6     https://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
 7 
 8     <entity
 9         name="Product"
10         table="product">
11         <id
12             name="id"
13             column="id"
14             type="string"
15             length="255">
16             <generator
17                 strategy="NONE">
18             </generator>
19         </id>
20 
21         <field
22             name="name"
23             type="string"
24             length="255"
25         />
26 
27         <embedded
28             name="price"
29             class="Ddd\Domain\Model\Money"
30         />
31     </entity>
32 </doctrine-mapping>

In the product mapping, we’re defining price as an instance variable that will hold a Money instance. At the same time, Money is designed with an amount and a Currency instance:

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <doctrine-mapping
 3     xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
 4     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 5     xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
 6     https://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
 7 
 8     <embeddable
 9         name="Ddd\Domain\Model\Money">
10 
11         <field
12             name="amount"
13             type="integer"
14         />
15 
16         <embedded
17             name="currency"
18             class="Ddd\Domain\Model\Currency"
19         />
20     </embeddable>
21 </doctrine-mapping>

Finally, it’s time to show the Doctrine mapping for our Currency Value Object:

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <doctrine-mapping
 3     xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
 4     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 5     xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
 6     https://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
 7 
 8     <embeddable
 9         name="Ddd\Domain\Model\Currency">
10 
11         <field
12             name="iso"
13             type="string"
14             length="3"
15         />
16     </embeddable>
17 </doctrine-mapping>

As you can see, the above code has a standard embeddable definition with just one string field that holds the ISO code. This approach is the easiest way to use embeddables and is much more effective. By default, Doctrine names your columns by prefixing them using the Value Object name. You can change this behavior to meet your needs by changing the column-prefix attribute in the XML notation.

Embedded Value with Doctrine <= 2.4.*

If you’re still stuck in Doctrine 2.4 probably it’s time to consider an upgrade since this version of Doctrine is currently too old (6 years old as of day of writing). Anyway you may wonder what an acceptable solution for using Embedded Values with Doctrine < 2.5 is. We need to now surrogate all the Value Object attributes in the Product Entity, which means creating new artificial attributes that will hold the information of the Value Object. With this in place, we can map all those new attributes using Doctrine. Let’s see what impact this has on the Product Entity:

 1 class Product
 2 {
 3     private string $productId;
 4     private string $name;
 5     private Money $price;
 6 
 7     private string $surrogateCurrencyIsoCode;
 8     private int $surrogateAmount;
 9 
10     public function __construct(string $aProductId, string $aName, Money $aPrice)
11     {
12         $this->setProductId($aProductId);
13         $this->setName($aName);
14         $this->setPrice($aPrice);
15     }
16 
17     private function setPrice(Money $aMoney): void
18     {
19         $this->price = $aMoney;
20 
21         $this->surrogateAmount = $aMoney->amount();
22         $this->surrogateCurrencyIsoCode =
23             $aMoney->currency()->isoCode();
24     }
25 
26     private function price(): Money
27     {
28         if (null === $this->price) {
29             $this->price = Money::fromAmountAndCurrency(
30                $this->surrogateAmount,
31                Currency::fromValue($this->surrogateCurrency)
32            );
33         }
34 
35         return $this->price;
36     }
37 
38     // ...
39 }

As you can see, there are two new attributes: one for the amount, and another for the ISO code of the currency. We’ve also updated the setPrice method in order to keep attribute consistency when setting it. On top of this, we updated the price getter in order to return the Money Value Object built from the new fields. Let’s see how the corresponding XML Doctrine mapping should be changed:

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <doctrine-mapping
 3     xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
 4     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 5     xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
 6     https://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
 7 
 8     <entity
 9         name="Product"
10         table="product">
11 
12         <id
13             name="id"
14             column="id"
15             type="string"
16             length="255">
17             <generator
18                 strategy="NONE">
19             </generator>
20         </id>
21 
22         <field
23             name="name"
24             type="string"
25             length="255"
26         />
27 
28         <field
29             name="surrogateAmount"
30             type="integer"
31             column="price_amount"
32         />
33         <field
34             name="surrogateCurrencyIsoCode"
35             type="string"
36             column="price_currency"
37         />
38     </entity>
39 </doctrine-mapping>

If we wanted to push these two attributes outside of the Domain, this could be achieved through the use of an Abstract Factory. First, we need to create a new Entity, DoctrineProduct, in our Infrastructure folder. This Entity will extend from Product Entity. All surrogate fields will be placed in the new class, and methods such as price or setPrice should be reimplemented. We’ll map Doctrine to use the new DoctrineProduct as opposed to the Product Entity.

Now we’re able to fetch Entities from the database, but what about creating a new Product? At some point, we’re required to call new Product, but because we need to deal with DoctrineProduct and we don’t want our Application Services to know about Infrastructure details, we’ll need to use Factories to create Product Entities. So, in every instance where Entity creation occurs with new, you’ll instead call createProduct on ProductFactory.

There could be many additional classes required to avoid placing the surrogate attributes in the original Entity. As such, it’s our recommendation to surrogate all the Value Objects to the same Entity, though this admittedly leads to a less pure solution.

Serialized LOB and Ad Hoc ORM

If the addition of searching capabilities to the Value Objects attributes is not important, there’s another pattern that can be considered: the Serialized LOB. This pattern works by serializing the whole Value Object into a string format that can easily be persisted and fetched. The most significant difference between this solution and the embedded alternative is that in the latter option, the persistence footprint requirements are reduced to a single column:

1 CREATE TABLE products (
2     id INTEGER NOT NULL,
3     name VARCHAR(255) NOT NULL,
4     price TEXT NOT NULL
5 );

In order to persist Product Entities using this approach, a change in the DbalProductRepository is required. The Money Value Object must be serialized into a string before persisting the final Entity:

 1 class DbalProductRepository
 2     extends DbalRepository
 3     implements ProductRepository
 4 {
 5     public function add(Product $aProduct): void
 6     {
 7         $sql = 'INSERT INTO products VALUES (?, ?, ?)';
 8         $stmt = $this->connection()->prepare($sql);
 9         $stmt->bindValue(1, $aProduct->id());
10         $stmt->bindValue(2, $aProduct->name());
11 
12         $stmt->bindValue(
13             3,
14             serialize(
15                 $aProduct->price()
16             )
17         );
18 
19         // ...
20     }
21 }

Let’s see how our Product is now represented in the database. The table column price is a TEXT type column that contains a serialization of a Money object representing 9.99 USD:

1 mysql> select * from products \G
2 *************************** 1. row ***************************
3    id: 1
4  name: Domain-Driven Design in PHP
5 price: O:22:"Ddd\Domain\Model\Money":2:{s:30:" Ddd\Domain\Model\Money amount";i:999;\
6 s:32:" Ddd\Domain\Model\Money currency";O:25:"Ddd\Domain\Model\Currency":1:{s:34:" D\
7 dd\Domain\Model\Currency isoCode";s:3:"USD";}}
8 1 row in set (0.00 sec)

This approach does the job. However, it’s not recommended due to problems occurring when refactoring classes in your code. Could you imagine the problems if we decided to rename our Money class? Could you imagine the changes that would be required in our database representation when moving the Money class from one namespace to another? Another tradeoff, as explained before, is the lack of querying capabilities. It doesn’t matter whether you use Doctrine or not; writing a query to get the products cheaper than, say, 200 USD is almost impossible while using a serialization strategy.

The querying issue can only be solved by using Embedded Values. However, the serialization refactoring problems can be fixed using a specialized library for handling serialization processes.

Improved Serialization with JMS Serializer

serialize/unserialize native PHP strategies have a problem when dealing with class and namespace refactoring. One alternative is to use your own serialization mechanism — for example, concatenating the amount, a one character separator such as “|,” and the currency ISO code. However, there’s another favored approach: using an open source serializer library such as Zumba JsonSerializer. Let’s see an example of applying it when serializing a Money object:

1 $myMoney = Money::fromAmountAndCurrency(
2     999,
3     Currency::fromValue('USD')
4 );
5 
6 $serializer = new \Zumba\JsonSerializer\JsonSerializer();
7 
8 $json = $serializer->serialize($myMoney);

In order to unserialize the object, the process is straightforward:

1 $serializer = new \Zumba\JsonSerializer\JsonSerializer();
2 // ...
3 $myMoney = $serializer->unserialize($json);

With this example, you can refactor your Money class without having to update your database. JMS Serializer can be used in many different scenarios — for example, when working with REST APIs. An important feature is the ability to specify which attributes of an object should be omitted in the serialization process — for example, a password.

Check out the Mapping Reference and the Cookbook for more information. JMS Serializer is a must in any Domain-Driven Design project.

Serialized LOB with Doctrine

In Doctrine, there are different ways of serializing objects in order to eventually persist them.

Doctrine Object Mapping Type

Doctrine has support for the Serialize LOB pattern. There are plenty of predefined mapping types you can use in order to match Entity attributes with database columns or even tables. One of those mappings is the object type, which maps an SQL CLOB to a PHP object using serialize() and unserialize().

According to the Doctrine DBAL 2 Documentation, object type:

maps and converts object data based on PHP serialization. If you need to store an exact representation of your object data, you should consider using this type as it uses serialization to represent an exact copy of your object as string in the database. Values retrieved from the database are always converted to PHP’s object type using unserialization or null if no data is present.

This type will always be mapped to the database vendor’s text type internally as there is no way of storing a PHP object representation natively in the database. Furthermore this type requires a SQL column comment hint so that it can be reverse engineered from the database. Doctrine cannot correctly map back this type correctly using vendors that do not support column comments, and will instead fall back to the text type instead.

Because the built-in text type of PostgreSQL does not support NULL bytes, the object type will result in unserialization errors. A workaround to this problem is to serialize()/unserialize() and base64_encode()/base64_decode() PHP objects and store them into a text field manually.

Let’s look at a possible XML mapping for the Product Entity by using the object type:

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <doctrine-mapping
 3     xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
 4     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 5     xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
 6     https://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
 7 
 8     <entity
 9         name="Product"
10         table="products">
11 
12         <id
13             name="id"
14             column="id"
15             type="string"
16             length="255">
17             <generator
18                 strategy="NONE">
19             </generator>
20         </id>
21 
22         <field
23             name="name"
24             type="string"
25             length="255"
26         />
27 
28         <field
29             name="price"
30             type="object"
31         />
32     </entity>
33 </doctrine-mapping>

The key addition is type="object", which tells Doctrine that we’re going to be using an object mapping. Let’s see how we could create and persist a Product Entity using Doctrine:

1 // ...
2 $em->persist($product);
3 $em->flush($product);

Let’s check that if we now fetch our Product Entity from the database, it’s returned in an expected state:

 1 // ...
 2 $repository = $em->getRepository(Product::class);
 3 $item = $repository->find(1);
 4 var_dump($item);
 5 
 6 /*
 7 class Ddd\Domain\Model\Product#177 (3) {
 8   private $productId =>
 9   int(1)
10   private $name =>
11   string(41) "Domain-Driven Design in PHP"
12   private $money =>
13   class Ddd\Domain\Model\Money#174 (2) {
14     private $amount =>
15     string(3) "100"
16     private $currency =>
17     class Ddd\Domain\Model\Currency#175 (1) {
18       private $isoCode =>
19       string(3) "USD"
20     }
21   }
22 }
23 */

Last but not least, the Doctrine DBAL 2 Documentation states that:

Object types are compared by reference, not by value. Doctrine updates this value if the reference changes and therefore behaves as if these objects are immutable value objects.

This approach suffers from the same refactoring issues as the ad hoc ORM did. The object mapping type is internally using serialize/unserialize. What about instead using our own serialization?

Doctrine Custom Types

Another option is to handle the Value Object persistence using a Doctrine Custom Type. A Custom Type adds a new mapping type to Doctrine — one that describes a custom transformation between an Entity field and the database representation, in order to persist the former.

As the Doctrine DBAL 2 Documentation explains:

Just redefining how database types are mapped to all the existing Doctrine types is not at all that useful. You can define your own Doctrine Mapping Types by extending Doctrine\DBAL\Types\Type. You are required to implement 4 different methods to get this working.

With the object type, the serialization step includes information, such as the class, that makes it quite difficult to safely refactor our code. Let’s try to improve on this solution. Think about a custom serialization process that could solve the problem. One such way could be to persist the Money Value Object as a string in the database encoded in amount|isoCode format:

 1 use Ddd\Domain\Model\Currency;
 2 use Ddd\Domain\Model\Money;
 3 use Doctrine\DBAL\Types\TextType;
 4 use Doctrine\DBAL\Platforms\AbstractPlatform;
 5 
 6 class MoneyType extends TextType
 7 {
 8     const MONEY = 'money';
 9 
10     public function convertToPHPValue(
11         $value,
12         AbstractPlatform $platform
13     )
14     {
15         $value = parent::convertToPHPValue($value, $platform);
16 
17         $value = explode('|', $value);
18         return Money::fromAmountAndCurrency(
19             $value[0],
20             Currency::fromValue($value[1])
21         );
22     }
23 
24     public function convertToDatabaseValue(
25         $value,
26         AbstractPlatform $platform
27     )
28     {
29         return implode(
30             '|',
31             [
32                 $value->amount(),
33                 $value->currency()->isoCode()
34             ]
35         );
36     }
37 
38     public function getName()
39     {
40         return self::MONEY;
41     }
42 }

Using Doctrine, you’re required to register all Custom Types. It’s common to use an EntityManagerFactory that centralizes this EntityManager creation. Alternatively, you could perform this step by bootstrapping your application:

 1 use Doctrine\DBAL\Types\Type;
 2 use Doctrine\ORM\EntityManager;
 3 use Doctrine\ORM\Tools\Setup;
 4 
 5 use Ddd\Infrastructure\Persistence\Doctrine\Type\MoneyType;
 6 
 7 class EntityManagerFactory
 8 {
 9     public function build(): EntityManager
10     {
11         Type::addType('money', MoneyType::class);
12 
13         return EntityManager::create(
14             [
15                 'driver'   => 'pdo_mysql',
16                 'user'     => 'root',
17                 'password' => '',
18                 'dbname'   => 'ddd',
19             ],
20             Setup::createXMLMetadataConfiguration(
21                 [__DIR__.'/config'],
22                 true
23             )
24         );
25     }
26 }

Now we need to specify in the mapping that we want to use our Custom Type:

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <doctrine-mapping>
 3 
 4     <entity
 5         name="Product"
 6         table="product">
 7 
 8         <!-- ... -->
 9 
10         <field
11             name="price"
12             type="money"
13         />
14     </entity>
15 </doctrine-mapping>

Let’s check the database to see how the price was persisted using this approach:

1 mysql> select * from products \G
2 *************************** 1. row ***************************
3    id: 1
4  name: Domain-Driven Design in PHP
5 price: 999|USD
6 1 row in set (0.00 sec)

This approach is an improvement on the one before in terms of future refactoring. However, searching capabilities remain limited due to the format of the column. With the Doctrine Custom types, you can improve the situation a little, but it’s still not the best option for building your DQL queries. See Doctrine Custom Mapping Types for more information.

Persisting a Collection of Value Objects

Imagine that we’d now like to add a collection of prices to be persisted to our Product Entity. These prices could represent the different prices the product has borne throughout its lifetime or the product price in different currencies. This could be named HistoricalPrice, as shown below:

 1 class HistoricalProduct extends Product
 2 {
 3     /**
 4      * @var Money[]
 5      */
 6     protected array $prices;
 7 
 8     public static function create(
 9         string $aProductId,
10         string $aName,
11         Money $aPrice,
12         array $somePrices
13     ): self {
14         return new self(
15             $aProductId,
16             $aName,
17             $aPrice,
18             $somePrices
19         );
20     }
21 
22     private function __construct(
23         string $aProductId,
24         string $aName,
25         Money $aPrice,
26         array $somePrices
27     )
28     {
29         parent::__construct($aProductId, $aName, $aPrice);
30         $this->setPrices($somePrices);
31     }
32 
33     private function setPrices(array $somePrices): void
34     {
35         $this->prices = $somePrices;
36     }
37 
38     public function prices(): array
39     {
40         return $this->prices;
41     }
42 }

HistoricalProduct extends from Product, so it inherits the same behavior, plus the price collection functionality.

As in the previous sections, serialization is a plausible approach if you don’t care about querying capabilities. However, Embedded Values are a possibility if we know exactly how many prices we want to persist. But what happens if we want to persist an undetermined collection of historical prices?

Collection Serialized into a Single Column

Serializing a collection of Value Objects into a single column is most likely the easiest solution. Everything that was previously explained in the section about persisting a single Value Object applies in this situation. With Doctrine, you can use an Object or Custom Type — with some additional considerations to bear in mind: Value Objects should be small in size, but if you wish to persist a large collection, be sure to consider the maximum column length and the maximum row width that your database engine can handle.

Collection Backed by a Join Table

In case you want to persist and query an Entity by its related Value Objects, you have the choice to persist the Value Objects as Entities. In terms of the Domain, those objects would still be Value Objects, but we’ll need to give them an id and set them up with a one-to-many/one-to-one relation with the owner, a real Entity. To summarize, your ORM handles the collection of Value Objects as Entities, but in your Domain, they’re still treated as Value Objects.

The main idea behind the Join Table strategy is to create a table that connects the owner Entity and its Value Objects. Let’s see a database representation:

1 CREATE TABLE historical_products (
2     id CHAR(36) NOT NULL,
3     name VARCHAR(255) NOT NULL,
4     price_amount INT(11) NOT NULL,
5     price_currency CHAR(3) NOT NULL,
6     PRIMARY KEY (id)
7 );

The historical_products table will look the same as products. Remember that HistoricalProduct extends Product Entity in order to easily show how to deal with persisting a collection. A new prices table is now required in order to persist all the different Money Value Objects that a Product Entity can handle:

1 CREATE TABLE prices (
2     id INT(11) NOT NULL AUTO_INCREMENT,
3     amount INT(11) NOT NULL,
4     currency CHAR(3) COLLATE NOT NULL,
5     PRIMARY KEY (id)
6 );

Finally, a table that relates products and prices is needed:

 1 CREATE TABLE products_prices (
 2     product_id CHAR(36) NOT NULL,
 3     price_id INT(11) NOT NULL,
 4     PRIMARY KEY (product_id, price_id),
 5     UNIQUE KEY uniq_price_id (price_id),
 6     KEY idx_product_id (product_id),
 7     CONSTRAINT fk_product_id FOREIGN KEY (product_id) REFERENCES historical_products\
 8  (id),
 9     CONSTRAINT fk_price_id FOREIGN KEY (price_id) REFERENCES prices (id)
10 );
Collection Backed by a Join Table with Doctrine

Doctrine requires that all database Entities have a unique identity. Because we want to persist Money Value Objects, we need to then add an artificial identity so Doctrine can handle its persistence. There are two options: including the surrogate identity in the Money Value Object, or placing it in an extended class.

The issue with the first option is that the new identity is only required due to the Database persistence layer. This identity is not part of the Domain.

An issue with the second option is the amount of alterations required in order to avoid this so-called boundary leak. With a class extension, creating new instances of the Money Value Object class from any Domain Object isn’t recommended, as it would break the Inversion Principle. The solution is to again create a Money Factory that would need to be passed into Application Services and any other Domain Objects.

In this scenario, we recommend using the first option. Let’s review the changes required to implement it in the Money Value Object:

 1 class Money
 2 {
 3     private int $amount;
 4     private Currency $currency;
 5 
 6     private string $surrogateId;
 7     private string $surrogateCurrencyIsoCode;
 8 
 9     public static function fromAmountAndCurrency(
10         int $anAmount,
11         Currency $aCurrency
12     ): self {
13         return new self($anAmount, $aCurrency);
14     }
15         
16     private function __construct(int $amount, Currency $currency)
17     {
18         $this->setAmount($amount);
19         $this->setCurrency($currency);
20     }
21 
22     private function setAmount(int $amount): void
23     {
24         $this->amount = $amount;
25     }
26 
27     private function setCurrency(Currency $currency): void
28     {
29         $this->currency = $currency;
30         $this->surrogateCurrencyIsoCode =
31             $currency->isoCode();
32     }
33 
34     public function currency(): Currency
35     {
36         if (null === $this->currency) {
37             $this->currency = Currency::fromValue(
38                 $this->surrogateCurrencyIsoCode
39             );
40         }
41 
42         return $this->currency;
43     }
44 
45     public function amount(): int
46     {
47         return $this->amount;
48     }
49 
50     public function equals(self $aMoney): bool
51     {
52         return
53             $this->amount() === $aMoney->amount() &&
54             $this->currency()->equals($aMoney->currency());
55     }
56 }

As seen above, two new attributes have been added. The first one, surrogateId, is not used by our Domain, but it’s required for the persistence Infrastructure to persist this Value Object as an Entity in our Database. The second one, surrogateCurrencyIsoCode, holds the ISO code for the currency. Using these new attributes, it’s really easy to map our Value Object with Doctrine.

The Money mapping is quite straightforward:

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <doctrine-mapping
 3     xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
 4     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 5     xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
 6     https://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
 7 
 8     <entity
 9         name="Ddd\Domain\Model\Money"
10         table="prices">
11 
12         <id
13             name="surrogateId"
14             type="integer"
15             column="id">
16             <generator
17                 strategy="AUTO">
18             </generator>
19         </id>
20 
21         <field
22             name="amount"
23             type="integer"
24             column="amount"
25         />
26         <field
27             name="surrogateCurrencyIsoCode"
28             type="string"
29             column="currency"
30         />
31     </entity>
32 </doctrine-mapping>

Using Doctrine, the HistoricalProduct Entity would have following mapping:

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <doctrine-mapping
 3     xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
 4     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 5     xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
 6     https://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
 7 
 8     <entity
 9         name="Ddd\Domain\Model\HistoricalProduct"
10         table="historical_products"
11         repository-class="Ddd\Infrastructure\Domain\Model\DoctrineHistoricalProductR\
12 epository">
13 
14         <many-to-many
15             field="prices"
16             target-entity="Ddd\Domain\Model\Money">
17 
18             <cascade>
19                 <cascade-all/>
20             </cascade>
21 
22             <join-table
23                 name="products_prices">
24 
25                 <join-columns>
26                     <join-column
27                         name="product_id"
28                         referenced-column-name="id"
29                     />
30                 </join-columns>
31 
32                 <inverse-join-columns>
33                     <join-column
34                         name="price_id"
35                         referenced-column-name="id"
36                         unique="true"
37                     />
38                 </inverse-join-columns>
39             </join-table>
40         </many-to-many>
41     </entity>
42 </doctrine-mapping>
Collection Backed by a Join Table with an Ad Hoc ORM

It’s possible to do the same with an ad hoc ORM, where Cascade INSERTS and JOIN queries are required. It’s important to be careful about how the removal of Value Objects is handled, in order to not leave orphan Money Value Objects.

Collection Backed by a Database Entity

Database Entity is the same solution as Join Table, with the addition of the Value Object that’s only managed by the owner Entity. In the current scenario, consider that the Money Value Object is only used by the HistoricalProduct Entity; a Join Table would be overly complex. So the same result could be achieved using a one-to-many database relation.

NoSQL

What about NoSQL mechanisms such as Redis, MongoDB, or CouchDB? Unfortunately, you can’t escape these problems. In order to persist an Aggregate using Redis, you need to serialize it into a string before setting the value. If you use PHP serialize/unserialize methods, you’ll face namespace or class name refactoring issues again. If you choose to serialize in a custom way (JSON, custom string, etc.), you’ll be required to again rebuild the Value Object during Redis retrieval.

PostgreSQL JSONB and MySQL JSON Type

If our database engine would allow us to not only use the Serialized LOB strategy but also search based on its value, we would have the best of both approaches. Well, good news: now you can do this. As of PostgreSQL version 9.4, support for JSONB has been added. Value Objects can be persisted as JSON serializations and subsequently queried within this JSON serialization.

MySQL has done the same. As of MySQL 5.7.8, MySQL supports a native JSON data type that enables efficient access to data in JSON (JavaScript Object Notation) documents. According to the MySQL 5.7 Reference Manual, the JSON data type provides these advantages over storing JSON-format strings in a string column:

  • Automatic validation of JSON documents stored in JSON columns. Invalid documents produce an error.
  • Optimized storage format. JSON documents stored in JSON columns are converted to an internal format that permits quick read access to document elements. When the server later must read a JSON value stored in this binary format, the value need not be parsed from a text representation. The binary format is structured to enable the server to look up subobjects or nested values directly by key or array index without reading all values before or after them in the document.

If Relational Databases add support for document and nested document searches with high performance and with all the benefits of an ACID (Atomicity, Consistency, Isolation, Durability) philosophy, it could save a lot of complexity in many projects.

Security

Another interesting detail of modeling your Domain concepts using Value Objects is regarding its security benefits. Consider an application within the context of selling flight tickets. If you deal with International Air Transport Association airport codes, also known as IATA codes, you can decide to use a string or model the concept using a Value Object. If you choose to go with the string, think about all the places where you’ll be checking that the string is a valid IATA code. What’s the chance you forget somewhere important? On the other hand, think about trying to instantiate new IATA("BCN'; DROP TABLE users;--"). If you centralize the guards in the constructor and then pass an IATA Value Object into your model, avoiding SQL Injections or similar attacks gets easier.

If you want to know more about the security side of Domain-Driven Design, you can follow Dan Bergh Johnsson or read his blog.

Wrap-Up

Using Value Objects for modeling concepts in your Domain that measure, quantify, or describe is highly recommended. As shown, Value Objects are easy to create, maintain, and test. In order to handle persistence within a Domain-Driven Design application, using an ORM is a must. However, in order to persist Value Objects using Doctrine, the preferred way is using embeddables. In case you’re stuck in version 2.4, there are two options: adding the Value Object fields directly into your Entity and mapping them (less elegant, but easier), or extending your Entities (far more elegant, but more complex).