
Value object w PHP to typ, który opakowuje dane i odróżnia się wyłącznie swoimi właściwościami. W przeciwieństwie do encji nie ma unikalnego identyfikatora. Dwa value objecty o tych samych wartościach właściwości muszą więc zostać uznane za równe.
Dobrymi kandydatami na value objecty są:
- cena
- numer telefonu
- adres
- hash commita
- identyfikator encji
- i tak dalej.
Projektując value object w PHP, zwróć uwagę na jego trzy główne cechy: niezmienność, równość strukturalną i samowalidację.
Przykład value object w 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;
}
}Niezmienność
Raz utworzony value object musi pozostać taki sam przez resztę cyklu życia aplikacji. Jeśli trzeba zmienić jego wartość, podmieniasz cały obiekt.
Mutowalny value object jest do przyjęcia, jeśli używasz go w całości w zasięgu lokalnym, z jedną referencją do obiektu. W przeciwnym razie ryzykujesz kłopoty.
Wracając do poprzedniego przykładu, tak zaktualizujesz kwotę w typie ceny:
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);
}
}Równość strukturalna
Value objecty w PHP nie mają identyfikatora. Innymi słowy: jeśli dwa value objecty mają te same wartości wewnętrzne, trzeba je uznać za równe. Ponieważ PHP nie pozwala przeciążyć operatora równości, musisz zaimplementować to sam.
Możesz stworzyć do tego dedykowaną metodę:
final class Price
{
// ...
public function isEqualsTo(Price $price): bool
{
return $this->amount === $price->amount &&
$this->currency === $price->currency;
}
}Samowalidacja
Walidacja value objectu musi się odbyć przy jego tworzeniu. Jeśli któraś właściwość jest niepoprawna, trzeba rzucić wyjątek. Razem z niezmiennością daje to pewność, że raz utworzony value object zawsze będzie poprawny.
Wracając do typu Cena: dla domeny aplikacji ujemna kwota nie ma sensu:
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;
}
}Podsumowanie
Value objecty pomagają pisać czysty kod. Zamiast pisać:
public function addPhoneNumber(string $phone) : void {}możesz napisać:
public function addPhoneNumber(string $phone) : void {}Czyta się to i rozumuje o tym łatwiej, a ty nie musisz zgadywać, jakiego formatu telefonu użyć. Skoro definiują je atrybuty i możesz je współdzielić między różnymi encjami, dają się cache’ować na zawsze.
Pomagają też ograniczyć duplikaty. Zamiast trzymać wiele pól na kwotę i walutę, użyjesz jednej klasy ceny.
Oczywiście, jak ze wszystkim w życiu, z value objectami w PHP nie należy przesadzać. Wyobraź sobie, że konwertujesz ich tony przy zapisie do bazy danych i konwertujesz z powrotem przy odczycie: wtedy grożą ci problemy z wydajnością. Do tego mnóstwo value objectów potrafi rozdąć bazę kodu.
Używaj ich do reprezentowania pola albo grupy pól twojej domeny, które wymagają walidacji, lub do usunięcia niejednoznaczności, jak w przypadku formatów numeru telefonu.


