1

Trying to register a Doctrine EventSubscriber but nothing is ever actually fired.

I have, on the Entity, in question, set the @ORM\HasLifeCycleCallbacks annotation.

Here's the Subscriber:

<?php

namespace App\Subscriber;

use App\Entity\User;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Event\PreUpdateEventArgs;
use Doctrine\ORM\Events;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;

class UserPasswordChangedSubscriber implements EventSubscriber
{
    private $passwordEncoder;

    public function __construct(UserPasswordEncoderInterface $passwordEncoder)
    {
        $this->passwordEncoder = $passwordEncoder;
    }

     public function getSubscribedEvents()
    {
        return [Events::prePersist, Events::preUpdate, Events::postLoad];
    }

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

        if (!$entity instanceof User) {
            return null;
        }

        $this->updateUserPassword($entity);
    }

    public function preUpdate(PreUpdateEventArgs $event)
    {
        $entity = $event->getEntity();

        if (!$entity instanceof User) {
            return null;
        }

        $this->updateUserPassword($entity);
    }

    private function updateUserPassword(User $user)
    {
        $plainPassword = $user->getPlainPassword();

        if (!empty($plainPassword)) {
            $encodedPassword = $this->passwordEncoder->encodePassword($user, $plainPassword);
            $user->setPassword($encodedPassword);
            $user->eraseCredentials();
        }
    }
}

The part that is making this particuarly frustrating is that this same code and configuration was fine in Symfony 3 whe autowiring was turned off and I manually coded all my services.

However, now, even if I manually code up a service entry for this, in the usual way, still nothing happens.

EDIT:

Here is my services.yaml after trying what suggested Domagoj from the Symfony docs:

App\Subscriber\UserPasswordChangedSubscriber:
        tags:
            - { name: doctrine.event_subscriber, connection: default }

It didn't work. Interestingly, If I un-implement the EventSubscriber interface, Symfony throws an exception (rightly). Yet my break points in the code are completely ignored.

I've considered an EntityListener, but it cannot have a constructor with arguments, doesn't have access to the Container and I shouldn't have to; this ought to work :/

MattBoothDev
  • 1,294
  • 2
  • 15
  • 25
  • Please, show all related configuration files. You said something about autowire and something about "manual" configuration but you didn't report related code here ... – DonCallisto Jul 09 '18 at 14:49
  • There is no configuration to post, that's relevant, at the moment as there isn't any. The answer below has made a suggestion (which involves configuration) which I'm about to try. On autowiring, Symfony 3.3 brought in some new changes regarding automatically injecting objects into classes without configuration (or optional configuration). Right now I'm going for the default config. You can read more here https://symfony.com/doc/current/service_container/3.3-di-changes.html – MattBoothDev Jul 10 '18 at 17:27

3 Answers3

5

I ended up figuring this out. The field that I was specifically updating was transient, and therefore Doctrine didn't consider this an Entity change (rightly).

To fix this, I put

// Set the updatedAt time to trigger the PreUpdate event
$this->updatedAt = new DateTimeImmutable();

In the Entity field's set method and this forced an update.

I also did need to manually register the Subscriber in the services.yaml using the following code. symfony 4 autowiring wasn't auto enough for a Doctrine Event Subscriber.

App\Subscriber\UserPasswordChangedSubscriber:
    tags:
        - { name: doctrine.event_subscriber, connection: default }
MattBoothDev
  • 1,294
  • 2
  • 15
  • 25
  • 4
    If your event subscriber implements `Doctrine\Bundle\DoctrineBundle\EventSubscriber\EventSubscriberInterface` instead of `Doctrine\Common\EventSubscriber` then it will get auto-wired and no manual service definition is necessary. – 7ochem Jul 27 '21 at 14:31
3

For your first problem, doctrine event subscribers are not autoconfigured/auto-tagged. For the reasons and solutions, you have some responses here.

Personnaly, I just have one Doctrine ORM mapper, so I put this in my services.yaml file :

services:
    _instanceof:
        Doctrine\Common\EventSubscriber:
            tags: ['doctrine.event_subscriber']
Fabien Salles
  • 1,101
  • 15
  • 24
0

You have to register your Event Listener as a service and tag it as doctrine.event_listener

https://symfony.com/doc/current/doctrine/event_listeners_subscribers.html#configuring-the-listener-subscriber

domagoj
  • 906
  • 1
  • 8
  • 20
  • I have done this, if you check the last sentence in the question :( I can see it within the bin/console debug:autowiring output, but can't see if the tags were wired up as well. The Symfony Events documentation claims that the tags are automatically wired in based on the notion that they are implementing the required interfaces. I'll try this again later tonight with the exact format within that page you linked. – MattBoothDev Jul 09 '18 at 13:16