Подтверждение регистрации

Добавление статуса ожидания и активности пользователя. Реализация команды подтверждения регистрации по токену из ссылки в электронном письме.

Скрытый контент
Комментарии (14)
Denis

Я немного не понял зачем в конструкторе по умолчанию ставить статус? А если мне потребуется из массива заполнить класс User данными? Получается, что статус будет не активный, но а пользователь на самом деле активен. Т.е. через конструктор я не смогу указать текущий статус? Поясните, пожалуйста, в чем дело.

Ответить
Дмитрий Елисеев

А если мне потребуется из массива заполнить класс User данными?

Конструктор используется по назначению только для первого создания объекта. Как и другие методы он содержит свою бизнес-логику, по которой может присваивать первоначальные значения, бросать исключения и генерировать события.

В случае же восстановления объекта по массиву из БД конструктор не используют. Вместо этого объект там создаётся без конструктора и заполняется данными прямо в приватные поля через рефлексию.

Например, пишем гидратор или находим любой готовый вроде такого:

class Hydrator
{
    public function hydrate(string $class, array $data): object
    {
        $reflection = new \ReflectionClass($class);
        $target = $reflection->newInstanceWithoutConstructor();
        foreach ($data as $name => $value) {
            $property = $reflection->getProperty($name);
            $property->setAccessible(true);
            $property->setValue($target, $value);
        }
        return $target;
    }
}

и с помощью него десериализуем объект текущими данными из БД:

$row = $stmt->fetch(PDO::FETCH_ASSOC);

$user = $this->hydrator->hydrate(User::class, [
    'id' => new Id($row['id']),
    'status' => new Status($row['status']),
]);

Если же нужно просто в разных юзкейсах создавать объект по-разному, то в этом случае можно сделать несколько именованных конструкторов, как мы делали в видео о конструкторах. В будущем эпизоде про регистрацию через соцсети мы сделаем именно так.

Ответить
Denis

Спасибо за ответ. Сегодня обязательно видео посмотрю.

Ответить
Сергей

Всем привет. Дмитрий, добрый день. Вот у меня такой вопрос возник. У нас хендлеры не могут быть описаны каким-то единым интерфейсом, т.к. на вход принимают разные команды. Точнее можно сделать команды , реализующие некий интерфейс или унаследованные от какой-то абстрактной команды, но тут уже будет какой-то костыль.

И вот у меня возникла потребность задекорировать все хендлеры, добавив логирование начала и конца работы каждого хендлера. Как-то это можно реализовать без костылей?

Ответить
Дмитрий Елисеев

В языках с дженериками можно указать общий интерфейс.

interface Handler<T> {
    public void handle(<T> command)
}

В PHP без шаблонов это пока не работает.

В любом языке можно сделать свой интерфейс CommandBus для запуска команд и декорировать уже шину. В PHP можно взять league/tactician и реализовать логирование через middleware в ней.

Ответить
Сергей

Не подскажете еще как быть с автовайрингом? Пытался внедрить симфонячий мессенджер. Указал в конфиге

MessageBusInterface::class => static function (ContainerInterface $container): MessageBusInterface {
    return new MessageBus([
        new HandleMessageMiddleware(new HandlersLocator([
            EmissionCommand::class => [$container->get(EmissionHandler::class)],
            TransferCommand::class => [$container->get(TransferHandler::class)],
        ])),
    ]);
}

Но если в хендлер внедряется MessageBusInterface, то получается Circular dependency. И вот что-то ума не приложу как эту беду победить.

Ответить
Дмитрий Елисеев

Переделать Locator на доставание хендлера только по требованию:

new HandlersLocator([
    EmissionCommand::class => EmissionHandler::class,
    TransferCommand::class => TransferHandler::class,
], $container)
Ответить
Сергей

Спасибо, Дмитрий. Пришлось переписать локатор и всё заработало.

Ответить
Евгений

Дмитрий, вы показали как вынести в отдельный класс операции со статусами.

Но есть ещё несколько часто используемых операций:

  • Сериализация статуса в код и обратно
  • Текстовое название статуса. Получение по коду статуса.
  • По названию получить код (для обработки форм с Тильды пришлось такое добавить)
  • Список доступных статусов (код => название)

Как бы вы решили такую задачу в рамках DDD ?

Ответить
Дмитрий Елисеев

Обычно название статуса нужно только для отображения на фронтенде. Домену интересен только код. Поэтому все эти преобразования будут только снаружи в контроллерах или в файлах переводов. Поэтому если для сериализации используется пакет вроде symfony/serializer, то для статуса легко пишется кастомный сериализатор.

Ответить
Ivan

Почему мы не используем mock в unit тестах? Когда вообще надо использовать мок? Нафига он нужен в принципе?

Ответить
Дмитрий Елисеев

Моки и стабы будут в 17-ом эпизоде, когда с их помощью нужно будет имитировать зависимости.

Ответить
Ivan
public function confirmJoin(Token $token)
{
    if (is_null($this->joinConfirmToken)) {
        throw new DomainException("User has already been confirmed!");
    }
    $this->joinConfirmationToken->validate($token);
    $this->status = Status::active();
    $this->joinConfirmationToken = null;
}

if (is_null($this->joinConfirmToken)) - такого не может быть ведь мы в Сommand ищем юзера по токену

Как мы дойдем до "User has already been confirmed!" если в этом случае токен null, а мы ищем юзера по токену?

Не является ли это логической ошибкой? Дмитрий

Ответить
Дмитрий Елисеев

Сейчас confirmJoin мы используем только в обработчике команды, поэтому проверка выглядит избыточно. Но скоро начнём его вызывать в UserBuilder и в фикстурах для заполнения БД демо-данными для разработки и для тестов. И тогда появятся проблемы, если такой проверки у нас не будет.

Поэтому любому методу должно быть всё равно, откуда и как его вызывают выше. Все проверки в нём должны быть всегда.

Ответить
Зарегистрируйтесь или войдите чтобы оставить комментарий