CurrentUser Events

Not sure if this is a bug or I am doing something wrong or not doing something - suspect one of last 2 options, but …

When I use BeforeLogin, AfterLogin, BeforeLogout, and AfterLogout events, the only one that the Identity is correct in the handler is AfterLogin.

I have tried separate classes for each event handler, same class with different methods, and even same class with same method - the same thing happens in each case.

The event passed to the AfterLogin handler contains the identity of the user that logged in. In all other cases the event contains an Identity object with null for the user. Yet in each case CurrentUser creates the event with the Identity of the user.

I have tried debugging this and the Identity seems to be correct up to the Injector.

I have looked through config settings but can not see anything that would affect this; it may be that I have missed a setting.

What am I doing/not doing right/wrong?

Without sample code and explanation as to how you listen to event no one can really help

1 Like

Hi @evstevemd
Thanks for your suggestions. Of course you weren’t to know, but I have traced through code, read and re-read documentation, searched the internet (including StackOverflow), etc.

As far as I can see, the change in the identity happens within Yii code.

The event listeners are setup in config as specified the documentation.

// use statements here
return [
    AfterLogin::class => [
        [AfterLoginHandler::class, 'handle']
    ],
    AfterLogout::class => [
        [AfterLogoutHandler::class, 'handle']
    ],
];

The handlers are:

class AfterLoginHandler
{

    public function handle(Event $event): void
    {
        $identity = $event->getIdentity(); // identity is the real identity with correct user id, etc
       // do other stuff
    }
}

class AfterLogoutHandler
{

    public function handle(Event $event): void
    {
        $identity = $event->getIdentity(); // identity is not the correct identity, user id is null
       // do other stuff
    }
}

The identity object is as shown in the demo code:


#[Entity(repository: IdentityRepository::class)]
class Identity implements CookieLoginIdentityInterface
{
    #[Column(type: 'primary')]
    private ?int $id = null;
    #[Column(type: 'string')]
    private string $authKey;
    #[Column(type: 'int', name: 'user_id')]
    private ?int $userId = null;
    #[BelongsTo(target: User::class, nullable: false, load: 'eager')]
    private ?User $user = null;


    public function __construct()
    {
        $this->regenerateCookieLoginKey();
    }

    public function getId(): ?string
    {
        return $this->user?->getId();
    }

    public function getCookieLoginKey(): string
    {
        return $this->authKey;
    }

    public function getUser(): ?User
    {
        return $this->user;
    }

    public function validateCookieLoginKey(string $key): bool
    {
        return $this->authKey === $key;
    }

    public function regenerateCookieLoginKey(): voi

Tracing through AfterLogin:
Identity is correct here
imaged
    {
        $this->authKey = Random::string(32);
    }
}

The rest of the relevant code is Yii.

CurrentUser:

    public function login(IdentityInterface $identity): bool
    {
        if ($this->beforeLogin($identity)) { // $identity is correct here, but *not* when it reaches the event handler
            $this->switchIdentity($identity);
            $this->afterLogin($identity);  // $identity is correct here, but *and* when it reaches the event handler
        }

        return !$this->isGuest();
    }

    public function logout(): bool
    {
        if ($this->isGuest()) {
            return false;
        }

        $identity = $this->getIdentity();

        if ($this->beforeLogout($identity)) { // $identity is correct here, but *not* when it reaches the event handler
            $this->switchIdentity($this->guestIdentityFactory->create());
            $this->afterLogout($identity); // $identity is correct here, but *not* when it reaches the event handler
        }

        return $this->isGuest();
    }

   private function beforeLogin(IdentityInterface $identity): bool
    {
        $event = new BeforeLogin($identity);
        /** @var BeforeLogin $event */
        $event = $this->eventDispatcher->dispatch($event); // $identity is correct here
        return $event->isValid();
    }

    private function afterLogin(IdentityInterface $identity): void
    {
        $this->eventDispatcher->dispatch(new AfterLogin($identity));  // $identity is correct here
    }

Yii’s events

final class AfterLogin
{
    private IdentityInterface $identity;

    public function __construct(IdentityInterface $identity)
    {
        $this->identity = $identity;
    }

    public function getIdentity(): IdentityInterface
    {
        return $this->identity; // returns the correct identity
    }
}

final class AfterLogout
{
    private IdentityInterface $identity;

    public function __construct(IdentityInterface $identity)
    {
        $this->identity = $identity;
    }

    public function getIdentity(): IdentityInterface
    {
        return $this->identity; // returns an identity with null userId
    }
}

In event Dispatcher

    public function dispatch(object $event): object
    {
        /** @var callable $listener */
        foreach ($this->listenerProvider->getListenersForEvent($event) as $listener) {
            if ($event instanceof StoppableEventInterface && $event->isPropagationStopped()) {
                return $event;
            }

            $spoofableEvent = $event;
            $listener($spoofableEvent); // $event contains correct identity
        }

        return $event;
    }

Tracing through AfterLogin:
Identity is correct here
image

In ListenerCollectionFactory

    public function create(array $eventListeners): ListenerCollection
    {
        $listenerCollection = new ListenerCollection();

        foreach ($eventListeners as $eventName => $listeners) {
            if (!is_string($eventName)) {
                throw new InvalidEventConfigurationFormatException(
                    'Incorrect event listener format. Format with event name must be used.'
                );
            }

            if (!is_iterable($listeners)) {
                $type = get_debug_type($listeners);

                throw new InvalidEventConfigurationFormatException(
                    "Event listeners for $eventName must be an iterable, $type given."
                );
            }

            /** @var mixed */
            foreach ($listeners as $callable) {
                $listener =
                    /** @return mixed */
                    fn (object $event) => $this->injector->invoke( // $event has correct identity
                        $this->callableFactory->create($callable),
                        [$event]
                    );
                $listenerCollection = $listenerCollection->add($listener, $eventName);
            }
        }

        return $listenerCollection;
    }

Tracing through AfterLogin:
Indentity is correct here

And it is correct in the handler

Tracing through AfterLogout:
Identity is correct here

Identity is incorrect in the handler

If any further information is required please let me know

1 Like

I will take a look at it, once at home.

Hi @evstevemd, When will you be home? Would be good to have an update on this.

1 Like

Ops, I forgot this completely, sorry!

Can you upload a very simple app on github that I can download and test. This time I will test it immediately :wink:

Hi,

I’ll do this by the end of the week.

Thanks.

1 Like

Hi,
I’ve found the issue - it was mine; essentially the way I was aliasing classes in use statements to avoid name clashes introduced a clash. Decent naming of classes and all is good.

1 Like