Symfony 3 get current user inside entity
Asked Answered
M

3

5

I was wondering if there is a way that i can initialize the property owner with an entity User of FOSUserBundle so that it contains the user who created the Post

I want to do this inside the constructor as shown below.

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Table(name="post")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\PostRepository")
 */
class Post
{
  /* here are defined some attributs */
  /**
  * @ORM\ManyToOne(targetEntity="User", inversedBy="posts")
  * @ORM\JoinColumn(name="owner", referencedColumnName="id")
  */
  private $owner;

  public function __construct()
  {
    $this->owner = /* get current user */ ;
  }

}

Is there a way to do this by replacing the comment in the constructor with something ?

Thank you for your answers

Menderes answered 3/2, 2017 at 23:11 Comment(6)
Doctrine does not actually use the constructor so no. Why not just set a relation between Post and User?Pfennig
relation is already there, when a new Post is created the owner attributes is initialized with the currently connected UserMenderes
So all you want is to initialize the owner when a new posted is first created? If so, both of the answers below are valid. Personally I would go with the factory approach possibly adding the create method to the post repository instead of a standalone class. But all the approaches listed make sense.Pfennig
don't know why someone downvoted a legitimate question... having said that, this question sounds like developer laziness.Kandicekandinsky
@DonOmondi - Exactly. Being lazy is a key component being an excellent developer. Only an idiot wants to work harder than they have to.Pfennig
i'm in fact lazy, i want the code to be simple and easy to readMenderes
J
4

I think you are way over-complicating this issue. When you create a new Post in your controller, either in the controller or in the repository do something like this:

use AppBundle\Entity\Post; //at top of controller

$em = $this->getDoctrine()->getManager();
$user = $this->container->get('security.token_storage')->getToken()->getUser();
$post = new Post();
$em->persist( $post );
$post->setOwner( $user );
// set other fields in your post entity
$em->flush();
Jarry answered 4/2, 2017 at 3:11 Comment(2)
In Symfony 4, in a Controller context you can do $this->getUser()Jennyjeno
@Jennyjeno OP question is about Entity context, where you cannot do thatInhalant
F
7

No, there isn't. [*]

There are at least two ways to deal with this:

  • Create your Post entities through a factory service which populates the owner property:

    namespace My\Bundle\EntityFactory;
    
    use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
    use My\Bundle\Entity\Post;
    
    class PostFactory
    {
        private $tokenStorage;
    
        public function __construct(TokenStorageInterface $tokenStorage)
        {
            $this->tokenStorage = $tokenStorage;
        }
    
        public function createPost()
        {
            $user = $this->tokenStorage()->getToken()->getUser();
            $post = new Post($user);
        }
    }
    

    (for this example, you will have to modify your Post constructor to accept the owner as a parameter)

    In services.yml:

    services:
        post_factory:
            class: My\Bundle\EntityFactory\PostFactory
            arguments: [@security.token_storage]
    

    To create an entity from your controller:

    $post = $this->container->get('post_factory')->createPost();
    
  • If you can tolerate that the owner will only be set once you persist the entity, you can use a doctrine event listener:

    namespace My\Bundle\EventListener;
    
    use Doctrine\ORM\Event\LifecycleEventArgs;
    use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
    use My\Bundle\Entity\Post;
    
    class PostOwnerAssignmentListener
    {
        private $tokenStorage;
    
        public function __construct(TokenStorageInterface $tokenStorage)
        {
            $this->tokenStorage = $tokenStorage;
        }
    
        public function prePersist(LifecycleEventArgs $event)
        {
            $entity = $event->getEntity();
            if ($entity instanceof Post && !$entity->getOwner()) {
                $entity->setOwner($this->tokenStorage->getToken()->getUser());
            }
        }
    }
    

    In services.yml:

    services:
        post_owner_assignment_listener:
            class: My\Bundle\EventListener\PostOwnerAssignmentListener
            arguments: [@security.token_storage]
            tags:
                - { name: doctrine.event_listener, event: prePersit }
    

    The advantage here is that the owner gets assigned no matter how and where the Post is created.

[*]: Well, technically with the default app.php you could access the kernel by declaring global $kernel; in your constructor and go from there, however this is very strongly discouraged and may break in strange and subtle ways.

Fictional answered 4/2, 2017 at 0:55 Comment(0)
J
4

I think you are way over-complicating this issue. When you create a new Post in your controller, either in the controller or in the repository do something like this:

use AppBundle\Entity\Post; //at top of controller

$em = $this->getDoctrine()->getManager();
$user = $this->container->get('security.token_storage')->getToken()->getUser();
$post = new Post();
$em->persist( $post );
$post->setOwner( $user );
// set other fields in your post entity
$em->flush();
Jarry answered 4/2, 2017 at 3:11 Comment(2)
In Symfony 4, in a Controller context you can do $this->getUser()Jennyjeno
@Jennyjeno OP question is about Entity context, where you cannot do thatInhalant
I
2

For Symfony 4+ with Autowiring and Entity event listener:

In /EventListener/PostPrePersistListener.php:

namespace App\EventListener;

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

class PostPrePersistListener
{
    private $tokenStorage;

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

    public function prePersist(Post $post, LifecycleEventArgs $event)
    {
        $post->setOwner($this->tokenStorage->getToken()->getUser());
    }
}

In services.yaml:

services:
    App\EventListener\PostPrePersistListener:
        autowire: true 
        tags:
            - { name: doctrine.orm.entity_listener, entity: 'App\Entity\Post', event: prePersist }

Modifying services.yaml is required as Symfony cannot know that this custom service is tagged to hook on doctrine.event_listener

This works at Entity-level as asked, to ensure Controller do not handle the owner value.

Inhalant answered 8/7, 2020 at 11:57 Comment(4)
I had to change doctrine.orm.event_listener to doctrine.event_listener.Pomcroy
@Pomcroy Be careful, if you use doctrine.event_listener you cannot specify the 'entity' field in the YAML file. So it will be called for all entities. This is fine if you want to do your stuff for any entity, or if you filter out entities with $event->getObject() instanceof YourEntityClassInhalant
@ErdalG. In here, the event listener name should be doctrine.orm.entity_listener (not the doctrine.orm.event_listener)Sampson
@Sampson good catch, thx!Inhalant

© 2022 - 2024 — McMap. All rights reserved.