Value Objects in PHP 8: Entities
Tags: php, programming, designpatterns, tutorial
Published: 2023-12-21 08:36:59

## Table of contents
- [Introduction](#introduction)
- [Entities](#entities)
   - [Identity in Entities](#identity)
   - [Mutability and State Changes](#mutability)
   - [Persistence Beyond a Single Transaction](#persistence)
- [Immutable Entities](#immutability)
- [Entity Version](#version)
- [Mixing Entities with Value Objects](#mixing)
- [Practical Considerations: Choosing Between Value Objects and Entities](#considerations)
* [Conclusion](#conclusion)




## <a name='introduction'></a>Introduction

In the previous articles, we learned how to create fundamental value objects and recognized their role in enhancing the stability and reliability of our applications.

This article will delve into another architectural pattern closely connected to value objects: **Entities**.

First, let's refresh our memory on the essential qualities of a **Value Object**.

**Value Objects** are the unsung heroes of our code, bringing _immutability_ and predictability to our applications. They encapsulate small data pieces, ensuring their state remains constant once set. This predictability simplifies our code and contributes to overall system stability.

A **Value Object** describes a value, a measurement or something unitary and atomic. This is why if two Value Objects have the same value, they can be considered the same object (also if they are two different instantiated objects). 

Explaining the concept in simple terms: `50 €` equals `50 €`.
However, `2 apples` are different from `2 pears`.

## <a name='entities'></a>Entities

Despite **Entities** shares a structural similarity with **Value Objects**, they possess distinct characteristics and qualities.

For example, an **Entity** possesses a _unique identity_, allowing it to undergo state changes while retaining its individuality. This distinct identity makes it suitable for scenarios where persistence and mutability are crucial. 

Let's dive deeper into the qualities that define an entity in our PHP applications.

### <a name='identity'></a>Identity in Entities

The initial characteristic defining an entity is its unique identity. Unlike **Value Objects**, **Entities** bear a distinctive mark that distinguishes them. This identity maintains consistency throughout their existence, providing a stable reference point.

An entity's identity can take various forms, typically manifesting as a number or a string (examples include UUID, email, fiscal code). This concept aligns closely with the idea of a *primary key* in databases.

Take the example of a "User" entity in a system. The unique identifier (ID) linked to each user plays a crucial role in defining their identity. This significance endures, facilitating differentiation even when other attributes, such as the username or email, undergo alterations.

 ```php
class User
{
    public function __construct(
        private readonly int $id,
        private string $name,
        private string $email
    ) {
        $this->validateId();
        $this->validateName();
        $this->validateEmail();
    }

    public function getId():int 
    {
        return $this->id;
    }
   
    public function getName():string 
    {
        return $this->name;
    }

    public function getEmail():string 
    {
        return $this->email;
    }

    public function __toString():string
    {
        return "{$this->id} | {$this->name} | {$this->email}";
    }

    // Validators
    // Additional methods for operations involving user data
}

// Usage:
$user = new User(1, 'John Doe', 'john@example.com');

// Output: 1 | John Doe | john@example.com
echo $user; 
```
This example shows a structural difference between an **Entity** and a **Value Object**. Unlike **Value Objects**, **Entities** can't use `public readonly` properties because attributes like name and email might change. Uncontrolled changes are undesirable, so these properties are `private/protected`. Access to them is managed through getter and setter methods. 

A `__toString` method can also help debug your entity easily. 

> Later in the article, we'll explore how to craft an entity with both immutable and mutable aspects, leveraging `public readonly` properties instead of traditional getters and setters.

### <a name='mutability'></a>Mutability and State Changes

Unlike **Value Objects**, **Entities** can undergo state changes during their lifecycle. This mutability grants them the flexibility to adapt to various scenarios, mirroring the dynamic nature of the real entities they represent.

In the context of our "User" entity example, users can dynamically update their profile information, alter their preferences, or modify their roles within a system. The inherent capacity of **Entities** to evolve over time makes them well-suited for modelling elements that experience variations in their attributes.

```php
class User
{
    public function __construct(
        private readonly int $id,
        private string $name,
        private string $email
    ) {
        $this->validateId();
        $this->validateName();
        $this->validateEmail();
    }

    // Getters
    // validators
    // __toString

    public function changeName(string $name): void
    {
        $this->name = $name;

        $this->validateName();
    }

    public function changeEmail(string $email): void 
    {
        $this->email = $email;

        $this->validateEmail();
    }
}

// Usage:
$user = new User(1, 'John Doe', 'john@example.com');

// Output: 1 | John Doe | john@example.com
echo $user; 

$user->changeName('Frank Doe');

// Output: 1 | Frank Doe | john@example.com
echo $user; 

$user->changeEmail('frank@example.com');

// Output: 1 | Frank Doe | frank@example.com
echo $user; 
```

The presented example shows the dynamic nature of the "User" entity. The entity's state is modified through the `changeName` and `changeEmail` methods, allowing users to update their information seamlessly and safely (using validators).
This mutability is a crucial aspect of **Entities**, enabling them to reflect the evolving characteristics of the real-world entities they represent. 

>It's noteworthy that two **Entities**, even if their attribute values differ, can be recognized as the same entity when sharing a common identifier. This suggests the possibility of referencing the same entity, albeit potentially representing different versions.

>This lays the groundwork for exploring the concept of an "entity version," a topic we'll delve into in a subsequent chapter.

### <a name='persistence'></a>Persistence Beyond a Single Transaction

**Entities** exhibit persistence beyond a single transaction or request, ensuring their endurance through various interactions with the system. This persistence proves invaluable when modelling elements requiring consistent storage, retrieval, and referencing.

Consider an "Order" entity within an e-commerce system. Following a purchase, the order retains its identity, and all associated details, such as purchased items and the customer, persist within the system's database. This enduring presence allows for future reference and retrieval, maintaining a comprehensive record of transactions.

Similarly, once registered, a "User" will be persisted and retrieved each time access to the system occurs.

When storing **Entities**, a recommended pattern is the **Repository Pattern**. I will delve into it more extensively in the future, but for now, suffice it to know that it abstracts the concept of an "_entity collection_". 

In an **entity collection**, you can _add_, _update_ or _delete_ an entity or _retrieve_ it using the identifier. In an evolved collection, you can also search by some criteria.

So, we first define a contract with methods like `add`, `update`, `delete` and `findById` for managing and retrieving **Entities**. 

```php
interface UserRepository 
{
    public function add(User $user):void;

    public function update(User $user):void;

    public function delete(User $user):void;

    public function findById(int $id):?User;

    public function findByEmail(string $email):?User;
}
```
Then, let the interface concrete implementation take care of the persistence and be responsible for the consistency of the data. This means that the concrete implementation has to ensure that everything will be done in a single transaction: if more than one write is necessary and an error occurs, then a rollback must be done. **_No broken data should be left behind_**.

Also, the code that will use the contract is unaware of where and how the entity will be stored, which could be anywhere (in a database, a file, or a remote service). 

This pattern will also improve your testing suite a lot. Switching your concrete implementation with an in-memory repository will dramatically reduce the time needed to run a functional test suite.

## <a name='immutability'></a>Immutable Entities
As we hinted at in the preceding chapters, one viable approach enabled by the use of `public readonly` properties is immutability, as we've already seen in the two previous articles of this series.

This implies that a new instance is generated with every modification to the Entity. Here's how the "User" entity might appear using this approach.

```php
class User
{
    public function __construct(
        public readonly int $id,
        public readonly string $name,
        public readonly string $email
    ) {
        $this->validateId();
        $this->validateName();
        $this->validateEmail();
    }

    // Validators
    // __toString

    public function changeName(string $name): User
    {
        return new User($this->id, $name, $this->email);
    }

    public function changeEmail(string $email): User 
    {
        return new User($this->id, $this->name, $email);
    }
}

// Usage:
$user = new User(1, 'John Doe', 'john@example.com');

// Output: 1 | John Doe | john@example.com
echo $user; 

$newUser = $user->changeName('Frank Doe')
                ->changeEmail('frank@example.com');

// Output: 1 | Frank Doe | frank@example.com
echo $newUser; 

// Output: 1 | John Doe | john@example.com
echo $user;  
```
The first noticeable change is that now the properties are no longer private but `public` and `readonly`. This implies that getter methods are no longer necessary, and information access could be performed directly using the properties.

The second change is that the `changeName` and `changeEmail` methods now return a new "User" instance, allowing us to use a fluent syntax and chain multiple modifications in succession.

The last observation is that the data remains unchanged in the initial "User" instance. Consequently, we end up with two instances of the same entity (as they share the same identifier) but with different data. This could lead to errors if not handled carefully. A potential solution to this issue will be addressed shortly in the "Entity Version" chapter.

However, the drawback of this approach lies in the fact that, especially in highly structured and large **Entities**, creating a new object instance might not be efficient, especially if done frequently. Still, it could be an interesting approach to ensure the robustness of your application. 

## <a name='version'></a>Entity Version

As we've seen in the previous chapters, when comparing **Entities**, the only parameter available is their identifier. Determining if two instances of the same entity possess identical data becomes challenging when internal data changes.

One might initially consider using a counter that increments upon internal data modification. The implementation could look something like this:

```php
class User
{
    public function __construct(
        public readonly int $id,
        public readonly string $name,
        public readonly string $email,
        public readonly int $version = 0
    ) {
        $this->validateId();
        $this->validateName();
        $this->validateEmail();
    }

    // Validators
    // __toString

    public function changeName(string $name): User
    {
        return new User($this->id, $name, $this->email, $this->version + 1);
    }

    public function changeEmail(string $email): User 
    {
        return new User($this->id, $this->name, $email,  $this->version + 1);
    }
}
```

However, this solution presents several issues and cannot be effectively used to ascertain the strict equality of two **Entities**:

```php
$user = new User(1, 'John Doe', 'john@example.com');

$newUser1 = $user->changeName('Frank Doe');
$newUser2 = $user->changeEmail('frank@example.com');

// Output: 1 | John Doe | john@example.com | v0
echo $user;  

// Output: 1 | Frank Doe | john@example.com | v1
echo $newUser1; 

// Output: 1 | John Doe | frank@example.com | v1
echo $newUser2;

// Output: bool(true)
var_dump($newUser1->version === $newUser2->version);
```

We need to find another solution. An intriguing approach is to create a non-random hash string based on entity data, providing a stable and comparable version identifier. A simple way to achieve this is as follows:

```php
class User
{
    public readonly string $version;

    public function __construct(
        public readonly int $id,
        public readonly string $name,
        public readonly string $email,
    ) {
        $this->validateId();
        $this->validateName();
        $this->validateEmail();

        $this->version = $this->calculateVersion();
    }

    // Validators
    // __toString
    // setter
    
    private function calculateVersion(): string 
    {
         return md5((string)$this);
    }
}

$user = new User(1, 'John Doe', 'john@example.com');

$newUser1 = $user->changeName('Frank Doe');
$newUser2 = $user->changeEmail('frank@example.com');
$newUser3 = $newUser1->changeName('John Doe');

var_dump($user->version === $newUser1->version);     // false
var_dump($user->version === $newUser2->version);     // false
var_dump($newUser1->version === $newUser2->version); // false
var_dump($user->version === $newUser3->version);     // true
```
Of course, many different hash functions could be used for this purpose, but I just wanted to demonstrate the concept, so I used the simplest one. With this trick, by the way, some optimization could be done. For example, store an entity only if changed or decide if a cache is invalid and needs a refresh, using the hash as a reference.

## <a name='mixing'></a>Mixing Entities with Value Objects

In all my examples, I have consistently used primitive properties for simplicity. However, nothing forbids us from applying what we've learned in the previous articles and incorporating **Value Objects** within our **Entities**.

Here's an example of the "User" entity transformed using **Value Objects**:

```php
class User
{
    public readonly string $version;

    public function __construct(
        public readonly UserId $id,
        public readonly UserName $name,
        public readonly Email $email,
    ) {
        $this->version = $this->calculateVersion();
    }

    public function changeName(UserName $name): User
    {
        return new User($this->id, $name, $this->email);
    }

    public function changeEmail(Email $email): User 
    {
        return new User($this->id, $this->name, $email);
    }

    public function __toString(): string
    {
        return "{$this->id} | {$this->name} | {$this->email}";
    }

    private function calculateVersion(): string 
    {
         return md5((string)$this);
    }
}
```

Property validation essentially disappears in this adaptation. The rest of the class remains quite similar to the version with primitives. However, it carries the advantage of centralizing validation rules for certain properties repeated across multiple **Entities**.
Of course, like the **Composite Value Objects**, we might need to validate business rules between correlated properties.

## <a name='considerations'></a>Practical Considerations: Choosing Between Value Objects and Entities

Understanding when to use **Value Objects** versus **Entities** is pivotal for effective system design. Here are some practical considerations:

### Use Value Objects When:
- The concept you're modelling doesn't require a unique identity.
- Immutability and predictability are paramount.
- You're dealing with smaller, isolated pieces of data.

### Use Entities When:
- A unique identity is crucial for the concept.
- The element undergoes state changes during its lifecycle.
- Persistence and adaptability are significant requirements.

## <a name='conclusion'></a>Conclusion

In conclusion, this article explored the architectural pattern of **Entities** closely connected to **Value Objects** in PHP applications. The fundamental qualities of Value Objects, such as immutability and predictability, were revisited to set the stage for understanding **Entities**.

**Entities**, distinguished by their unique identity and mutability, were examined in-depth, showcasing how **Entities** can effectively model real-world entities, allowing for state changes while maintaining a stable identity. 
The persistence of **Entities** beyond a single transaction was discussed, emphasizing their importance in scenarios requiring consistent storage and retrieval.

Implementing Immutable **Entities** using `public readonly` properties was presented as an alternative approach. This approach involves creating new instances for each modification, ensuring the integrity of the original data. The concept of an "entity version" was introduced, exploring methods to handle changes in entity data over time.

The article also demonstrated the integration of **Value Objects** within **Entities**, showcasing how these patterns can collaborate to enhance code clarity and maintainability. Practical considerations for choosing between **Value Objects** and **Entities** were outlined, guiding developers based on specific system requirements.

Ultimately, the choice between **Value Objects** and **Entities** depends on the nature of the concepts being modelled and the desired system characteristics. 

By mastering these architectural patterns, PHP developers can design robust, predictable, and adaptable applications that align with best practices in software architecture.