So, I did some digging and looking, and took some cues from FOSUserBundle.
To answer my first question, it looks like neither is the best option, thus rendering the second question moot.
What I ended up doing:
- Add a new field to my User entity,
$plainPassword
. I then changed the mapping of my UserType to this field, instead of $password directly.
- Be sure to blank out
$plainPassword
in User::eraseCredentials
- Made a new service called UserManager. In this, I gave it a function
updateUser()
which handles the actual encoding of the password (no longer Controller handled, yay!).
That's more or less what FOSUserBundle does. They then call updateUser manually (I believe from the Controller). However, I want to be able to forget about this step for the most part. That's where Doctrine Events came in to play.
I added two Doctrine Event listeners (both to UserManager): prePersist and preUpdate.
Both basically check if we are dealing with a User object, then call updateUser()
to update the User's password. preUpdate had to have an extra step of manually setting the new value.
To make sure it triggers, User::setPlainPassword()
wipes out the password and refreshes my salt (this is necessary because plainPassword isn't a mapped property, so just changing it won't allow the User to trigger preUpdate).
A lot of moving pieces, but now that I have it in place, whenever I use User::setPlainPassword()
to change the plaintext password (regardless where), it will now properly encode and save the actual password values. Woot!
namespace My\Bundle\UserBundle\DependencyInjection;
use Doctrine\ORM\Event\PreUpdateEventArgs;
use Doctrine\ORM\Event\LifecycleEventArgs;
use My\Bundle\UserBundle\Entity\User;
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
class UserManager
{
protected $encoderFactory;
public function __construct(EncoderFactoryInterface $encoderFactory)
{
$this->encoderFactory = $encoderFactory;
}
public function getEncoder(User $user)
{
return $this->encoderFactory->getEncoder($user);
}
public function updateUser(User $user)
{
$plainPassword = $user->getPlainPassword();
if (!empty($plainPassword)) {
$encoder = $this->getEncoder($user);
$user->setPassword($encoder->encodePassword($plainPassword, $user->getSalt()));
$user->eraseCredentials();
}
}
public function preUpdate(PreUpdateEventArgs $event)
{
$user = $event->getEntity();
if (!($user instanceof \GamePlan\Bundle\UserBundle\Entity\User)) {
return;
}
$this->updateUser($user);
$event->setNewValue('password', $user->getPassword());
//die($event->getOldValue('password') . ' ' . $event->getNewValue('password') . ' ' . $event->hasChangedField('password') ? 'Y' : 'N');
}
public function prePersist(LifecycleEventArgs $event)
{
$user = $event->getEntity();
if (!($user instanceof \GamePlan\Bundle\UserBundle\Entity\User)) {
return;
}
$this->updateUser($user);
}
}
// In My\Bundle\UserBundle\Entity\User
public function setPlainPassword($plainPassword)
{
$this->plainPassword = $plainPassword;
// Change some mapped values so preUpdate will get called.
$this->refreshSalt(); // generates a new salt and sets it
$this->password = ''; // just blank it out
}
# In My/Bundle/UserBundle/Resources/config/services.yml
services:
my_user.manager:
class: My\Bundle\UserBundle\DependencyInjection\UserManager
arguments:
- @security.encoder_factory
tags:
- { name: doctrine.event_listener, event: prePersist }
- { name: doctrine.event_listener, event: preUpdate }