Текущий пользователь в обработчике события

Проверенные VDS на SSD в Европе и России

Тема в разделе "Вопросы по теории программирования", создана пользователем halva, 18 июн 2017.

  1. halva

    halva Новичок

    Сообщения:
    52
    Ваш город:
    Yekaterinburg, Russia
    Adress:
    Yekaterinburg, Russia
    Country:
    Location on Map:
    Допустим у меня есть система, которая работает под reactphp + PSR-7.
    Есть текущий пользователь (авторизованный или гость). Есть какой-то обработчик события, который обрабатывается на какой-то сущности и в нем есть логика завязанная на текущего пользователя и эту сущность.

    Вопрос, как передать текущего пользователя в обработчик события?
     
  2. Вурдалак

    Вурдалак Newbie

    Сообщения:
    5.862
    Ваш город:
    Russia, Moscow
    Adress:
    Moscow, Russia
    Country:
    Location on Map:
    Можно ввести value object Context, который будет содержать все нужные контекстные данные, и передавать вторым аргументом:
    PHP:
    public function handle(OrderWasCancelled $eventContext $context) {
        
    // $context->getUserId()
    }
    Или я не совсем верно понял вопрос? Какое отношение к событиям имеет PSR-7?
     
  3. Вурдалак

    Вурдалак Newbie

    Сообщения:
    5.862
    Ваш город:
    Russia, Moscow
    Adress:
    Moscow, Russia
    Country:
    Location on Map:
    Если имеется в виду https://github.com/reactphp/http, то не совсем ясно зачем это нужно и в чём проблема (в плане, у тебя есть request, из него ты получаешь куки, и, соответственно, знаешь «текущего пользователя»). Я подумал речь идёт об асинхронных обработчиках событий.
     
  4. fixxxer

    fixxxer К.О.

    Сообщения:
    12.198
    Ваш город:
    Moscow, Russia
    Adress:
    Moscow, Russia
    Country:
    Location on Map:
    Я для этого делал обертку для соединений и реестр этих оберток.

    Типа

    PHP:
    class ConnectionContext
    {
        
    // everything I need
    }

    class 
    Connection
    {

        public function 
    __construct(RatchetConnectionInterface $ratchetConnectionConnectionContext $connectionContext)
        {
            
    $this->conn $ratchetConnection;
            
    $this->setContext($connectionContext);
        }

        ....
    }

    class 
    ConnectionsRegistry
    {

        
    /**
         * @var \SplObjectStorage
         */
        
    private $connections;

        
    /**
         * @param RatchetConnectionInterface $conn
         * @param ConnectionContext $connectionContext
         */
        
    public function registerConnection(RatchetConnectionInterface $connConnectionContext $connectionContext)
        {
            
    $this->connections->attach($conn, new Connection($conn$connectionContext));
        }

        
    /**
         * @param RatchetConnectionInterface $conn
         */
        
    public function unregisterConnection(RatchetConnectionInterface $conn)
        {
            
    $connection $this->find($conn);
            if (
    $connection) {
                
    $connection->close();
            }
            
    $this->connections->detach($conn);
        }

        
    /**
         * @param RatchetConnectionInterface $conn
         * @return Connection|null
         */
        
    public function find(RatchetConnectionInterface $conn)
        {
            if (
    $this->connections->contains($conn)) {
                return 
    $this->connections->offsetGet($conn);
            } else {
                return 
    null;
            }
        }
    }

    class 
    ServerController implements MessageComponentInterface
    {
        ...
        public function 
    onOpen(RatchetConnectionInterface $ratchetConnection)
        {
            
    $this->connectionsRegistry->registerConnection($ratchetConnnetion, new ConnectionContext());
        }

        public function 
    onMessage(RatchetConnectionInterface $ratchetConnection$buffer)
        {
            if (
    null !== $connection $this->connectionsRegistry->find($ratchetConnection)) {
                
    $this->messageRouter->handleMessage($buffer$connection);
            } else {
                
    $this->logger->warning(
                    
    'Got a message from an unregistered connection, closing: ' json_encode((array)$ratchetConnection)
                );
                
    $conn->close();
            }
        }
        ...
    }

    ...

    // ну и где-то там в обработчике action-ов...
    $context $connection->getContext();
    $context->associateUser($user);
    $context->hasAssociatedUser();
    $context->getAssociatedUser();
    #etc
    Но это было давно, может щас и родными средствами протащить контекст можно.
     
    Последнее редактирование: 18 июн 2017
  5. halva

    halva Новичок

    Сообщения:
    52
    Ваш город:
    Yekaterinburg, Russia
    Adress:
    Yekaterinburg, Russia
    Country:
    Location on Map:
    @Вурдалак, про контекст, это моя первая идея, по факту о реестр в рамках запроса. Но проблема заключается в транспортировке этого контекста в нужное место приложения.

    В стандартно php приложении можно объявить реестр с доступом из любого места приложения, допустим как Yii::$app.
    В reactphp же Request-Response, перекликающийся с PSR-7 по своей сути (это к вопросу о том причём тут PSR-7), нельзя писать в переменную статического класса, так как каждый клиентский запрос будет переписывать эту переменную.

    Пока исполняется фронт-контроллер, выполняем экшены, с этим проблем нет. Для этих объектов Request-Response объекты как родные, да и эти объекты не трудно передавать по цепочке вызовов. Но когда появляется нужда, вызывать какие-то события, то в обработчиках по этим событиям нужно учитывать эти Request-Response объекты, передать по цепочке их уже невозможно (именно в этой теме я и пытаюсь выяснить, как их передать, как эту цепочку выстроить). Конечно это возможно, но тогда мне придется Request-объект или Context-объект текущего клиентского запроса подписывать к каждому объекту, который выполняется контексте этого клиентского запроса.

    Я сам пока что не могу придумать архитектурное решение при котором эта проблема решается. Потому и пришел спросить тут, вдруг уже всё придумано и есть где посмотреть.
     
    Последнее редактирование: 18 июн 2017
  6. halva

    halva Новичок

    Сообщения:
    52
    Ваш город:
    Yekaterinburg, Russia
    Adress:
    Yekaterinburg, Russia
    Country:
    Location on Map:
    Я все еще жду совета :)
     
  7. fixxxer

    fixxxer К.О.

    Сообщения:
    12.198
    Ваш город:
    Moscow, Russia
    Adress:
    Moscow, Russia
    Country:
    Location on Map:
    Ну я вроде ответил :) У меня там, конечно, ratchet, но какая нафиг разница.
     
  8. halva

    halva Новичок

    Сообщения:
    52
    Ваш город:
    Yekaterinburg, Russia
    Adress:
    Yekaterinburg, Russia
    Country:
    Location on Map:
    @fixxxer, ну это у тебя по сути контроллер и экшен, на этом уровне проблемы нет. Проблема когда ты начинаешь работать на уровень глубже, а именно с бизнес логикой или инфраструктурной логикой, которая не должна быть завязана на объект запроса. Также у тебя передается зависимость через метод, а представь что у тебя есть ивент менеджер, который может работать на разных уровнях приложения, но при этом вызывать события с контекстом текущего клиентского запроса.

    Походу я пишу одно и тоже второй раз. Допустим есть такой экшен:
    Код:
    public function savePage($request) 
    {
        $pages = $this->getEntityManager()->getRepository('pages');
        $page = $this->getEntityManager()->getEntity('pages');
        $page = $this->hydrateFromRequest($request);
        $pages->save($page);
    }
    
    Где то на сохранении сущности, выполняется обработчик события, допустим такого содержимого
    Код:
    'onSave' => function ($entity, $context) {
        if (!$entity->getUser()) {
            $entity->setUser($context->getCurrentUser());
        }
    },
    
    Вопрос, каким способом можно получить $context в onSave. Я пока что вижу, только такое решение

    Код:
    public function savePage($request) 
    {
        $pages = $this->getEntityManager()->getRepository('pages');
        $page = $this->getEntityManager()->getEntity('pages');
        $page->getEventManager()->setContext($request->getAttribute('context'));
        $page = $this->hydrateFromRequest($request);
        $pages->save($page);
    }
    
    где, для каждой сущности $page, да и любой сущности будет свой ивент менеджер.

    Код:
    public function getEventManager()
    {
        if (!$this->eventManager) {
            $this->eventManage = new \EventManager;
        }
      
        return $this->eventManager;
    }
    

    Но как по мне это фуууу.
     
    Последнее редактирование: 21 июн 2017
  9. Вурдалак

    Вурдалак Newbie

    Сообщения:
    5.862
    Ваш город:
    Russia, Moscow
    Adress:
    Moscow, Russia
    Country:
    Location on Map:
    @halva тебя интересует как пробросить объект контекста в какой-то конкретной библиотеке, но почему-то ты заводишь задачу, говоря про reactphp и PSR-7.
     
  10. halva

    halva Новичок

    Сообщения:
    52
    Ваш город:
    Yekaterinburg, Russia
    Adress:
    Yekaterinburg, Russia
    Country:
    Location on Map:
    Я говорю о них, что бы не описывать бэкграунд из архитектурных решений и ограничений вытекающих из этих решений. Меня интересует как пробросить контекст, а не только как пробросить контекст в конкретной библиотеке. Если в какой-то библиотеке такое пробрасывание контекста уже есть, я не против ссылок на код или описание механизмов такого пробрасывания.
     
  11. Вурдалак

    Вурдалак Newbie

    Сообщения:
    5.862
    Ваш город:
    Russia, Moscow
    Adress:
    Moscow, Russia
    Country:
    Location on Map:
    Если очень упрощённо, то мы делаем
    PHP:
    // BEGIN TRANSACTION
    $user $this->userRepository->find(...); // SELECT ... FOR UPDATE
    $user->doSomething();
    $events $user->getEvents();
    $this->userRepository->save($user);
    // COMMIT
    $this->eventDispatcher->dispatch($events$context);
    Ну и в dispatcher просто можно заюзать любой message bus, хоть Symfony EventDispatcher, упаковав объект события + context в одно мета-событие. Потом при вызове listener'а прокинуть объекты в аргументы. Но я подозреваю, что тебе это не особо интересно, потому что ты ищешь какой-то секретный универсальный способ пробрасывания контекста, именно по этой причине ты настаиваешь на том, что тебя интересует просто «как пробросить контекст», а в какой библиотеке — не важно. Если Doctrine не поддерживает такого концепта, то красиво сделать вряд ли выйдет.
     
  12. fixxxer

    fixxxer К.О.

    Сообщения:
    12.198
    Ваш город:
    Moscow, Russia
    Adress:
    Moscow, Russia
    Country:
    Location on Map:
    @halva, все твои проблемы сводятся к глобальным переменным (или статике/синглтонам, что одно и то же). Не используй их, и все будет хорошо.

    Что происходит в обычном случае, без всяких реактов, при обработке запроса? Создается IoC-контейнер и дальше понеслась. Тут ровно то же самое, только раз у тебя пачка реквестов в рамках одного процесса, то и контейнеров будет столько же.
     
  13. halva

    halva Новичок

    Сообщения:
    52
    Ваш город:
    Yekaterinburg, Russia
    Adress:
    Yekaterinburg, Russia
    Country:
    Location on Map:
    @fixxxer, ну да, видимо все сводится к тому что на каждый реквест придется делать свой IoC-контейнер.

    Советовался сегодня с джавистом. Он сказал, что такую проблему решает созданием реестра реквестов, где ключ это id потока. Такой бы вариант подходил если бы reactphp был многопоточный, но он однопоточный и асинхронный.
     
  14. fixxxer

    fixxxer К.О.

    Сообщения:
    12.198
    Ваш город:
    Moscow, Russia
    Adress:
    Moscow, Russia
    Country:
    Location on Map:
    Реестр реквестов (а точнее соединений) - это ровно то, что я продемонстрировал примером кода выше. Ключом вполне может быть и объект (потому там и SplObjectStorage). Многопоточность тут вообще не имеет значения. Без многопоточности даже проще - не нужно думать о thread safety.

    IoCC достаточно клонировать существующий. Ну или сделать цепочку PerRequestContainer->AppContainer. Глобальные инстансы ищутся в общей пачке, зависящее от конкретного реквеста - в локальной. Учитывая, что "синглтоны" не должны зависеть от реквеста, в локальной пачке, по идее, будет только реквест (и, возможно, соединение, если оно где-то может быть нужно). Создавать PerRequestContainer при получении запроса, типа $perRequestContainer = ContainerFactory::assembleForRequest($request, $appContainer). Дальше резолвишь в этом perRequestContainer что у тебя там первым (фронт-контроллер? роутер? пер-реквест-бутстрапер)? ну и по цепочке понеслась.
     
    Последнее редактирование: 23 июн 2017
  15. halva

    halva Новичок

    Сообщения:
    52
    Ваш город:
    Yekaterinburg, Russia
    Adress:
    Yekaterinburg, Russia
    Country:
    Location on Map:
    @fixxxer, Понятно. Делегирование общего контейнера в контейнер запроса выглядит понятнее чем клонирование, да и таким разделением можно описывать карты зависимостей отдельно для контейнера запроса и общего контейнера. Из минусов, конечно же лишние вызовы.
     
  16. fixxxer

    fixxxer К.О.

    Сообщения:
    12.198
    Ваш город:
    Moscow, Russia
    Adress:
    Moscow, Russia
    Country:
    Location on Map:
    Ну, я на ходу придумываю, сам-то такого не делал, нужды не было. Но особых проблем не вижу в этой идее.
     
  17. halva

    halva Новичок

    Сообщения:
    52
    Ваш город:
    Yekaterinburg, Russia
    Adress:
    Yekaterinburg, Russia
    Country:
    Location on Map: