Гибкое изменение поведения приложения

German Malinovsky

Новичок
Есть самый простой процесс регистрации пользователя, где он вводит мыло и пароль и сразу же под ними может зайти на сайт. Но понадобилось добавить дополнительную функциональность (которая гдето в админке активируется и деактивируется), что вызывается при завершении регистрации. Например, подтверждение регистрации. Но функциональность подтверждения нельзя явно в пишивать в текущую реализацию регистрации (напр. добавить флаг $confirmationRequired в контроллере и там же добавить все логику с ней связаную). Для этого она вынесена в UserConfirmationListener что сразабывает при событии завершения регистрации.

UserConfirmationListener вклинивается неявно в процесс регистрации, и мне нужно не только изменить сам объект по контракту UserInterface, но и немного изменить поведение ответа приложения(Если раньше нужно было перенаправлять при успешной регистрации по URL /confirmed, то теперь вместо него на URL /confirmation)

Я это сделал через метод setResponse() в UserEvent, и потом в метода registerUser() его возвращаю. Все работает, но мне кажется это слишком грязно. вызывается метод регистрации, в котором могут быть разные события вызваны, но логично было бы возвратить свежесозданные объект $user.

Вынести вызов события можно было бы в контроллер, но оно же должен вызываться непосредственно перед сохранием нового объекта(т.е. перед $user->save())

Так вот вопрос в том как это можно бы улучшить? Т.е. более продумано подменять стандартный Response новым объектом Response или изменять поведение приложения, при этом это было "loose coupling" как я сделал с добавлением Listener, ведь неизвестно сколько еще может быть подобных дополнительных функциональностей.


Псевдокод
PHP:
class UserEvent
{
    public function __construct(UserInterface $user)
    {
        $this->user = $user
    }

    public function getUser()
    {
      return $this->user;
    }
    public function setResponse(ResponseInterface $response)
    {
        $this->response = $response;
    }

    public function getResponse()
    {
        return $this->response;
    }
}

class UserConfirmationListener
{
    //Вызывается при событии user.registration.suсcess
    public function onUserRegistrationComplete(UserEvent $event)
    {
        $user = $event->getUser();
        $user->enabled = false;
        $user->confirmationCode = "random string";

        $response = Response::url("/confirmation");
        $this->setResponse($response);
    }
}

class UserService()
{
    public function registerUser($email, $password, $enable = false)
    {
        $user = new User($email, $password);
        if ($enable)
        {
            $user->enabled = true;
        }

        $event = new UserEvent($user)
        $this->eventDispatcher->fire('user.registration.suсcess', $event);
    
        $user->save();

        if (null !== $response = $event->getResponse())
        {
           return $response;
        }
     
    }
}

class UserController()
{
     public function registerAction()
    {

         if (!empty($_POST))
         {
             //если не возвращали response, то берем стандартный
              if (null === $response = $this->userService->registerUser($_POST['email'], $_POST['password']){
                       $response =  Response::url("/confirmed");
             }
                
             return $response;

           
         }

        return Response::template("path/to/template");
    }

    public function confirmedAction()
    {
        //Показывает страницу, с сообщением об успешной регистрации.
    }

    public function confirmationAction()
    {
        //Показывает страницу, где описано что делать дальше чтобы подтвердить аккаунт и соответвенно завершить регистрацию
    }
}
 
Последнее редактирование:

German Malinovsky

Новичок
Просмотры есть, а ответов нету. Может я сумбурно объяснил суть вопроса? Или может вы как-то по другому решаете эту проблему?
 

fixxxer

К.О.
Партнер клуба
Как-то сложно. Я просто делаю сервис-класс, этакий хелпер, и из контроллера его дергаю.

оно же должен вызываться непосредственно перед сохранием нового объекта(т.е. перед $user->save())
Зачем?
 

German Malinovsky

Новичок
Подробнее можно, хотябы псевдо код какой-то. Сервис-класс что делает? У меня есть сервис для работы с регистрацией.
В сервисе идет и формирование модели из запроса и ее сохранение. Очень часто нужно перед этим самым сохранением некоторые поля модели изменить (как в моем случае в слушателе $user->enabled = false).
Если вызов события убрать из сервиса (перед $user->save()) и переместить в контроллер, то выйдет что слушатель как изменит поле модели должен будет еще раз сделать $user->save(). Но это мне кажется плохая идея, т.к. изменение модели проходит уже вне сервиса.
 
Последнее редактирование:

German Malinovsky

Новичок
У меня появилась идея, что состоит в том, что в UserConfirmationListener вовсе убрать set/getResponse() и код поменять на приблизительно такой:
PHP:
  public function onUserRegistrationComplete(UserEvent $event)
  {
  $user = $event->getUser();
  $user->enabled = false;
  $user->confirmationCode = "random string";
 
  //Фильтр что применяется после отработки контроллера
  $this->filterChain->addPrepend(function ($request, $response)
  {
  return Response::url("/confirmation");
  });

  }
Т.е. теперь добавить фильтр, что в итоге изменяет поведение на то что нам надо. Но мне кажется есть решение попроще, т.к. такой системы фильтрации у меня не реализовано, и это займет время.
 

fixxxer

К.О.
Партнер клуба
Подробнее можно, хотябы псевдо код какой-то
Да примерно как у тебя UserService, только без выпендрежей. :) Скажем, метод UserService->register(), который все делает и возвращает нужный респонс.

UserService это как ты такой "субконтроллер", которому разрешается быть немного еще и моделью (скажем, обернуть все в транзакцию). Не вижу в этом большой проблемы.
 

keltanas

marty cats
Не надо в ивенте респонс таскать. Лучше сделать флаг, а на основе этого флага в контроллере уже создать респонс.
Конвертацию реквест->респонс вообще должен сам контроллер осуществлять, или его лисенеры.
А сервис авторизации должен в каком-то виде контроллеру результат передать, а не респонс. Про респонс он в целом ничего не знает, как и про реквест.
 

German Malinovsky

Новичок
Ну с флагом я описал что это проще всего, но это подходит если мы эту систему пишем сами, и в контроллере этот флаг ставим. А если такая система позволяет написать расширение за счет листенеров, тогда флагов никаких не может быть, т.к. мы добавляем только свой листенер в отдельном неймспейсе и подписываемся на событие. И это наше расширение ничего не знает о внутренности контроллера и прочем, только о событии. Как тогда изменить респонс? У меня вот толь пока идея о фильтре появилась.
 

hell0w0rd

Продвинутый новичок
Я бы создал onKernelRequest подписчик, который бы проверял все ли ок с юзером. Если что-то не заполнено - редирект на заполнение, если забанен - редирект туда-то, если надо сменить пароль - редирект на смену пароля и тд.
В общем Middleware до контроллера.
 
Сверху