
Um value object em PHP é um tipo que encapsula dados e que se distingue apenas pelas suas propriedades. Ao contrário de uma Entidade, não possui um identificador único. Assim, dois value objects em PHP com os mesmos valores de propriedade devem ser considerados iguais.
Bons candidatos a value object são:
- preço
- número de telefone
- morada
- um hash de commit
- um identificador de entidade
- e por aí adiante.
Ao desenhar um value object em PHP, tens de prestar atenção às suas três características principais: imutabilidade, igualdade estrutural e autovalidação.
Exemplo de value object em 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;
}
}Imutabilidade
Depois de instanciares um value object em PHP, ele deve manter-se o mesmo durante o resto do ciclo de vida da aplicação. Se precisares de alterar o seu valor, substituis o objeto por completo.
Usar um value object mutável é aceitável se o mantiveres inteiramente num âmbito local, com uma única referência ao objeto. Fora disso, arriscas-te a ter problemas.
Retomando o exemplo anterior, é assim que atualizas o montante de um tipo Preço:
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);
}
}Igualdade estrutural
Os value objects em PHP não têm identificador. Por outras palavras, se dois value objects tiverem os mesmos valores internos, devem ser considerados iguais. Como o PHP não permite sobrecarregar o operador de igualdade, tens de a implementar tu próprio.
Podes criar um método dedicado para isso:
final class Price
{
// ...
public function isEqualsTo(Price $price): bool
{
return $this->amount === $price->amount &&
$this->currency === $price->currency;
}
}Autovalidação
A validação de um value object acontece no momento da criação. Se uma das propriedades não for válida, é lançada uma exceção. Junta isto à imutabilidade e, depois de criares um value object, tens a certeza de que ele será sempre válido.
Voltando ao exemplo do tipo Preço, um montante negativo não faz sentido no domínio da aplicação:
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;
}
}Conclusão
Os value objects são úteis para escrever código limpo. Em vez de escreveres:
public function addPhoneNumber(string $phone) : void {}Podes escrever:
public function addPhoneNumber(string $phone) : void {}Fica mais fácil de ler e de raciocinar, e deixas de ter de decidir que formato de telefone usar. Como são os atributos que os definem e podes partilhá-los com entidades diferentes, podem ficar em cache para sempre.
Ajudam também a reduzir as duplicações. Em vez de teres vários campos de montante e de moeda, usas uma única classe Preço.
Claro que, como tudo na vida, não convém abusar dos value objects em PHP. Imagina converteres toneladas de value objects para os guardares na base de dados, ou reconverteres tudo em value objects quando os voltas a ler: arriscas-te a ter problemas de desempenho. Além disso, ter toneladas de value objects torna a base de código mais pesada.
Usa-os para representar um campo ou um grupo de campos do teu domínio que exijam validação, ou para evitar ambiguidades, como por exemplo os formatos de telefone.


