Одержимість примітивами (Primitive Obsession) - доменні поняття представлені «голими» рядками, числами й масивами: email - string, гроші - float, телефон - string, діапазон дат - два окремі DateTime.
function transfer(string $fromIban, string $toIban, float $amount, string $currency): void
Проблеми:
- валідація розкидана - кожне місце, що приймає IBAN, мусить перевіряти формат (і одне колись забуде);
- легко переплутати аргументи однакового типу (
$toIban, $fromIban) - компілятор не допоможе; - неявні правила (гроші не можна додавати в різних валютах, сума не від'ємна) повторюються чи пропускаються;
- поведінка розповзається по допоміжних функціях (
formatPhone(),normalizeEmail()).
Ліки - об'єкт-значення (Value Object):
final readonly class Money
{
private function __construct(public int $cents, public Currency $currency) {}
public static function of(int $cents, Currency $currency): self
{
if ($cents < 0) {
throw new InvalidArgumentException('Сума не може бути від\'ємною');
}
return new self($cents, $currency);
}
public function add(self $other): self
{
if ($other->currency !== $this->currency) {
throw new CurrencyMismatch();
}
return new self($this->cents + $other->cents, $this->currency);
}
public function equals(self $other): bool
{
return $this->cents === $other->cents && $this->currency === $other->currency;
}
}
function transfer(Iban $from, Iban $to, Money $amount): void
Властивості об'єкта-значення:
- валідний за побудовою: створити некоректний
Ibanнеможливо - перевірка в одному місці; - незмінний (
readonly): операції повертають новий об'єкт; - рівність за значенням, а не за посиланням;
- поведінка поруч з даними:
$money->add(),$email->domain(),$phone->formatted().
У Laravel: власні касти (CastsAttributes) перетворюють колонки бази на об'єкти-значення й назад:
protected function casts(): array
{
return ['price' => MoneyCast::class, 'email' => EmailCast::class];
}
Енуми PHP - теж ліки від одержимості примітивами для фіксованих наборів значень (статуси, ролі, валюти).
Де зупинитися: не кожен рядок потребує класу. Об'єкт-значення окупається, коли з поняттям пов'язані правила (валідація, операції, форматування) і воно передається між шарами застосунку.
Докладніше в документації: Refactoring.Guru: Primitive Obsession