1
votes

I created a Listener for prePersist and preFlush Event that need to access tu current logged in user:

<?php

namespace App\EventListener;

use App\Entity\AbstractEntity;
use App\Entity\User;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Events;
use Doctrine\Persistence\Event\LifecycleEventArgs;
use Exception;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use function is_object;

class DatabaseActivitySubscriber implements EventSubscriber
{
    /**
     * @var TokenInterface|null
     */
    private ?TokenInterface $token;

    public function __construct(TokenStorageInterface $tokenStorage)
    {
        $this->token = $tokenStorage->getToken();
    }

    /**
     * @return array|string[]
     */
    public function getSubscribedEvents()
    {
        return [
            Events::prePersist,
            Events::preUpdate,
        ];
    }

    /**
     * Initialise le nom de l'utilisateur a l'origine de la création sous la forme "Nom Prénom (id)"
     *
     * @param LifecycleEventArgs $args
     * @throws Exception
     */
    public function prePersist(LifecycleEventArgs $args)
    {
        if ($args->getObject() instanceof AbstractEntity) {
            $args->getObject()->setCreateUser($this->getUser()->getLastName() . ' ' . $this->getuser()->getLastName() . ' (' . $this->getuser()->getId() . ')');
        }
    }

    /**
     * @return string|\Stringable|UserInterface|null|User
     */
    private function getUser()
    {

        if (null === $token = $this->token) {
            return null;
        }

        if (!is_object($user = $token->getUser())) {
            // e.g. anonymous authentication
            return null;
        }

        return $user;
    }

    /**
     * @param LifecycleEventArgs $args
     */
    public function preUpdate(LifecycleEventArgs $args)
    {
        if ($args->getObject() instanceof AbstractEntity) {
            $args->getObject()->setUpdateUser($this->getuser()->getLastName() . ' ' . $this->getuser()->getLastName() . ' (' . $this->getuser()->getId() . ')');
        }
    }
}

but the token is always null.

So i did some verification with what's existing in controller:


    /**
     * @Route("/create", name="create")
     * @param Request $request
     * @param TrainingManager $trainingManager
     * @return Response
     */
    public function create(Request $request, TrainingManager $trainingManager)
    {
        $training = new Training();
        $form = $this->createForm(FormNameType::class, $training);
        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
                $user = $this->getUser();
                $trainingManager->save($training);
        }
        return $this->render('create.html.twig', ['form' => $form->createView(),]);
    }

and the user is well retrieve with $this->getUser(); the line just after, my dataBaseSubscriber is launch, and in constructor token is null. Same token actually.

I checked 'security.token_storage' is autowiring properly and i also tried to forced it in service.yml

This confuse me a lot. The only difference between those 2 instance of UsageTrackingStorage is one is used in controller, the other in Subscriber. But it should the same instance injected...

1

1 Answers

5
votes

it's highly probable that subscribers - like your subscriber - are initialized quite early in the request lifecycle. That is, before the request is taken apart for all the auth stuff.

The TokenStorage is the correct one, however, at the moment in time your subscriber is initialized (read: __construct is called) almost nothing exists, beside the object itself, but it's still empty (apparently). There are/can be EventSubscribers that can change the auth process, so it would be absurd, if the token already existed there. (In fact I believe the auth in symfony is done via EventListeners).

So when at that point getToken() is called on it, it returns null, since it doesn't hold any token, yet, those are added later.

The solution is quite simple: Just store the TokenStorage - instead of the (null) token - and call the "getToken" later when you actually need it and there are actually token in there. The overhead is almost neglible (caching might be possible, don't see a good reason though).