Преимущество VO в том, что это значения, которые уже проверены на границы и тип. В вашем примере валидация происходит в геттере - это мягко говоря бессмысленно. Вот вам пример
class UUID
{
/** @var string */
private $value;
/**
* @param string $value
*/
public function __construct(string $value)
{
if (!preg_match('/^[\da-f]{32}$/', $value)) {
throw new \InvalidArgumentException(
sprintf('Argument "$value" must be correct UUID, actual value: "%s"', $value)
);
}
$this->value = $value;
}
/**
* @return string
*/
public function getValue(): string
{
return $this->value;
}
}
Дальше в коде вам достаточно делать type hinting на тип VO и все, значение будет корректным. От рефлексии, или runkit вы все равно защититься не сможете.
-- --
DTO - это штука, для удобного транспорта данных, между разными частями системы. Например у вас есть метод, который на вход принимает 20+ аргументов (например регистрация), вызывать такое кодло вероятно будет не удобно, но собрав dto вы можете его передать одним аргументом и рассчитывать на то, что данные переданы с правильными типами. Граничные же значения придется проверить, так как в задачу "транспорта" не входит контроль правильности данных между системами, что его используют.
но вот внутри в тех частях логика которых фиксирована и не может меняться - вполне подходит массив ключ-значение
Использование KV в контексте DTO/VO - дико хреновая практика, в очень редких кейсах ее использование оправдывает себя. Дело в том, что массив - это набор произвольных данных. Что бы писать надежный код - вам придется на каждом этапе делать проверку правильности этого массива. Что это за проверки?
* все нужные ключи существуют
* все значения по этим ключам правильных типов
* массив не содержит левых данных
как реализовать на php типизированую коллекцию типа как в СИ
class MyTypedCollection implements \Countable, \IteratorAggregate, \ArrayAccess
...