Problem/Motivation
Triggering a save inside either of the post save hooks (hook_ENTITY_TYPE_insert() or hook_ENTITY_TYPE_update()) is a dangerous thing to do, as you potentially end up in horrible recursive scenarios. The documentation for both state:
* This hook runs once the entity has been stored. Note that hook
* implementations may not alter the stored entity data.
The impact of this is more limited for hook_ENTITY_TYPE_insert() as it itself will not run the second time and $entity->original will not exist the first time round, so will be correctly loaded. But even with insert, you can still end up in situations where a module which runs later may have an update hook run before an insert hook (for example) and other data issues.
Proposed resolution
Switch to hook_user_presave() to set the username. The only draw back to this is that the ID is not available for implementations of hook_email_registration_name(). I think stability is more important than that one use case, and modules that are desperate to do that could always do that manually themselves.
Remaining tasks
Make the switch...
API changes
$account->id() would not be available in hook_email_registration_name().
| Comment | File | Size | Author |
|---|
Issue fork email_registration-2935622
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
sophie.sk+1 for this. We have some other code that runs in
hook_user_insertandhook_user_update, which is being thrown off-kilter by the$account->save();in this module's insert.As an interim, it looks as though you can just comment out the save at the end of the insert hook.
Comment #3
sophie.skHere's an initial patch that removes the
$account->save()and restores the username if it hasn't worked.This seems to still create users with the email_registration prefix.
Comment #4
sophie.skComment #6
sophie.skThere seems to be some inconsistent behaviour/expectations between what the username should be and what the username i and it's affecting the patch/testing.
Previously I've noticed that users get created with usernames like email_registration_* but then sometimes it is something different (the first part of their email address). The code in the user_insert suggests that it's going to be email_registration_*, but the test looks for the first part of their email address.
Some consistency is required here and I don't know who's best to make that decision. I can modify the test (next week) to look for the right email address, but I don't feel like patching it about like that is the best way to solve this.
Comment #7
andypostGood idea to explore hook change but patch does something different
Comment #8
bohemier commentedSimple patch attached. My first tests indicate this is working for new registrations as well as hook_email_registration_name.
Editing a user account will also trigger hook_email_registration_name, which is an added benefit of this approach.
I didn't test commerce checkout pane yet.
We need to notify upgraders about possible backwards compatibility issues with new user creation and the non existent account->id()...
Comment #9
bohemier commentedComment #11
bohemier commentedFixed comments, doc and decoration. Not sure test fails because of this...
Comment #13
andypostThe problem with presave hook is that there's no some tokens (id for example is not generated yet)
@bohemier looks you're affected by related issue
Comment #14
bohemier commentedCorrect... I had to revert back because of this for my use case. Thanks @andypost for the reference...
Comment #15
andrewbelcher commentedI still think this is something worth fixing and we are continuing to run into issues with the interaction with other modules with this bit of code.
Updated patch fixes an issue with the unique name lookup failing if
NULLis passed for the uid as it causes the query to return no results at all.Comment #16
andrewbelcher commentedLet's try not to code with my eyes closed :(
Comment #17
andrewbelcher commentedComment #18
krystalcode commentedI believe that the `email_registration_unique_username` function should not be changed, it's the caller that should be making sure to provide the arguments expected by the function. That can be simply done by `intval`.
Comment #19
ocastle commentedPatch #18 solves the issue for me, was having issues with other modules causing duplication.
Comment #20
jeremyskinner commentedThe attached patch provides an alternative solution. Rather than changing the hook to a presave (which has other implications), this patch moves the logic into a separate function which is called by the existing
email_registration_user_insertmethod. The new function takes an optional$saveparameter which indicates whether the user should be saved. This defaults to TRUE for compatibility.Consumers can then optionally remove the email_registration_user_insert through a module_implements_alter and call the new function from a presave (or from any other place) instead.
The advantage of this approach is it doesn't alter the existing behaviour for the majority of users, but allows flexibility to customize the behaviour if it's causing issues, which would solve @andrewbelcher's original issue.
Comment #21
krystalcode commentedDifficult call, I'm all for maintaining compatibility. However, this should be considered a bug that needed to be fixed anyway. The following quote from the hook documentation as posted in the issue description makes it clear that the implementation was wrong in the first place. It may break some sites and you shouldn't need a developer to get the module to work.
Comment #22
anybodyI think this needs further maintainer feedback to decide, how to proceed here? Or from someone very very experienced in Drupal development? Any active maintainers?
Or could someone raise this in slack perhaps for help?
Comment #24
anybodyCreated MR!10 from #20 by @JeremySkinner - please note for credits.
So we can proceed and review here more easily.
Comment #25
anybody@Grevil could you please have a look, now, as you're deep into this area?
So we can probably finish this! What do you think? We might create a 2.x branch for switching to semver and have this possibly dangerous change separated for now?
Comment #26
grevil commentedThe current MR's changes are the NON-dangerous compatibility changes. We might as well merge them as a current workaround and create a follow-up issue / leave this issue open.
Although I agree with @krystalcode here. The hook implementations wasn't properly implemented in the first place. And I do not see a case, where we do not have the $account variable in presave?
So I'd say we should rather orientate our code on patch #18 by @krystalcode rather than #20. And adjust our submodule logic accordingly.
Comment #27
anybody#20 would be no danger, while I'd say we should be careful with #18 as it changes functionality and in contrast to the current implementation means, the username will change, when the user is being resaved! That might be unexpected for existing sites.
I'm fine with a MR here and I'm fine to plan this for a 2.x release, as it's kind of breaking change. But we shouldn't rush here?
Comment #28
anybodyPS: Implementing #18 will take things closer to the logic of the new submodule. I think the logic from there (when to change an existing username) is good and should be uplifted then @Grevil?
See a similar request in #3153809: [2.x] Update username when updating user entity!
Would you like to prepare a MR from all of that, once the other issues are fixed?
Targeting 2.x then (in the MR name?)
Comment #29
anybodyComment #30
grevil commentedYea, let's do that!
Yea, we need to circumvent that, similar to how we did it in the submodule.
Comment #31
grevil commentedComment #32
grevil commentedComment #33
grevil commentedGave credit in #3392840: [2.x] Rework username generation save logic.
Comment #34
grevil commentedDone through #3392840: [2.x] Rework username generation save logic