DEV Community

Ian Rodrigues
Ian Rodrigues

Posted on • Originally published at ianrodrigues.dev on

Writing value objects in PHP

If you already have heard about DDD (Domain-Driven Design), you probably also may have heard about Value Objects. It is one of the building blocks introduced by Eric Evans in "the blue book".

A value object is a type which wraps data and is distinguishable only by its properties. Unlike an Entity, it doesn't have a unique identifier. Thus, two value objects with the same property values should be considered equal.

A good example of candidates for value objects are:

  • phone number
  • address
  • price
  • a commit hash
  • a entity identifier
  • and so on.

When designing a value object, you should pay attention to its three main characteristics: immutability, structural equality, and self-validation.

Here's an example:

<?php declare(strict_types=1);

final class Price
{
    const USD = 'USD';
    const CAD = 'CAD';

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

    /** @var string */
    private $currency;

    public function __construct(float $amount, string $currency = 'USD')
    {
        if ($amount < 0) {
            throw new \InvalidArgumentException("Amount should be a positive value: {$amount}.");
        }

        if (!in_array($currency, $this->getAvailableCurrencies())) {
            throw new \InvalidArgumentException("Currency should be a valid one: {$currency}.");
        }

        $this->amount = $amount;
        $this->currency = $currency;
    }

    private function getAvailableCurrencies(): array
    {
        return [self::USD, self::CAD];
    }

    public function getAmount(): float
    {
        return $this->amount;
    }

    public function getCurrency(): string
    {
        return $this->currency;
    }
}
Enter fullscreen mode Exit fullscreen mode

Immutability

Once you instantiate a value object, it should be the same for the rest of the application lifecycle. If you need to change its value, it should be done by entirely replacing that object.

Using mutable value objects is acceptable if you are using them entirely within a local scope, with only one reference to the object. Otherwise, you may have problems.

Taking the previous example, here's how you can update the amount of a Price type:

<?php declare(strict_types=1);

final class Price
{
    // ...

    private function hasSameCurrency(Price $price): bool
    {
        return $this->currency === $price->currency;
    }

    public function sum(Price $price): self
    {
        if (!$this->hasSameCurrency($price)) {
            throw \InvalidArgumentException(
                "You can only sum values with the same currency: {$this->currency} !== {$price->currency}."
            );
        }

        return new self($this->amount + $price->amount, $this->currency);
    }
}
Enter fullscreen mode Exit fullscreen mode

Structural Equality

Value objects don't have an identifier. In other words, if two value objects have the same internal values, they must be considered equal. As PHP doesn't have a way to override the equality operator, you should implement it by yourself.

You can create a specialized method to do that:

<?php declare(strict_types=1);

final class Price
{
    // ...

    public function isEqualsTo(Price $price): bool
    {
        return $this->amount === $price->amount && $this->currency === $price->currency;
    }
}
Enter fullscreen mode Exit fullscreen mode

Another option is to create a hash based on its properties:

<?php declare(strict_types=1);

final class Price
{
    // ...

    private function hash(): string
    {
        return md5("{$this->amount}{$this->currency}");
    }

    public function isEqualsTo(Price $price): bool
    {
        return $this->hash() === $price->hash();
    }
}
Enter fullscreen mode Exit fullscreen mode

Self-Validation

The validation of a value object should occur on its creation. If any of its properties are invalid, it should throw an exception. Putting this together with immutability, once you create a value object, you can be sure it will always be valid.

Taking the Price type example once again, it doesn't make sense to have a negative amount for the domain of the application:

<?php declare(strict_types=1);

final class Price
{
    // ...

    public function __construct(float $amount, string $currency = 'USD')
    {
        if ($amount < 0) {
            throw new \InvalidArgumentException("Amount should be a positive value: {$amount}.");
        }

        if (!in_array($currency, $this->getAvailableCurrencies())) {
            throw new \InvalidArgumentException("Currency should be a valid one: {$currency}.");
        }

        $this->amount = $amount;
        $this->currency = $currency;
    }
}
Enter fullscreen mode Exit fullscreen mode

Using with Doctrine

Storing and retrieving value objects from the database using Doctrine is quite easy using Embeddables. According to the documentation, Embeddables are not entities. But, you embed them in entities, which makes them perfect for dealing with value objects.

Let's suppose you have a Product class, and you would like to store the price in that class. You will end up with the following modeling:

<?php declare(strict_types=1);

/** @Embeddable */
final class Price
{
    /** @Column(type="float") */
    private $amount;

    /** @Column(type="string") */
    private $currency;

    public function __construct(float $amount, string $currency = 'USD')
    {
        // ...

        $this->amount = $amount;
        $this->currency = $currency;
    }

    // ...
}

/** @Entity */
class Product
{
    /** @Embedded(class="Price") */
    private $price;

    public function __construct(Price $price)
    {
        $this->price = $price;
    }
}
Enter fullscreen mode Exit fullscreen mode

Doctrine will automatically create the columns from the Price class into the table of the Product class. By default, it prefixes the database columns after the Embeddable class name, in this case: price_amount and price_currency.

Conclusion

Value objects are useful for writing clean code. Instead of writing:

public function addPhoneNumber(string $phone): void {}
Enter fullscreen mode Exit fullscreen mode

You can write:

public function addPhoneNumber(PhoneNumber $phone): void {}
Enter fullscreen mode Exit fullscreen mode

Which makes it easy to read and reason about it, also you don't need to figure out which phone format you should use.

Since their attributes define them, and you can share them with other different entities, they can be cacheable forever.

They can help you to reduce duplication. Instead of having multiples amount and currency fields, you can use a pure Price class.

Of course, like everything in life, you should not abuse of value objects. Imagine you converting tons of objects to primitive values to store them in the database, or converting those back to value objects when fetching them from the database.Indeed, you can have performance issues. Also, having tons of granular value objects can bloat your codebase.

With value objects, you can reduce the primitive obsession. Use them to represent a field or group of fields of your domain that require validation or can cause ambiguity if you use primitive values.

Thanks for reading, and happy coding!

Further Reading

Top comments (7)

Collapse
 
cnastasi profile image
Christian Nastasi • Edited

Hi, nice article.

I actually wrote one on the same topic recently. Would love to hear your thoughts, even a brief comment from you would mean a lot!

Link to the article

Thanks in advance

Collapse
 
ianrodrigues profile image
Ian Rodrigues

Thanks, @cnastasi!

Collapse
 
vidalvasconcelos profile image
Vidal Vasconcelos

When I was revisiting this post, something took my attention. The hashing comparison example has a trick, that until this moment I didn't know. If the argument's method has the same type of my class, I can access your private methods 😱

Collapse
 
ianrodrigues profile image
Ian Rodrigues

Yes, that is part of OO principles.

Collapse
 
mtamazlicaru profile image
Marius Tamazlicaru

Hi, thanks for the article. Could you please specify what OO principle is this?

Thread Thread
 
ianrodrigues profile image
Ian Rodrigues

Hi @mtamazlicaru, sorry for the very late response. I've been away from dev.to for a while.

This behavior is part of the encapsulation principle in object-oriented programming. Encapsulation is about keeping a class's internal data and implementation details 'enclosed' within the class, and only exposing a public interface to the outside world.

However, a unique aspect of this principle is that in many programming languages, two instances of the same class can access each other's private methods and properties. This is because the class itself is considered the boundary of encapsulation, not the individual instances.

So, when two objects are of the same class, they are allowed to see each other's private parts, so to speak. This feature can be quite useful in certain scenarios, like implementing methods that compare two objects of the same class. Hope this helps clarify things!

Collapse
 
gadnis profile image
Edvinas Gurevicius

Could you show folder structure example for value objects?