Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Doctrine Listener: Encode the User's Password

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

In AppBundle, create a new directory called Doctrine and a new class called HashPasswordListener:

... lines 1 - 2
namespace AppBundle\Doctrine;
... lines 4 - 7
class HashPasswordListener implements EventSubscriber
{
... lines 10 - 13
}

If this is your first Doctrine listener, welcome! They're pretty friendly. Here's the idea: we'll create a function that Doctrine will call whenever any entity is inserted or updated. That'll let us to do some work before that happens.

Implement an EventSubscriber interface and then use Command+N or the "Code"->"Generate" menu, select "Implement Methods" and choose the one method: getSubscribedEvents():

... lines 1 - 4
use Doctrine\Common\EventSubscriber;
... lines 6 - 7
class HashPasswordListener implements EventSubscriber
{
public function getSubscribedEvents()
{
... line 12
}
}

In here, return an array with prePersist and preUpdate:

... lines 1 - 7
class HashPasswordListener implements EventSubscriber
{
public function getSubscribedEvents()
{
return ['prePersist', 'preUpdate'];
}
}

These are two event names that Doctrine makes available. prePersist is called right before an entity is originally inserted. preUpdate is called right before an entity is updated.

Next, add public function prePersist():

... lines 1 - 6
use Doctrine\ORM\Event\LifecycleEventArgs;
... lines 8 - 9
class HashPasswordListener implements EventSubscriber
{
... lines 12 - 18
public function prePersist(LifecycleEventArgs $args)
{
... lines 21 - 30
}
... lines 32 - 36
}

When Doctrine calls this, it will pass you an object called LifecycleEventArgs, from the ORM namespace.

This method will be called before any entity is inserted. How do we know what entity is being saved? With $entity = $args->getEntity(). Now, if this is not an instanceof User, just return and do nothing:

... lines 1 - 4
use AppBundle\Entity\User;
... lines 6 - 9
class HashPasswordListener implements EventSubscriber
{
... lines 12 - 18
public function prePersist(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if (!$entity instanceof User) {
return;
}
... lines 25 - 30
}
... lines 32 - 36
}

Encoding the Password

Now, on to encoding that password.

Symfony comes with a built-in service that's really good at encoding passwords. It's called security.password_encoder and if you looked it up on debug:container, its class is UserPasswordEncoder. We'll need that, so add a __construct() function and type-hint a single argument with UserPasswordEncoder $passwordEncoder. I'll hit Option+Enter and select "Initialize Fields" to save me some time:

... lines 1 - 7
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoder;
class HashPasswordListener implements EventSubscriber
{
private $passwordEncoder;
public function __construct(UserPasswordEncoder $passwordEncoder)
{
$this->passwordEncoder = $passwordEncoder;
}
... lines 18 - 36
}

In a minute, we'll register this as a service.

Down below, add $encoded = $this->passwordEncoder->encodePassword() and pass it the User - which is $entity - and the plain-text password: $entity->getPlainPassword(). Finish it with $entity->setPassword($encoded):

... lines 1 - 9
class HashPasswordListener implements EventSubscriber
{
... lines 12 - 18
public function prePersist(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if (!$entity instanceof User) {
return;
}
$encoded = $this->passwordEncoder->encodePassword(
$entity,
$entity->getPlainPassword()
);
$entity->setPassword($encoded);
}
... lines 32 - 36
}

That's it: we are encoded!

Encoding on Update

So now also handle update, in case a User's password is changed! The two lines that actually do the encoding can be re-used, so let's refactor those into a private method. To shortcut that, highlight them, press Command+T - or go to the "Refactor"->"Refactor this" menu - and select "Method". Call it encodePassword() with one argument that's a User object:

... lines 1 - 9
class HashPasswordListener implements EventSubscriber
{
... lines 12 - 18
public function prePersist(LifecycleEventArgs $args)
{
... lines 21 - 25
$this->encodePassword($entity);
}
... lines 28 - 48
/**
* @param User $entity
*/
private function encodePassword(User $entity)
{
if (!$entity->getPlainPassword()) {
return;
}
$encoded = $this->passwordEncoder->encodePassword(
$entity,
$entity->getPlainPassword()
);
$entity->setPassword($encoded);
}
}

Tip

I didn't mention it, but you also need to prevent the user's password from being encoded if plainPassword is blank. This would mean that the User is being updated, but their password isn't being changed.

Super nice!

Now that we have that, copy prePersist, paste it, and call it preUpdate. You might think that these methods would be identical... but not quite. Due to a quirk in Doctrine, you have to tell it that you just updated the password field, or it won't save.

The way you do this is a little nuts, and not that important: so I'll paste it in:

... lines 1 - 6
use Doctrine\ORM\Event\LifecycleEventArgs;
... lines 8 - 9
class HashPasswordListener implements EventSubscriber
{
... lines 12 - 28
public function preUpdate(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if (!$entity instanceof User) {
return;
}
$this->encodePassword($entity);
// necessary to force the update to see the change
$em = $args->getEntityManager();
$meta = $em->getClassMetadata(get_class($entity));
$em->getUnitOfWork()->recomputeSingleEntityChangeSet($meta, $entity);
}
... lines 43 - 63
}

Registering the Subscriber as a Service

Ok, the event subscriber is perfect! To hook it up - you guessed it - we'll register it as a service. Open app/config/services.yml and add a new service called app.doctrine.hash_password_listener. Set the class. And you guys know by now that I love to autowire things. It doesn't always work, but it's great when it does:

... lines 1 - 5
services:
... lines 7 - 21
app.doctrine.hash_password_listener:
class: AppBundle\Doctrine\HashPasswordListener
autowire: true
... lines 25 - 27

Finally, to tell Doctrine about our event subscriber, we'll add a tag. This is something we talked about in our services course: it's a way to tell the system that your service should be used for some special purpose. Set the tag to doctrine.event_subscriber:

... lines 1 - 5
services:
... lines 7 - 21
app.doctrine.hash_password_listener:
class: AppBundle\Doctrine\HashPasswordListener
autowire: true
tags:
- { name: doctrine.event_subscriber }

The system is complete. Before creating or updating any entities, Doctrine will call our listener.

Let's update our fixtures to try it.

Leave a comment!

74
Login or Register to join the conversation
Default user avatar
Default user avatar Mike Milano | posted 4 years ago

It seems LifecycleEventArgs->getEntity() and LifecycleEventArgs->getEntityManager() are deprecated, replaced by LifecycleEventArgs->getObject() and LifecycleEventArgs->getObjectManager().

Also, the for the refactor shortcut (super cool btw) the video mentions to use Command+T, when on my PHPStrom, it is Ctrl+T.

1 Reply

Yo Mike!

Hmm, are you sure about the deprecation? It actually makes sense - changes like this have been made in the past - but I don't see it marked as deprecated in the code yet: https://github.com/doctrine.... Is there another spot you were seeing it? I'd definitely be interested to know :).

And quite likely about Ctrl+T - Command+T is a Mac thing, Windows & Linux are different (i.e. swap Command for Ctrl). And actually, unfortunately, *some* keyboard shortcuts are *totally* different between operating systems... and that's no fun at all :p.

For those reading this later, PHPStorm's docs on shortcuts have a little OS selector in the upper right, so you can see what a shortcut is in your OS: https://www.jetbrains.com/h...

Cheers!

Reply
Default user avatar

My mistake, I had used `Doctrine\Common\Persistence\Event\LifecycleEventArgs` instead of `Doctrine\ORM\Event\LifecycleEventArgs`. Sorry for the noise.

Reply

Sweeeet! No worries, things *do* become deprecated (or I make other mistakes), and it's nice that there are people who take the time to let me know :).

Cheers!

Reply
Jean-tilapin Avatar
Jean-tilapin Avatar Jean-tilapin | posted 4 years ago

Hi,
When I register a new user (chapter 18), I have an error "Integrity constraint violation: 1048 [password field cant be null] (translated from french ^^'". So after a few tests, I've determined that the problem must come from the listener.
Inside the prePersist function, if I put a "dump($entity );die;" right after "$entity = $args->getEntity();", I see that the prepersist event is detected. and obtain this :

User {#5368 ▼
-id: null
-name: "Test"
-mail: "test"
-password: null
-plainPassword: "test"
}
But ! If I put the dump/die after the "if (!$entity instanceof User)", I have the Integrity Constraint Violation". That shoud mean that my $entity isn't an instance of User, therefore the encodedPassword function isn't executed, right ? Yet, the previous test has shown that it really is an instance of User. So I don't understand.
Could someone please explain me what I've missed ?
Thank you.

Edit : if I comment the "if !$entity part" in the prePersist function, no more errors and the user is registered.

Edit 2 : found my mistake. A namespace error, again. Mfff.

1 Reply

Hey Xav!

First: let's be 100% sure that your $entity is not an User object by dumping it and checking its type

Second: Check at the top of your file (in the use statement section) what type of User was declared, maybe if you used autocompletion, you might chose a wrong one, it should be your "User Class"

If neither of these cases, please let me see your listener code

Cheers!

Reply
Default user avatar

I forget use use statement for user too, thank you Xav you save my time.

Reply
Default user avatar
Default user avatar Yang Liu | posted 4 years ago

Hi,

when you implemented the prePersist/preUpdate methods, they take a LifecycleEventArgs parameter. But for someone new to symfony like me, how do I know this is need here or even what this is called?

1 Reply

Hey Yang!

That's a GREAT question - I hate when things are just magically shown - so shame on me :).

Of course, you can read the documentation. And, at the very least, you would probably need to read a little bit of documentation to *at least* know that there *is* an argument to this method. But, to know *what* class this is (i.e. that it is a LifecycleEventArgs), I like to simply dump() the variable to find out what it is:


public function prePersist($event)
{
dump($event);die; // what are you!?
}

When in doubt, this is what I like to do!

Cheers!

1 Reply
Default user avatar
Default user avatar Bozhidar Hristov | posted 2 years ago

That won't work if ONLY plainPassword is changed and nothing else, since plain password is not persisted field - so preUpdate Event won't fire - since ORM doesn't see any changes in entity.

Reply

Hey Bozhidar Hristov!

You are absolutely correct :). That's why, in the previous video, we modify the setPlainPassword() field to also set the password field to null - https://symfonycasts.com/sc...

That's a bit of a hack, but it was done to solve this issue: it causes the preUpdate to fire and the password to bet set to the encoded value before save :).

Btw, in newer tutorials, I've decided against using a Doctrine listener to encode the password - it's just a bit too magic, and requires tricky things like this to work. Now, I encode the password manually when I create a User, which is usually just in registration and maybe also in a "user fixture" place (so, it's not too many places, and you could centralize this all into a service if you want).

Cheers!

Reply
Cesar Avatar

Hi guys,

A little question, I have created a user profile edit form but when I submit it the password changes to null because the HashPasswordListener (created in chapter 10) is processing it. To fix that, I added a condition in the preUpdated function:

if ((!$entity instanceof Users) or ($_SERVER['REQUEST_URI'] == '/myURL')) {
return;
}

It works ok but I am wondering if it's correct to do that or if there is a better ("professional") way to correct that. I hope you can give me advice.

Cesar

Reply

Hey Cesar.

It's strange, maybe you missed something, check out `encodePassword()` method, it should check if there is a plain password and encode only if there is a new password. Here is a tip for you https://symfonycasts.com/sc...

Hope this will help

Cheers!

Reply
Cesar Avatar

You are right! I forgot a line in encodePassword function. Problem solve. Thanks Vladimir.

Reply
Default user avatar
Default user avatar 3amprogrammer | posted 4 years ago

How do I know what class to include in use statement when phpstorm suggests multiple one? For instance of LifecycleEventArgs I have Doctrine\Common and Doctrine\ORM.

Reply

Hey,

What about LifecycleEventArgs class - it depends on do you use Doctrine ORM or no. Most probably you do in Symfony projects, so choose the second one which "Doctrine\ORM\LifecycleEventArgs", otherwise - choose the "Doctrine\Common\LifecycleEventArgs". But this things is hard to guess, that's why you should always based on its docs! In docs take to account what FQCN is used in the use statement. In popular third party libraries it always well documented, otherwise you just need to guess or inspect their code.

Cheers!

1 Reply
Default user avatar

Hi,

I put a post previously in form. I have now got as a result of my code:

$userPassword = $formForgotPassword->getData();

var_dump($userPassword["plainPassword"]);

$user->setPassword($userPassword["plainPassword"]);
$em = $this->getDoctrine()->getManager();
$em->persist($user);
$em->flush();

var_dump($user->getPassword());

die();

this:

string(3) "123" string(3) "123"

So now the password is here but it is not encoded... I have looked twice the video and just can not understand why and how to get this listener to listen to my controller which is normally called before any entity is inserted. I have tried to put a var_dump() but it just show me that when updating the password the listener is not called at all. How can I get this listener to listen when the user reset his/her password?

Thanks

Reply

Hey James,

I'm glad you handled with setting a password! Well, first of all you should achieve this event listener to work. Please, double check you defined event listener as service correctly, it should has a tag - { name: doctrine.event_subscriber }.
Then ensure you have all necessary methods inside event listener class: prePersist() and preUpdate() - where I'd recommend you to simply add a die('listener works'); statement at the beginning of each method to see it works. Also your event listener must have `getSubscribedEvents()` method:


public function getSubscribedEvents()
{
return ['prePersist', 'preUpdate'];
}

Please note, you must use exactly 'prePersist' and 'preUpdate' strings as it's a reserved event names. And the same names you should use for methods.

BTW, please, ensure you clear the cache, I'd recommend you manually remove "dev" and "prod" folders in the cache directory.
Let me know if it helps

Cheers!

Reply
Default user avatar

Just understood now the whole process from the code above, I need to not try to add directly the password but send it to setplainPassword and then the listener will encode and setPassword for me so:

not: $user->setPassword($userPassword["plainPassword"]);
but: $user->setplainPassword($userPassword["plainPassword"]);

Thanks again for your help!

Reply

Hey James,

Yes! Actually, it depends on how you are you doing it, but the most common way is using the secondary $plainPassword property instead of the same ($password property) for it.

Reply
Default user avatar

I got an error in a console, while tried to load fixtures:
> purging database
> loading AppBundle\DataFixtures\ORM\LoadFixtures

[UnexpectedValueException]
Could not determine how to assign plainPassword to a AppBundle\Entity\User object

what should I do in this case?...

Reply

Ah, this *probably* means that you don't have a setPlainPassword() method - so Alice doesn't know how to set the plainPassword field. Can you check to see if you have that?

Cheers!

Reply
Vladimir Z. Avatar
Vladimir Z. Avatar Vladimir Z. | posted 4 years ago

Would that do it?


# Prevent the user's password from being encoded if plainPassword is blank.
# This would mean that the User is being updated, but their password isn't being changed.
if (!$entity->getPlainPassword()) {
return;
}

$this->encodePassword($entity);
Reply

Hey Vlad,

What do you mean here? What's your question and what are you trying to do?

Reply
Vladimir Z. Avatar
Vladimir Z. Avatar Vladimir Z. | victor | posted 4 years ago

Hi Victor,
That code refers to 3:24 of this video.
Thanks,

Reply

Yo Vlad!

There is a slight difference between the Doctrine event subscriber and the event subscriber used by the JMSSerializerBundle. They philosophically do the same thing, but under the hood, Doctrine uses *its* event library, and JMS uses Symfony's event library. So the way that you wire them up is slightly different. So I don't think adding the "class" option works with Doctrine. BUT, Doctrine does support something slightly different, that does exactly this - it's called an entity listener. They're great, but the config is a little weird for me so I stick to subscribers. Check them out http://docs.doctrine-projec... and http://symfony.com/doc/curr...

And yes to your code block - checking for !$entity->getPlainPassword() should do it - that's the bit I forgot!

Cheers!

Reply
Vladimir Z. Avatar

Hi Ryan,
How do I find out what options an event subscriber supports?
Thank you!

Reply

Ah, great question :). Unfortunately, it's just one of those things that's up to the docs of the different libraries to support. *Ideally*, it would be on the php doc of the interface. For Doctrine, that's true (it simply says that this method returns an array of event names). For JMS, it should be in its docs, or you can go digging for it (https://github.com/schmittj.... In short, it's up to the implementation of the event system, so they can kinda do whatever they want. tl;dr: it can only really be found in the docs, or, buried in the code.

Cheers!

Reply
Vladimir Z. Avatar
Vladimir Z. Avatar Vladimir Z. | posted 4 years ago

Hi Ryan,
In your REST tutorial, your getSubscribedEvents() method is slightly different:


public static function getSubscribedEvents()
{
# Only call onPostSerialize for Programmer classes!
return array(
array(
'event' => 'serializer.post_serialize',
'method' => 'onPostSerialize',
'format' => 'json',
'class' => 'AppBundle\Entity\Programmer'
)
);
}

Instead of doing the if (!$entity instanceof User) check, can the getSubscribedEvents be refactored to the following?


public static function getSubscribedEvents()
{
# Only call onPostSerialize for Programmer classes!
return array(
array(
'event' => 'prePersist',
'method' => 'prePersist',
'class' => 'AppBundle\Entity\User'
),
array(
'event' => 'preUpdate',
'method' => 'preUpdate',
'class' => 'AppBundle\Entity\User'
)
);
}
Reply
Default user avatar
Default user avatar Damjan Ribovski | posted 4 years ago

Hi,
I have a question regarding:

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

From what I understand HashPasswordListener will be used/called for every Entity (not only User).

If I want to have listeners for more then one entity type I have two options:

1. register more listeners and have similar if statement in all of them - this means
that all listeners will be called even if only one (or none) will actually be used for particular entity type.

2. Have only one listener and many if/else statements in order to find out which entity I am saving - and this will break few SOLID rules.

Is there a better way?

Thank you!

Best regards,
Damir

Reply

Hey Damjan,

Think about feature, i.e. what tasks exactly should your listener do. If you name your listener as HashPasswordListener, so this listener should be used for hashing passwords only. If you need to hash passwords for more than one entity - you do not need to create another HashPasswordListener to implement the same feature for another entity, just add a new "if (!$entity instanceof SomeClass)" statement to it. But if you need to implement another feature, better to create another listener and give it a good name. You can also take a look from a bit another side: Do not duplicate code, so if you have the same functionality but for a few entities - implement it for both in one listener. But if you have different tasks - better to decouple code to different listeners with a good names which will easy to understand and maintain for others, or for you in the future.

So, in short, your 1st option is better, but it depends :)

Cheers!

Reply
Default user avatar
Default user avatar Damjan Ribovski | victor | posted 4 years ago

Hi Victor,
Great, thank you for the explanation.
Best regards!

Reply
Default user avatar
Default user avatar Terry Caliendo | posted 4 years ago

While I enjoyed learning about the Doctrine listener, it seems like a lot of extra work to put in the Doctrine listener to encode the password. Why not just encode the password whenever setPassword() is called? Something like the following?

User Entity:

    public function setPassword($password)
{

$encoded = $this->passwordEncoder->encodePassword(
$this,
$password
);
$this->password = $encoded ;

}

Reply

Hey Terry,

Good question! Because in this case you have to inject the password encoder service into the entity, which is a really bad practice, since entity is just a *data* not a service.

Cheers!

Reply
Default user avatar
Default user avatar Terry Caliendo | victor | posted 4 years ago

Got it. Thanks.

Reply
Default user avatar
Default user avatar Terry Caliendo | victor | posted 4 years ago

Just curious... why does Doctrine give ORM annotation to allow manipulation within the Entity file? Is it still bad practice to do it as part of the Entity file as opposed to a separate listener initiated in the services.yml file?

http://symfony.com/doc/curr...

Reply

Hey Terry,

Actually, Doctrine Lifecycle Callbacks are fine and you can use it as well, but they have some limitation like you can't inject other services into the entity. That's why listeners help with it - we can do the same with event listeners and we also can inject other service into it since listener is just a service.

Cheers!

Reply
Default user avatar
Default user avatar Terry Caliendo | victor | posted 4 years ago

Got it. Thanks.

Reply
Default user avatar
Default user avatar Alexandru Lazar | posted 4 years ago

Hello,
I did this for practice and got :
Cannot autowire service "app.doctrine.hash_password_listener": argument "$passwordEncoder" of method "App\Doctrine\HashPasswordListener::__construct()" references class "Symfony\Component\Security\Core\Encoder\UserPasswordEncoder" but no such service exists. Try changing the type-hint to "Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface" instead.

I changed to UserPasswordEncoderInterface and when updating the user details i get:
Warning: strlen() expects parameter 1 to be string, object given

pointing to encodeUserPassword ---

$encoded = $this->passwordEncoder->encodePassword($entity,$entity->getPlainPassword());

Reply

Hey Alexandru Lazar!

Ok, really interesting things are happening here! We've changed & improved autowiring in Symfony 3.3 and 3.4, so that's part of what you're seeing. First, what version of Symfony are you on? I would not expect the first error, though I would expect a deprecation notice. Here are some details:

1) In Symfony 3, autowiring is quite magic: if you type-hint UserPasswordEncoder, it tries to find any service with that class and pass it in. But, that magical way is now deprecated. And in Symfony 4, it's much simpler: your type-hint must exactly match a service id in the container. Here are all the details about that: https://knpuniversity.com/s.... This is why - unless you're trying Symfony 4.0 BETA - I am surprised by your first error. On Symfony 4, you *must* use UserPasswordEncoderInterface (that's the service id in the container). But in Symfony 3, both should work (but using UserPasswordEncoder will trigger a deprecation notice).

2) About the strlen() error, that's interesting. Basically, if you look deep enough, the second argument to encodePassword() - $entity->getPlainPassword() - is ultimately passed to a class that checks its length: https://github.com/symfony/.... Is $user->getPlainPassword() somehow an object? I don't think this has anything to do with autowiring, I think there's another issue.

Let me know what you find out! Cheers!

Reply
Default user avatar
Default user avatar Alexandru Lazar | weaverryan | posted 4 years ago | edited

Heyweaverryan !
Thanks for the fast answer. I am using 4.0 Beta and will go with the encoder interface. A for the second one I am clueless at the moment. I even tried to force it to string and it says it can`t be transformed. It`s clearly not autowiring since i got passed that using UserPassswordEncoderInterface. I`ll keep trying and let you know.
Thanks for the fast answer!
Cheers!

Reply

Ah, cool! That answers the first question perfectly then :).

Try this:

1) dump($this->passwordEncoder);die;. It should be an instance of UserPasswordEncoder

2) dump($entity->getPlainPassword());die;. This should be a string... but your error seems to make me think this is an object!

Cheers!

Reply
Default user avatar
Default user avatar Alexandru Lazar | weaverryan | posted 4 years ago

I sort it out. Before finding this tutorial i tried to pass * @ORM\PreUpdate in the user entity on the password field. Working great now! Thanks for the support you got me i`ll buy this tutorial!

Cheers!

Reply
Default user avatar
Default user avatar Christophe Lablancherie | posted 4 years ago

Hi,

I was using FOSUserBundle on my project, but i would to change and take a solution coded by myself. I found your solution very usefull but i've one question. I have 100 account registered and also 100 password, registered with sha and salt...But in the screencast we used bcrypt, so what could i migrate the account member ?

Thanks for your answer

Reply

Hey Christophe Lablancherie!

You can definitely do this (sort of) and you have 2 options:

1) Continue using sha with salt - this is just a setting in security.yml, and you can keep the same hashing algorithm even if you're changing from FOSUserBundle to something else. Of course, bcrypt is more secure than just using sha.

2) Allow your 100 old accounts to use sha, but use bcrypt for the new accounts. You would do this by adding some flag on the old accounts so that you know they use the old method. Then, implement the EncoderAwareInterface in your User class to tell Symfony which to use in which case.

Cheers!

Reply
Default user avatar
Default user avatar Christophe Lablancherie | weaverryan | posted 4 years ago

Thanks for your answer ! I will do some research for EncoderAwareInterface and if it's easy to implement i will do this to do on bcrypt :)

Reply
Richard Avatar
Richard Avatar Richard | posted 4 years ago

Your service tag auto completed towards the end there. Mine didn't! Why? Yes. I have symfony plugin... ;)

Reply

Yo Richard!

Weeeeeeeeell, mine only "kinda" auto completed :). It auto-completed doctrine.event_listener, but as soon as I typed doctrine.event_subscriber... the auto-complete did *not* work! If you got no auto-completion, I'm not sure - just make sure you have the latest version of the plugin! That magic definitely comes from the plugin :).

Cheers!

Reply
Default user avatar
Default user avatar Trashcan4U | posted 4 years ago

Why don't you encode the password in setter method setPlainPassword()? You have to encode password if 'plainPassword' is not empty. So it would make sense to encode it right away once you set it. If there is a listener, it would be called everytime User entity is updated unnecessarily. No?

Reply

Hey Trashcan4U,

Because... we can't do it in setPlainPassword(), i.e. we can't inject services into entities - actually that's a bad practice. Entities are just data, to perform any actions on data we need other stuff like services, listeners, etc. And since we need a PasswordEncored service, we can't get it inside User entity. That's why we do it outside.

And yes, you're correct, it'll be called every time when user is update but... we have a quick check to make sure we can proceed further:


if (!$entity->getPlainPassword()) {
return;
}

So if password wasn't updated, i.e. just ther fields were updated - then skip encoding password. Actually, it's not a big deal.

How does it sound to you?

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.5.9",
        "symfony/symfony": "3.1.*", // v3.1.4
        "doctrine/orm": "^2.5", // v2.7.2
        "doctrine/doctrine-bundle": "^1.6", // 1.6.4
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
        "symfony/swiftmailer-bundle": "^2.3", // v2.3.11
        "symfony/monolog-bundle": "^2.8", // 2.11.1
        "symfony/polyfill-apcu": "^1.0", // v1.2.0
        "sensio/distribution-bundle": "^5.0", // v5.0.22
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.16
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "knplabs/knp-markdown-bundle": "^1.4", // 1.4.2
        "doctrine/doctrine-migrations-bundle": "^1.1" // 1.1.1
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.0.7
        "symfony/phpunit-bridge": "^3.0", // v3.1.3
        "nelmio/alice": "^2.1", // 2.1.4
        "doctrine/doctrine-fixtures-bundle": "^2.3" // 2.3.0
    }
}