Drupal 7 User IDs - Not Consecutive
Putting together a new Drupal site, I had a puzzling moment when I noticed a new user's ID was number 15. This was mysterious because the site had only 4 users on it. And just moments before I had created a user who's ID was 13. ID number 14 went missing, and I couldn't find it anywhere in the database.
I asked around, and eventually found a thread with the answer: Provide a sequences API. It's long so I'll summarize... In Drupal 5, there was a database table devoted to generating ID sequences. In Drupal 6, they got rid of that in favor of auto-incrementing IDs. In Drupal 7, they brought back the sequence API. Drupal uses this new code for the user IDs, while node IDs (and many other IDs) are still auto-increment. (Although possibly not for long - don't ask me why they dislike auto-increment so much). [Edit - removed italics around "they".]
To get to the point, some tables behave as I would have expected, i.e. node IDs are continuous (for now). But, Users share their IDs with other parts of the system. In Drupal core, the same set of ID numbers is shared between users, actions and batch jobs. Third-party modules may introduce even more things that take from this shared space. The case that bothers me most is the batch jobs. Every batch job performed by Drupal will increment the ID sequence. Even the batch jobs that complete in a single page load; they take an ID even if they are never stored in the database!
I prefer consecutive user IDs. And I expected them to be consecutive, because I have experience with Drupal 6.x. To the Drupal core developers, this makes me a "client who thinks they know more then [sic] they actually do." Sounds like we can't turn to the Drupal core developers for help on this one.
Those developers who claim to think to know exactly how much they know :-) they haven't taken away all our options. At least not yet. It turns out the user_save() function is OK with us specifying a UID. [Edit - It looks like the patch above makes this possible, so I guess they gave us an option after all!] So we can override the default behavior of Drupal core. Here's how.
The code below implements several Drupal hooks. You'll need to replace custom
everywhere you see it with the name of your own module or profile.
The code below has changed since I first posted here. The code below may have problems if you've customized the user creation behavior in other ways. It should work better than my previous code on most Drupal instances.
First, in our module/profile .install file we create a table like Drupal core's {sequences}
table. Our table is named custom_uid_sequences
and will be used only when generating new UIDs. To use this code, add it to your .install file and run Drupal's update.php script.
<span style="color: #000000"><span style="color: #0000BB"><?php<br></span><span style="color: #FF8000">/**<br> * Implements hook_schema().<br> *<br> * Adds a table just like core sequences table. We will use it when creating users.<br> */<br></span><span style="color: #007700">function </span><span style="color: #0000BB">custom_schema</span><span style="color: #007700">() {<br> </span><span style="color: #0000BB">$system_schema </span><span style="color: #007700">= </span><span style="color: #0000BB">system_schema</span><span style="color: #007700">();<p> </p></span><span style="color: #0000BB">$custom_uid_sequence </span><span style="color: #007700">= </span><span style="color: #0000BB">$system_schema</span><span style="color: #007700">[</span><span style="color: #DD0000">'sequences'</span><span style="color: #007700">];<br> return array(</span><span style="color: #DD0000">'custom_uid_sequences' </span><span style="color: #007700">=> </span><span style="color: #0000BB">$custom_uid_sequence</span><span style="color: #007700">);<br>}<p></p></span><span style="color: #FF8000">/**<br> * Installs our custom schema, including table for user IDs.<br> */<br></span><span style="color: #007700">function </span><span style="color: #0000BB">custom_update_7001</span><span style="color: #007700">() {<br> </span><span style="color: #0000BB">drupal_install_schema</span><span style="color: #007700">(</span><span style="color: #DD0000">'custom'</span><span style="color: #007700">);<br>}<br></span><span style="color: #0000BB">?></span></span>
After we've created this table, we can tell Drupal to use this table in place of {sequences} when creating users. And tell Drupal to use the regular table after the user creation is complete. These hooks belong in your custom .module or .profile file.
<span style="color: #000000"><span style="color: #0000BB"><?php<br></span><span style="color: #FF8000">/**<br> * Implements hook_entity_presave().<br> *<br> * Before saving a user, we use a database connection that prefixes the<br> * {sequences} table with 'custom_uid_'. This is to make user IDs come<br> * from a consecutive sequence of numbers, rather than skipping numbers<br> * when batch jobs are performed or other Drupal behavior uses the<br> * {sequences} table. This approach should work OK as long as the user<br> * create process has not been customized to start batch jobs, create<br> * actions, or do anything else that relies on {sequences}.<br> */<br></span><span style="color: #007700">function </span><span style="color: #0000BB">custom_entity_presave</span><span style="color: #007700">(</span><span style="color: #0000BB">$entity</span><span style="color: #007700">, </span><span style="color: #0000BB">$type</span><span style="color: #007700">) {<br> if (</span><span style="color: #0000BB">$type </span><span style="color: #007700">== </span><span style="color: #DD0000">'user' </span><span style="color: #007700">&& empty(</span><span style="color: #0000BB">$entity</span><span style="color: #007700">-></span><span style="color: #0000BB">uid</span><span style="color: #007700">)) {<br> </span><span style="color: #FF8000">// We're saving a new user.<p> </p></span><span style="color: #0000BB">$active_key </span><span style="color: #007700">= </span><span style="color: #DD0000">'default'</span><span style="color: #007700">; </span><span style="color: #FF8000">// Drupal provides no way to learn active key, so we assume default.<p> // Create a new database connection, identical to the active one except adding a prefix for the sequences table.<br> </p></span><span style="color: #0000BB">$db_info </span><span style="color: #007700">= </span><span style="color: #0000BB">Database</span><span style="color: #007700">::</span><span style="color: #0000BB">getConnectionInfo</span><span style="color: #007700">(</span><span style="color: #0000BB">$active_key</span><span style="color: #007700">);<br> </span><span style="color: #0000BB">$db_data </span><span style="color: #007700">= </span><span style="color: #0000BB">$db_info</span><span style="color: #007700">[</span><span style="color: #0000BB">$active_key</span><span style="color: #007700">];<br> </span><span style="color: #0000BB">$db_data</span><span style="color: #007700">[</span><span style="color: #DD0000">'prefix'</span><span style="color: #007700">][</span><span style="color: #DD0000">'sequences'</span><span style="color: #007700">] = </span><span style="color: #DD0000">'custom_uid_'</span><span style="color: #007700">;<p> </p></span><span style="color: #0000BB">Database</span><span style="color: #007700">::</span><span style="color: #0000BB">addConnectionInfo</span><span style="color: #007700">(</span><span style="color: #DD0000">'custom_uid'</span><span style="color: #007700">, </span><span style="color: #DD0000">'default'</span><span style="color: #007700">, </span><span style="color: #0000BB">$db_data</span><span style="color: #007700">); </span><span style="color: #FF8000">// Not sure what a $target is. 'default' is only value that works.<p> </p></span><span style="color: #0000BB">$GLOBALS</span><span style="color: #007700">[</span><span style="color: #DD0000">'_custom_db_active_key'</span><span style="color: #007700">] = </span><span style="color: #0000BB">Database</span><span style="color: #007700">::</span><span style="color: #0000BB">setActiveConnection</span><span style="color: #007700">(</span><span style="color: #DD0000">'custom_uid'</span><span style="color: #007700">);<br> </span><span style="color: #FF8000">// Until custom_entity_insert() is called, all Drupal sequences will come from the custom_uid_sequences table. Hopefully it will be used only to create the new user.<br> </span><span style="color: #007700">}<br>}<p></p></span><span style="color: #FF8000">/**<br> * Implements hook_entity_insert().<br> *<br> * If custom_entity_presave() changed the active DB connection, we restore it's previous setting here.<br> */<br></span><span style="color: #007700">function </span><span style="color: #0000BB">custom_entity_insert</span><span style="color: #007700">(</span><span style="color: #0000BB">$entity</span><span style="color: #007700">, </span><span style="color: #0000BB">$type</span><span style="color: #007700">) {<br> if (</span><span style="color: #0000BB">$type </span><span style="color: #007700">== </span><span style="color: #DD0000">'user' </span><span style="color: #007700">&& isset(</span><span style="color: #0000BB">$GLOBALS</span><span style="color: #007700">[</span><span style="color: #DD0000">'_custom_db_active_key'</span><span style="color: #007700">])) {<br> </span><span style="color: #FF8000">// During user insert, we used custom_uid_sequences. Now we return to normal sequences table.<br> </span><span style="color: #0000BB">Database</span><span style="color: #007700">::</span><span style="color: #0000BB">setActiveConnection</span><span style="color: #007700">(</span><span style="color: #0000BB">$GLOBALS</span><span style="color: #007700">[</span><span style="color: #DD0000">'_custom_db_active_key'</span><span style="color: #007700">]);<br> unset(</span><span style="color: #0000BB">$GLOBALS</span><span style="color: #007700">[</span><span style="color: #DD0000">'_custom_db_active_key'</span><span style="color: #007700">]);<br> }<br>}<br></span><span style="color: #0000BB">?></span></span>
The preceding code may not work for you. It may not work with future versions of Drupal. It comes with no warranty expressed or implied. Still, I hope you like it and find it helpful.
If you feel like I do, that it is worth a little extra effort to keep numerical IDs consecutive, please chime in on the issues above and/or leave a comment here.
Tags: