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