2
votes

I have registered the following listener as a service. This saves the logged-in-user. It works perfectly. After saving the entity the user-id is in createdBy and updatedBy. Ok a little problem: The command "php app / console doctrine: fixtures: load" throws the error "Call to a member function getUser () on a non-object. That's kind of understandable. Only now do I have to disable every time the service before? Do you have another solution?

class UserListener implements EventSubscriber
{
    protected $container;

    public function __construct(ContainerInterface  $container)
    {
        $this->container = $container;
    }

    public function getSubscribedEvents()
    {
        return array(
            Events::prePersist,
            Events::preUpdate
        );
    }

    public function prePersist(LifecycleEventArgs $args)
    {
        $entity = $args->getEntity();

        if ($entity instanceof Post) {

            $user = $this->container->get('security.context')->getToken()->getUser();

            if (!is_object($user) || !$user instanceof User) {
                throw new AccessDeniedException();
            }

            $entity->setCreatedBy($user);
            $entity->setUpdatedBy($user);
        }
    }

    /**
     * @param PreUpdateEventArgs $args
     */
    public function preUpdate(PreUpdateEventArgs $args)
    {
        $entity = $args->getEntity();

        $em = $args->getEntityManager();

        if ($entity instanceof Post) {

            $user = $this->container->get('security.context')->getToken()->getUser();

            if (!is_object($user) || !$user instanceof User) {
                throw new AccessDeniedException();
            }

            $entity->setUpdatedBy($user);

            $uow = $em->getUnitOfWork();
            $meta = $em->getClassMetadata(get_class($entity));
            $uow->recomputeSingleEntityChangeSet($meta, $entity);
        }
    }
}
3

3 Answers

1
votes

When you call the command load-fixtures, you are not logged in. Maybe $this->container->get('security.context')->getToken() returns null ?

0
votes

I had a similar listener to and was trying to save a (mock) entity ("Post" in your case). Of course I was not logged in when starting the command from console.

I added a condition to the listener:

if ($entity instanceof Post) {
    if (null !== $entity->getUser() {
        return;
    }
}

Now I could set a (mock) User on the Post entity inside my fixtures:

$user = $em->getReference('Application\Sonata\UserBundle\Entity\User', 1);
$post = new Post();
$post->setUser($user);

Still not sure if setting the User inside a entity listener is good practice.

I guess currently most people would recommend using a ValueObject for "Post" or at least a constructor that would require setting e.g. a "name" and a "User".

0
votes

My final code (Symfony 2.6):

1) The Listener:

<?php

namespace AppBundle\Listener;

use AppBundle\Entity\Post;
use AppBundle\Entity\Comment;

use Doctrine\ORM\Event\LifecycleEventArgs;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;

/**
 * Class BlameableListener
 *
 * @package AppBundle\Listener
 */
class BlameableListener
{
    private $tokenStorage;

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

    public function prePersist(LifeCycleEventArgs $args)
    {
        $entity = $args->getEntity();

        if ($entity instanceof Post || $entity instanceof Comment) {
            // is authentication information available?
            if (null !== $this->tokenStorage->getToken()) {
                // get User
                $user = $this->tokenStorage->getToken()->getUser();
                if (is_object($user)) {
                    $entity->setAuthorEmail($user->getEmail());
                }
            }
        }
    }
}

2) Config as Service

services:
    app.blameable.listener:
        class: AppBundle\Listener\BlameableListener
        arguments:
            - "@security.token_storage"
        tags:
            - { name: doctrine.event_listener, event: prePersist }