
Доменные примитивы
Доменные примитивы — это небольшие Value Object-ы, которые инкапсулируют важное бизнес-значение и правила валидации.
Зачем нужны?
- Гарантия корректности данных — нельзя создать Email без проверки.
- Правила в одном месте — валидация email не расползается по проекту.
- Устойчивость к ошибкам — невозможно случайно передать “строку, которая должна быть email”, но ей не является.
Когда важные значения передаются как обычные строки — например email или UUID — мы создаём себе лишнюю работу.
Пример классического подхода:
$email = $_POST['email'] ?? '';
$service->registerUser($email);
Каждый раз, в каждом месте, где проходит значение, нужно:
- проверить, что поле не пустое
- провалидировать формат email
- нормализовать (обрезать пробелы, привести к lowercase)
- убедиться, что оно прошло все фильтры
- повторить проверки в сервисе
- повторить проверки в репозитории перед записью
Любая пропущенная проверка = баг.
Решение — доменные примитивы
Пример 1
Это email пользователя, который мы получаем из формы и передаём дальше.
Валидируем и нормализуем его теперь только один раз — в конструкторе.
final class Email
{
private string $value;
public function __construct(string $value)
{
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException("Invalid email: $value");
}
$this->value = strtolower(trim($value));
// Здесь можно добавить свои проверки:
// - запрещённые домены
// - длина
// - бизнес-правила
}
// остальные методы...
}
Пример использования
$emailInput = $_POST['email'] ?? null;
if (!$emailInput) {
throw new RuntimeException("Email is required.");
}
try {
$email = new Email($emailInput); // валидация и нормализация происходят здесь
} catch (InvalidArgumentException $e) {
// пример обработки ошибок
// можно вывести сообщение пользователю или записать в лог
die("Ошибка: " . $e->getMessage());
}
$service->registerUser($email);
- дальше по коду email уже гарантированно валиден
- не нужно повторять проверки
- весь код упрощается
Пример 2
Это уникальный идентификатор события (Event), который используется в системе повсеместно.
Раньше приходилось валидировать UUID в контроллере, сервисе, репозитории — теперь только один раз.
final class EventUuid
{
private string $value;
public function __construct(string $value)
{
if (!preg_match('/^[0-9a-fA-F-]{36}$/', $value)) {
throw new InvalidArgumentException("Invalid UUID: $value");
}
$this->value = strtolower($value);
// Здесь можно добавить свои проверки:
// - версия UUID
// - запрещённые значения
// - проверка на корректность формата
}
// остальные методы...
}
Пример использования
$uuidInput = $_GET['event_uuid'] ?? null;
if (!$uuidInput) {
throw new RuntimeException("Event UUID is required.");
}
try {
$uuid = new EventUuid($uuidInput); // строгая проверка прямо здесь
} catch (InvalidArgumentException $e) {
die("Ошибка UUID: " . $e->getMessage());
}
$eventService->load($uuid);
- дальше по системе UUID всегда валидный
- нет повторной валидации
- нет риска случайно передать мусор