I've built a UserType for a custom User object. One of the fields is a password.
Ideally, I'd like it to encode the password when I submit the form as a data transformer/related instead of having to handle this in the controller. However, it's a salted password, so this poses a problem because I like to regenerate the salt every time I generate the password. I don't know of a way to get this extra value in to my DataTransformer.
So, I basically have two questions:
Thanks.
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:
$plainPassword
. I then changed the mapping of my UserType to this field, instead of $password directly.$plainPassword
in User::eraseCredentials
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 }