Value Objects in PHP

Explanation:
The equality of two Value Objects is not based on identity (them being the same object, checked with the operator ===), but on their content. Value Objects are almost always immutable so that they can be shared between other domain objects. Mutating a Value Object means creating a new one, often with a Factory Method, that being said “Once set, the value object cannot be modified without changing its identity”.

Benefits:

  • Readability – the types of things that we pass to different parts of our application match to what they represent in our domain and it’s clearer what kind of value is expected without having to check in the code
  • Validation – ensure every existing value object is in a correct state. This means that we don’t have to check them when we use them.
  • Type hinting – we can type hint primitive types for parameters.
  • Immutability – One of the main causes of bugs in software is when the state of an object inadvertently changes, or its reference becomes null. Immutable objects can be passed around to any function and their states will always remain the same.

Value Objects are easier to create, test, use and maintain, they also compose related attributes as an integral unit. Value Objects are completely replaceable when the measurement or description changes and can be compared with others through value equality with Side-Effect-Free behavior.

When creating a new value object we need to make sure that it has a valid state, and that the class can not be extended.

final class Person
{
    /** @var string */
    private $name;
 
    public function __construct(string $name)
    {
        if (strlen($name) < 3 || !ctype_alpha($name)) {
            throw new InvalidArgumentException("Person class name property has to consist of at least three letters");
        }
        $this->name = $name;
    }
 
    public function equals(Person $person): bool
    {
        return $this === $person;
    }
 
    public function getName()
    {
        return $this->name;
    }
}


// Using setters for validation

final class Person
{
    /** @var string */
    private $name;
 
    public function __construct(string $name)
    {
	$this->setName($name);
    }

    private function setName(string $name)
    {
        if (strlen($name) < 3 || !ctype_alpha($name)) {
            throw new InvalidArgumentException("Person class name property has to consist of at least three letters");
        }
        $this->name = $name;
    }
 
    public function equals(Person $person): bool
    {
        return $this === $person;
    }
 
    public function getName()
    {
        return $this->name;
    }

}