There's a lot of interest in streamlining our object handling. Two current initiatives are (a) a set of generalized object handling methods that build on our current ones in http://drupal.org/node/113435 and (b) Creating a library of CRUD API functions for Drupal in http://drupal.org/node/79684.
hook_user() is a big barrier, discussed recently on the dev list in this thread, http://lists.drupal.org/archives/development/2007-02/msg00203.html.
From that thread:
Unlike our other object hooks (e.g., hook_nodeapi, hook_taxonomy), hook_user passes two arguments by reference. Both are representations of the user object, one in array and the other in object format. - Nedjo Rogers
Having to pass the array is really annoying, especially since you
need to call user_save separately with "category" values as well as the array of data you want to save!All you should really need to do is call a user_save($user) with a fully updated user object (or array ;)).
Profile module knows which fields it is responsible for, I can't see why it needs to rely on a separate edit array and category value to tell it what it already knows based on the {profile_fields} table.
The main trick is that each module that adds data to the user object needs to remember to unset() the values it is responsible for, lest they be serialized into the {user}.data graveyard. - Rowan Kerr
All this code being discussed here is really old and it wouldn't hurt to come up with a more elegant solution. - Gerhard Killesreiter
So for code improvement and updating as well as the objective of streamlining our object handling, we should tackle this. The main work needed is:
- Refactor
user_save()
to use a single argument, the user object, passed by reference. Details: incorporate the additional array of arguments to be saved into the user object, as well as the category. Ensure that modules unset the user properties they are responsible for. - Refactor
hook_user()
to have only a single argument passed by reference, the user object.
This task would put us leaps ahead in paving the way for a unified set of object handling methods, whatever the approach to that larger task is.
Comment | File | Size | Author |
---|---|---|---|
#56 | drupal.hook-user-revamp.56.patch | 55.72 KB | sun |
#54 | drupal.hook-user-revamp.54.patch | 55.89 KB | sun |
#50 | drupal.user-api-revamp.50.patch | 49.65 KB | sun |
#46 | drupal.user-api-revamp.46.patch | 49.23 KB | sun |
#44 | drupal.hook-user-revamp.44.patch | 49.27 KB | sun |
Comments
Comment #1
pwolanin CreditAttribution: pwolanin commentedsubscribing- I'd be interested in taking a shot at some of this refactoring.
Comment #2
pwolanin CreditAttribution: pwolanin commentedBumping so I remember to work on this
Comment #3
beginner CreditAttribution: beginner commentedsubscribe
Comment #4
webchickWould love to see this done in 7.x. Probably would make it play nicer with the registry, too.
Comment #5
pwolanin CreditAttribution: pwolanin commenteddear webchick, user module needs a lot of love. Even if we are not going to have a generalized object API (?), then just to bring this module up to D5 standard (overall, much less D7) is a boatload of work and pain for zero (end user) visible gain.
So, it must be done, but we also need some mechanism to make it worthwhile. Maybe X hours of access to webchick and Dries on the secret bat phone? A case of beer? We need some way to make updating these crufty core modules rewarding and fun.
Comment #6
webchickHm. How about cartoon doodles? :) I like doing those. :D
PICTURE IT. A cartoon detailing the triumph of the spunky hero(ine) __________ battling the evil, soul-sucking cruft of user.module! Action! Mayhem! Romance! Er. Maybe not.
Comment #7
nedjoThe current structure of the user API largely reflects two design aims:
1. The user object can be assigned arbitrary properties at any time and saving the user object will save these properties, which will be present the next time that user's record is loaded.
2. User properties are divided among a number of 'categories', each of which may be handled by a distinct module and shows up in the UI as a separate editing form with its own page.
Probably the first step in any rewrite of the API is to drop both of these aims. They're dated, we don't need them anymore.
For aim 1, if we want to retain the ability to save and retrieve arbitrary values, we should require these to be explicitly added to the 'data' field:
Then we can use
drupal_write_record()
, relying on its support for serialization based on the fields' schema property. We can also drop the cumbersome necessity of modules unsetting the data they're responsible for.For aim 2, we can drop the $category arg to
hook_user()
and do the same as we do with e.g. nodes. If modules want to alter the user edit form and add fields or fieldsets, they can go ahead and do so, and be responsible for saving their own data. If for some reason they prefer to add a different callback for their data, they can do that too.Refactoring the API will still be a big task, but dropping these two unneeded features of the user API should limit the pain.
Comment #8
nedjoHere's a first very rough, conceptual, incomplete etc. idea of what a patch would look like.
General approach:
* Eliminate 'form' op from hook_user, use form_alter instead.
* Modules are responsible for adding data to $user->data array.
* Eliminate user 'categories'. Drop $category argument from hook_user.
* Encode $user object in user forms as $form['#user'] (like we do with $form['#node']).
* Change global $user to global $current_user to limit confusion and avoid collisions with $user variables.
Some of the many outstanding issues:
1. How should modules add properties to the $user->data array on forms? E.g., contact module adds a fieldset. Should this be added to the data array on form validation?
2. How to replace the category functionality, especially in profile module.
TODO:
Pretty much everything, including:
* Profile module needs thorough reworking. Not touched yet.
* For clarity, change all $account variables to $user.
* Address remaining global $user refs. I only searched for "global $user", which will miss e.g. "global $locale, $user"
Comment #9
drewish CreditAttribution: drewish commentedsubscribe
Comment #10
sunsubscribing
Comment #11
nedjoAny further work on this issue should start with breaking out a series of separate issues, based in part on the the various points I outlined in #8.
Comment #12
sunI need to fix #367006: [meta] Field attach API integration for entity forms is ungrokable, which is why I subscribed. Not sure whether it's about right to still get this into D7.
Renaming $user to $current_user sounds like total scope creep to me. That can be done at any time later on in a follow-up patch. ...which probably reduces this patch to about 60% of it's size.
So I have 2 options now: Either fix the current bug by introducing $form + $form_state to hook_user_form(), or try to redo this patch here from scratch, but limited to user forms. In case of the latter, that would only cover some points of your list.
Comment #13
sunTaking over.
Comment #14
sunwow, this might pass already!
Comment #16
sunuhm, what?
Comment #18
sunLet's see what the bot thinks now.
Comment #20
sunAwesome, 13 less :)
Let's try this.
Comment #21
sunComment #23
sunThis one should pass?
Comment #24
sunPASS!
Do it more nicely.
Now we need a RTBC here. :)
Comment #25
sunBetter title. ;)
@nedjo: Sorry for hi-jacking this issue. We can revert later on, or just move the remaining parts into a new issue ;)
Comment #26
catchNice cleanup.
Someone should do the same job on hook_comment() soon (see the comments in tracker.module hook implementations for the wtfs).
Comment #27
sunIn advance... this looks kinda wonky when looking at the patch only. But when looking at the actual code, it makes more sense, because all of those form elements are basically set up separately.
Looks wrong. This patch should fix.
This review is powered by Dreditor.
Comment #28
sunempty() makes no sense. Actually never made sense when looking at the old code. ;)
Even more with the new code, which turns $account into http://api.drupal.org/api/function/drupal_anonymous_user/7 here. So we basically always have $account->roles set up.
Anyway, replacing that with isset().
This review is powered by Dreditor.
Comment #29
Damien Tournoud CreditAttribution: Damien Tournoud commentedThis is awesome. One thing we should really do is to give each category its own form, aliased to user_profile_form with a hook_forms(). That would allow us to remove the crappy #user_category stuff. But this can definitely wait for another issue.
Comment #30
mattyoung CreditAttribution: mattyoung commentedsub
Comment #31
JayKayAu CreditAttribution: JayKayAu commentedThis is completely off-topic, but I'm always amazed at how an innocent looking thread, with beautiful continuity, has a one year gap in the middle of it. I ♥ open source, and you too Drupal! :D
Comment #32
Dries CreditAttribution: Dries commentedVery nice clean-up. Committed to CVS HEAD. Thanks for the resurrection, sun.
Comment #33
kika CreditAttribution: kika commentedWhat about docs?
Comment #34
catchYep, needs docs.
Comment #35
sun.
Comment #36
Dave ReidThank you, thank you, thank you. Great change.
Comment #37
yched CreditAttribution: yched commentedYay !! Sun++++
Comment #38
sunc123456 (Christian, please get a proper nick ;) asked me why
&& !$admin
slipped into the condition for user pictures here. I have no idea, so here comes a quick follow-up.Comment #39
Dries CreditAttribution: Dries commentedCommitted to CVS HEAD. Thanks.
Comment #40
sunReverting status for documentation.
Comment #41
sunok, this was nice - but unfortunately, it left some weird things behind:
In general, the remaining things should be easy to solve since there are only a few instances in core - as long as we have an agreement.
Proposal:
This bears a problem though: In order to add a form submit handler before the user form's main submit handler (where user_save() is invoked), a module would have to use
Actually, I'm not even sure why a module would want or have to run a submit handler before the main submit handler, so this may not be an issue at all.
However, that won't work out, because user data should also be alterable by just invoking user_save(), i.e. without having a form in the process at all.
Hence, it seems like we want to move all hook_user_validate() and hook_user_submit() implementations, which do not store data in a custom storage, to hook_user_insert()/hook_user_update(), to properly prepare the $user properties for {users}.data. For example:
Becomes:
Comment #42
Damien Tournoud CreditAttribution: Damien Tournoud commentedThere is already a patch for the first step of the way: #585834: Kill hook_user_validate().
Comment #43
sunTo be able to move forward in #585834: Kill hook_user_validate() we need an agreement here. Please comment on the proposal in #41.
Comment #44
sunok. What I am doing here:
- Remove hook_user_validate() + hook_user_submit(). All of that is dealt with via hook_form_alter().
- Rename user_edit_form() to user_account_form(). Always invoking the corresponding _validate() and _submit() handler. And also revamping this stub form builder by moving conditions into proper #access properties, which allows us to clean up the corresponding _validate() and _submit() functions, which was the No. 1 trigger for totally senseless contrib modules, which have to do crazy workarounds just to achieve form elements that already exist in core, but simply can't be activated.
- Solving the users.data storage problem I outlined earlier by cleaning up hook names: hook_user_update() becomes hook_user_presave(), allowing modules to set $user properties to NULL, so they don't get stored in users.data. Additionally, that strangely named hook_user_after_update() becomes hook_user_update().
If tests pass, this patch is fairly complete.
Comment #46
sunoh, neaty - just forgot to remove a stale variable :)
This one will pass.
Comment #47
sunGreen! :)
btw, there's even more to do here: hook_user_categories() and all the magic around it no longer makes sense. Profile module and contributed modules now use the Menu API and Form API to add tabs to the user account page(s). That, we can tackle in a separate issue though.
Hint: In an ideal world, we'd just have user_save($account), just like we have node_save($node) -- all those strange arguments date back to D...
Comment #48
Dave ReidThe first time I saw this with drupal_anonymous_user()...it was a major WTF flag for me because it is not clear what is going on with that.
Everything else looks and works good. Very nice cleanups.
I'm on crack. Are you, too?
Comment #49
sunYeah, I can see that. As visible from that snippet though, we passed an empty string to user_save() previously, while user_save() expects an $account, which equally looked like a WTF to me.
Hence, I changed that to pass the anonymous user account (it's an $account at least). That also guarantees that all hook_user_* implementations can safely check $account->uid without having to check whether it's set first.
So the question is: What do we want to pass in case a new account shall be created?
Unfortunately, we can't look at other APIs, because as mentioned before, most use a more modern pattern of just user_save($account), where ->uid is either =0 (insert) or >0 (update).
Two options to resolve this:
...while user_save() would internally call drupal_anonymous_user() in case $account is not an object (i.e. NULL, '', 0, or whatever).
Comment #50
sunImplemented option 2) of #49, which keeps the existing behavior, i.e. user_save(NULL, $edit) creates a new account.
This should be ready to fly now.
Comment #51
catchPersonalizable isn't a real word, maybe it's a neologism. But I found 775k mentions on google so seems common enough.
That's my only nitpick, and it doesn't matter, so RTBC.
I'm on crack. Are you, too?
Comment #52
webchickGreat clean-up! However, since you already turned this into a kitten slaughter-fest, let's finish the job.
Ok, but /why/ do I want to use this hook?
This documentation is completely useless. When do I want to use this hook? Also, add a @see to hook_user_insert() (and vice-versa)
I like this from a consistency/predictability standpoint, but are we sure this is wise? If anonymous is treated like any other old $account, might I not accidentally do something evil to the anon user account, like add them to the admin role? :P
Let's name this user_register_form(), for consistency with user_account_form().
Where does mail actually get set, then?
I'm on crack. Are you, too?
Comment #53
sunTagging absolutely critical clean-ups for D7. Do not touch this tag.
Comment #54
sunYes, User API makes no sense. It never did. But we only have a few days left.
Yes, the code already takes care for that, and, the tests do, too. In fact, every single test that is run.
I take no responsibility for that scope-creepish-patch-bloat. :P
Unconditionally here:
Comment #56
sunRe-rolled against HEAD. This one will pass again.
Comment #57
webchickCool, thanks. Those docs still need a bit of work, but we can definitely handle those in follow-up patches.
I'm very happy to blow this inane part of user module away. :) And +1 for more consistency with comment/node APIs.
Committed to HEAD! This needs to be documented in the upgrade docs.
Comment #58
sun.
Comment #59
moshe weitzman CreditAttribution: moshe weitzman commentedComment #60
webchickNot quite. This is still critical because we lack docs.
Comment #61
catch#653068: Update documentation is incomplete
Comment #62
sunWhen I wrote this patch, I wasn't really aware of #300481: Add hook_form_FORM_ID_post_alter() to run after non-specific hook_form_alter()...
this needs to be fixed, potentially also in all other locations these patches touched.
Powered by Dreditor.
Comment #63
sunHowever, aside from that, added docs to http://drupal.org/node/727352
Comment #64
jhodgdonThese update docs are now on the main update page http://drupal.org/update/modules/6/7
Comment #65
moshe weitzman CreditAttribution: moshe weitzman commentedI think this can be closed. If there is an issue still pending, I suggest opening a new issue.