
Un Value Object en PHP es un tipo que envuelve datos y que se distingue únicamente por sus propiedades. A diferencia de una entidad, no tiene identificador único. Por eso, dos Value Objects en PHP con los mismos valores en sus propiedades deben considerarse iguales.
Buenos candidatos a Value Object son:
- un precio
- un número de teléfono
- una dirección
- un hash de commit
- un identificador de entidad
- y así sucesivamente.
Al diseñar un Value Object en PHP hay que prestar atención a sus tres características principales: inmutabilidad, igualdad estructural y autovalidación.
Ejemplo de Value Object en PHP
final class Price
{
const EU = 'EU';
const CAD = 'CAD';
/** @var float */
private $amount;
/** @var string */
private $currency;
public function __construct(float $amount, string $currency = 'EU')
{
if ($amount < 0) {
throw new InvalidArgumentException("Le montant doit être une valeur positive: {$amount}.");
}
if (!in_array($currency, $this->getAvailableCurrencies())) {
throw new InvalidArgumentException("La devise doit être valide: {$currency}.");
}
$this->amount = $amount;
$this->currency = $currency;
}
private function getAvailableCurrencies(): array
{
return [self::EU, self::CAD];
}
public function getAmount(): float
{
return $this->amount;
}
public function getCurrency(): string
{
return $this->currency;
}
}Inmutabilidad
Una vez instanciado un Value Object en PHP, debe seguir siendo el mismo durante el resto del ciclo de vida de la aplicación. Si necesitas cambiar su valor, tienes que sustituir el objeto entero.
Usar un Value Object mutable es aceptable si lo mantienes por completo en un ámbito local, con una sola referencia al objeto. Fuera de ahí, te expones a problemas.
Retomando el ejemplo anterior, así se actualiza el importe de un tipo precio:
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(
"Vous ne pouvez additionner que les valeurs ayant la même devise: {$this->currency} !== {$price->currency}."
);
}
return new self($this->amount + $price->amount, $this->currency);
}
}Igualdad estructural
Los Value Objects en PHP no tienen identificador. Dicho de otro modo: si dos Value Objects tienen los mismos valores internos, deben considerarse iguales. Como PHP no permite sobrecargar el operador de igualdad, tienes que implementarla tú mismo.
Puedes crear un método dedicado para ello:
final class Price
{
// ...
public function isEqualsTo(Price $price): bool
{
return $this->amount === $price->amount &&
$this->currency === $price->currency;
}
}Autovalidación
La validación de un Value Object debe ocurrir en el momento de crearlo. Si alguna de sus propiedades no es válida, hay que lanzar una excepción. Si a eso le sumas la inmutabilidad, una vez creado el Value Object puedes tener la certeza de que siempre será válido.
Volviendo al ejemplo del tipo Precio: un importe negativo no tiene sentido para el dominio de la aplicación.
final class Price
{
// ...
public function __construct(float $amount, string $currency = 'USD')
{
if ($amount < 0) {
throw new InvalidArgumentException("Le montant doit être une valeur positive: {$amount}.");
}
if (!in_array($currency, $this->getAvailableCurrencies())) {
throw new InvalidArgumentException("La devise doit être valide: {$currency}.");
}
$this->amount = $amount;
$this->currency = $currency;
}
}Conclusión
Los Value Objects sirven para escribir código limpio. En lugar de escribir esto:
public function addPhoneNumber(string $phone) : void {}Puedes escribir esto:
public function addPhoneNumber(string $phone) : void {}Se lee y se razona mejor, y no tienes que averiguar qué formato de teléfono toca usar. Como los definen sus atributos y puedes compartirlos con entidades distintas, se pueden cachear para siempre.
También ayudan a reducir la duplicación. En vez de tener varios campos de importe y de divisa, puedes usar una única clase precio.
Por supuesto, como todo en la vida, no hay que abusar de los Value Objects en PHP. Imagina que conviertes toneladas de Value Objects para guardarlos en la base de datos, o que los vuelves a convertir en Value Objects al recuperarlos: te arriesgas a tener problemas de rendimiento. Además, tener toneladas de Value Objects puede engordar tu base de código.
Úsalos para representar un campo o un grupo de campos de tu dominio que necesiten validación, o para evitar cualquier ambigüedad, como por ejemplo con los formatos de teléfono.


