clean-code-php
clean-code-php copied to clipboard
reusable value-objects w/ better type safety
I didn't really understand the point of traits until recently and now I can't live without them. And since I didn't find anything about traits on here, I wanted to share my findings. Is this something you are interested in?
In this first example, the two arguments workEmail
and personalEmail
are switched. But according to the signature of updateEmail
, everything is in order since both arguments are of type string:
final class User
{
public function updateEmail(string $workEmail, string $personalEmail): void
{
/* ... */
}
}
$user = new User();
$user->updateEmail('[email protected]', '[email protected]');
Here we created the value-object EmailTrait
to wrap any email-value. But instead of a class, the value-object is a trait. We re-use the trait in WorkEmail
and PersonalEmail
. Both have the same behavior as EmailTrait
but have their own type WorkEmail
and PersonalEmail
respectively.
trait EmailTrait
{
private string $email;
private function __construct(string $email)
{
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new \InvalidArgumentException(/* ... */);
}
$this->email = $email;
}
public static function fromString(string $email): self
{
return new self($email);
}
public function toString(): string
{
return $this->email;
}
}
final class WorkEmail
{
use EmailTrait;
}
final class PersonalEmail
{
use EmailTrait;
}
Now in this second example, the signature of updateEmail
is absolutely clear about its argument types and can't be misused:
final class User
{
public function updateEmail(WorkEmail $work, PersonalEmail $personal): void
{
/* ... */
}
}
$work = WorkEmail::fromString('[email protected]');
$personal = PersonalEmail::fromString('[email protected]');
$user = new User();
$user->updateEmail($work, $personal);
This is especially helpful, when there are a lot of primitive values that behave essentially the same, but represent different things in the domain (IDs, postal addresses, event-objects etc.).
Hello, You can achieve the same result with inheritance. Can you explain why you prefer to use traits ? Thanks
@sebastianstucke87 Because you didn't find any article about traits, here us one that could help to understand more features about traits. Overriding & Extending a PHP Trait Method | Andy Carter https://andy-carter.com/blog/overriding-extending-a-php-trait-method
Using traits in Value Objects is an indicator that you are using Value Objects incorrectly. A Value Object is a Minimum Viable Universal Unit, not a Unique Unit. It is not good to create a unique Value Object for each variable (DRY). An exception can be Value Objects for identifiers for which the uniqueness of the type is important, and the code is almost always identical.
final class Email
{
private string $email;
public function __construct(string $email)
{
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new \InvalidArgumentException(/* ... */);
}
$this->email = $email;
}
public function getEmail(): string
{
return $this->email;
}
public function __toString(): string
{
return $this->email;
}
}
final class User
{
public function updateEmail(Email $work, Email $personal): void
{
/* ... */
}
}
$user->updateEmail(
new Email('[email protected]'),
new Email('[email protected]'),
);
For more control over the data structure, you can use the Value Object to describe a group of object properties.
final class UserContactData
{
private Email $work;
private Email $personal;
public function __construct(Email $work, Email $personal)
{
$this->work = $work;
$this->personal = $personal;
}
public function getWorkEmail(): Email
{
return $this->work;
}
public function getPersonalEmail(): Email
{
return $this->personal;
}
}
final class User
{
private UserContactData $contact_data;
public function changeContactData(UserContactData $contact_data): void
{
$this->contact_data = $contact_data
}
}
$user->changeContactData(new UserContactData(
new Email('[email protected]'),
new Email('[email protected]'),
));
If you are using the Doctrine, then you can describe it through Embeddables.
The topic of traits has already been raised in another issue #130. So far, there are no adequate examples of using traits. I would also like to see a good example of using traits here.