web-dev-qa-db-fra.com

L'injection de SecurityContext dans un écouteur prePersist ou preUpdate dans Symfony2 pour obtenir l'utilisateur dans un createdBy ou un updatedBy provoque une erreur de référence circulaire

J'ai configuré une classe d'écoute où je définirai la colonne ownerid sur n'importe quel doctrine prePersist. Mon fichier services.yml ressemble à ceci ...

services:
my.listener:
    class: App\SharedBundle\Listener\EntityListener
    arguments: ["@security.context"]
    tags:
        - { name: doctrine.event_listener, event: prePersist }

et ma classe ressemble à ça ...

use Doctrine\ORM\Event\LifecycleEventArgs;
use Symfony\Component\Security\Core\SecurityContextInterface;

class EntityListener
{

protected $securityContext;

public function __construct(SecurityContextInterface $securityContext)
{
    $this->securityContext = $securityContext;
}


/**
 *
 * @param LifecycleEventArgs $args 
 */
public function prePersist(LifecycleEventArgs $args)
{

    $entity = $args->getEntity();
    $entityManager = $args->getEntityManager();

    $entity->setCreatedby();

}
}

Le résultat de ceci est l'erreur suivante.

ServiceCircularReferenceException: référence circulaire détectée pour le service "doctrine.orm.default_entity_manager", chemin: "doctrine.orm.default_entity_manager -> doctrine.dbal.default_connection -> my.listener -> security.context -> security.authentication.manager -> fos_user .user_manager ".

Mon hypothèse est que le contexte de sécurité a déjà été injecté quelque part dans la chaîne mais je ne sais pas comment y accéder. Des idées?

44
Jeremy

J'ai rencontré des problèmes similaires et la seule solution de contournement consistait à passer le conteneur entier dans le constructeur (arguments: ['@service_container']).

use Doctrine\ORM\Event\LifecycleEventArgs;
use Symfony\Component\DependencyInjection\ContainerInterface;

class MyListener
{
    protected $container;

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

    // ...

    public function prePersist(LifeCycleEventArgs $args)
    {
        $securityContext = $this->container->get('security.context');

        // ...
    }
}
68
gilden

Depuis Symfony 2.6, ce problème devrait être résolu. Une demande d'extraction vient d'être acceptée dans le maître. Votre problème est décrit ici. https://github.com/symfony/symfony/pull/1169

Depuis Symfony 2.6, vous pouvez injecter le security.token_storage Dans votre écouteur. Ce service contiendra le jeton utilisé par le SecurityContext dans <= 2.5. Dans 3.0, ce service remplacera complètement la SecurityContext::getToken(). Vous pouvez voir une liste de modifications de base ici: http://symfony.com/blog/new-in-symfony-2-6-security-component-improvements#deprecated-the-security-context-service =

Exemple d'utilisation en 2.6:

Votre configuration:

services:
    my.entityListener:
        class: App\SharedBundle\Listener\EntityListener
        arguments:
            - "@security.token_storage"
        tags:
            - { name: doctrine.event_listener, event: prePersist }


Votre auditeur

namespace App\SharedBundle\Listener;

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

class EntityListener
{
    private $token_storage;

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

    public function prePersist(LifecycleEventArgs $args)
    {
        $entity = $args->getEntity();
        $entity->setCreatedBy($this->token_storage->getToken()->getUsername());
    }
}


Pour un exemple Nice created_by, vous pouvez utiliser https://github.com/hostnet/entity-blamable-component/blob/master/src/Listener/BlamableListener.php pour l'inspiration. Il utilise le hostnet/entity-tracker-component qui fournit un événement spécial qui est déclenché lorsqu'une entité est modifiée pendant votre demande. Il y a aussi un bundle pour configurer cela dans Symfony2

36
Anyone

Il y a déjà une excellente réponse dans ce sujet, mais tout change. Il y a maintenant des classes d'écouteurs d'entité dans Doctrine: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html#entity-listeners-class

Vous pouvez donc ajouter une annotation à votre entité comme:

/**
 * @ORM\EntityListeners({"App\Entity\Listener\PhotoListener"})
 * @ORM\Entity(repositoryClass="App\Repository\PhotoRepository")
 */
class Photo 
{
    // Entity code here...
}

Et créez une classe comme celle-ci:

class PhotoListener
{        
    private $container;

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

    /** @ORM\PreRemove() */
    public function preRemoveHandler(Photo $photo, LifecycleEventArgs $event): void
    {
         // Some code here...
    }
}

Vous devez également définir cet écouteur dans services.yml comme ça:

photo_listener:
  class: App\Entity\Listener\PhotoListener
  public: false
  autowire: true
  tags:
    - {name: doctrine.orm.entity_listener}
1
Nikita Leshchev

J'utilise les fichiers de configuration doctrine pour définir les méthodes preUpdate ou prePersist:

Project\MainBundle\Entity\YourEntity:
    type: entity
    table: yourentities
    repositoryClass: Project\MainBundle\Repository\YourEntitytRepository
    fields:
        id:
            type: integer
            id: true
            generator:
                strategy: AUTO

    lifecycleCallbacks:
        prePersist: [methodNameHere]
        preUpdate: [anotherMethodHere]

Et les méthodes sont déclarées dans l'entité, de cette façon, vous n'avez pas besoin d'un écouteur et si vous avez besoin d'une méthode plus générale, vous pouvez créer une BaseEntity pour conserver cette méthode et étendre les autres entites à partir de cela. J'espère que cela aide!

0
Calin Bolea