The Value Object and Doctrine

When you are diving deeper in how to construct a good Domain Model. You uncover different concepts which allows you to make a more descriptive and better though out Domain Model. One of those concepts is a Value Object. A Value Object is an important concept in Domain Driven Design. But you don’t need to worry about Domain Driven Design for now. In this article I’m looking to it from a more general view point. And how we can actually implement it with Symfony and Doctrine. If you are using DDD (Domain Driven Design) or not. It does not matter. Value Objects should always be an important part of your Domain.

Entity Versus Value Object

Before we dive into the actual implementations. We need to understand the difference between an Entity and a Value Object. In object-oriented programming related attributes and methods are represented by objects.

If we for example have a Product. This Product object might be represented by the attributes name, price and maybe a lot more different attributes. But in our database this object is identified with an id. This identification makes this object an Entity. Meaning, its an object wit an identity. An Entity is mutable (aka changeable). This is because you can change this objects attributes without changing its identity. A product with id 1, will always be that same product. Even if you change its name or whatever attribute.

Now our product has a Price. A price consist of a value and a currency. This Price object is a Value Object because we don’t care about its identity. We only care that its a Price. When we change a price, we don’t just change the object. We create a new one. The attributes can not be changed, and this is known as immutable.

So important to remember is that a Value Object is an object whose equality is not based on identity. If we have 2 products with both the same Price. Then those 2 Price objects are equal to each other by its value, not by it’s identity. But if you have 2 Products with exactly the same name, price and all attributes are equal. Then those 2 Entities are not equal to each other. Because equality is based on identity. Each Product has another id.

When do we need a Value Object?

So now you know how to identify the difference between an Entity and a Value Object. Does it have an id?Then its an Entity. If not then its a Value Object.

But it all depends on context. If you for example have a Client which has an Address. You want to identify each Client, their names might change. But that one Client will always be that same Client. So this is an Entity. But if it’s the address of one client or another, it doesn’t matter. Its just an address for a client. It does not have an identity. We don’t need to reuse that same exact identified Address object for different clients. So in this case Address is a Value Object.

But what if we have a warehouse and need to send packages to addresses? Then an Address need to have an identity. We need to be able to identify how many packages where send to an Address. When we want to record some activity to this Address, it needs to be identified. So in this case an Address is an Entity.

So deciding if an object is an Entity or a Value Object is all depended on context. When designing your Domain Model you will have to ask these questions. Its when you actually start to think about how your application need to work and is going to be used on the Domain level, then you will get the context and can make the correct choices. Without understanding the Domain, you can not make correct choices. Remember that.

Value Object Example

Now we have a good understanding about the Value Object. So it’s time get into implementation now.
To be used for our Product Entity. I have created the following simple Price Value Object.

<?php
class Price
{

    /**
     * @var float
     */
    private $amount;

    /**
     * @var string
     */
    private $currency;
    
    public function __construct(float $amount, string $currency)
    {
        if ($amount < 0) {
            throw new \InvalidArgumentException('Given amount '.$amount.' must be bigger then 0!');
        }

        $this->amount = $amount;
        $this->currency = $currency;
    }
    
    public function getAmount(): float
    {
        return $this->amount;
    }
    
    public function getCurrency(): string
    {
        return $this->currency;
    }
    
    public function __toString(): string
    {
        return $this->currency.' '.$this->amount;
    }

    public function equals(Price $price): bool
    {
        return ((string) $this === (string) $price);
    }
}

This implementation always assures our Price is bigger then 0. We don’t want to give out Products for free. This is a Validation at the Domain Level. In your Value Objects you need to make sure it can not have incorrect values. If you for example create an Email Value Object, you need to make sure this Value Object is a correct Email Address.

Embeddable in Doctrine

To create Value Objects in your Doctrine Entities we need to use the concept of Embeddables. Embeddables are classes which are as the name tells you, embedded into an Entity.

 So first we create our Product Entity.

<?php
/**
 * @ORM\Entity
 */
class Product
{

    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     * @var int
     */
    private $id;

    /**
     * @ORM\Column(type="string")
     * @var string
     */
    private $name;

    /**
     * @ORM\Embedded(class="App\Domain\Model\Product\Price")
     * @var Price
     */
    private $price;

    // Getters and Setters
}

As you can see here, we have defined Price as an Embedded class. Its important to let Doctrine know which class we are going to embed.

Do you remember the Value Object example? I have added ORM mapping to it. Now it is defines as an Embeddable.

<?php
/**
 * @ORM\Embeddable()
 */
class Price
{

    /**
     * @ORM\Column(type="float")
     * @var float
     */
    private $amount;

    /**
     * @ORM\Column(type="string")
     * @var string
     */
    private $currency;
    
    public function __construct(float $amount, string $currency)
    {
        if ($amount < 0) {
            throw new \InvalidArgumentException('Given amount '.$amount.' must be bigger then 0!');
        }

        $this->amount = $amount;
        $this->currency = $currency;
    }
    
    public function getAmount(): float
    {
        return $this->amount;
    }
    
    public function getCurrency(): string
    {
        return $this->currency;
    }
    
    public function __toString(): string
    {
        return $this->currency.' '.$this->amount;
    }

    public function equals(Price $price): bool
    {
        return ((string) $this) === ((string) $price);
    }
}

As you can see, its easy. We model our Value Objects as an Embeddable and then we can use this as Value Objects for our Entities.

In the database you get the following columns for Product:

  • id
  • name
  • price_amount
  • price_currency

By now you know that a Value Object is not an Identity, it only exists as value. As a result there is no separate table for our Value Object.

Attributes of Value Object

To recap and having a short overview what a Value Object is:

  • It has no identity. Thus Value Objects need to be compared by value, not identity.
  • It is immutable. Value Objects need to be replaced with a new value. It is a VALUE object, the object itself is the value. So, when making changes you create a new Value Object.
  • Can’t exist without an Entity. It can not exist on it’s own. It’s always part of an Entity.
  • Value Objects should not have separate tables.

Keep this in mind when working with Value Objects.

Conclusion

So now you know how to create a Value Object with Doctrine. Remember that a Value Objects is an important part of your Domain Model. It will help make your Domain more descriptive. This is not an unnecessary increased complexity. But when used correctly it will decrease complexity. To correctly use Value Objects it’s important to understand the context. You need to have a good knowledge about your Domain, about the problems you are going to solve.  So remember, it’s not just a DDD concept, it can and should be used in any application of any complexity.