While converting https://www.drupal.org/project/restrict_by_ip to D8, I found a bug that occurs when you add a form field to the user registration form with hook_form_alter() and try to access the form value in hook_user_insert(). In D7, the additional form values are available in the $edit parameter of hook_user_insert(). In D8, the additional form values are not available in the $account parameter of hook_user_insert().

The following change records describe how to convert hook_user_insert():
https://www.drupal.org/node/2294409
https://www.drupal.org/node/1554986

The form alter doesn't change:

function MODULE_form_user_register_form_alter(&$form, $form_state) {
  $form['some_extra_field'] = array(
    '#type' => 'hidden',
    '#value' => 'querty',
  );
}

D7 hook_user_insert():

function MODULE_user_insert(&$edit, $account, $category) {
  if (isset($edit['some_extra_field'])) {
    // Do something here.
  }
}

D8 hook_user_insert():

function MODULE_user_insert($account) {
  if (isset($account->some_extra_field)) {
    // Do something here.
  }
}

The problem is that $account->some_extra_field doesn't exist. I inspected the $account object in PHPStorm and didn't find the additional form values anywhere.

CommentFileSizeAuthor
#2 2563697-2.patch761 bytesrocketeerbkw
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

rocketeerbkw created an issue. See original summary.

rocketeerbkw’s picture

Status: Active » Needs review
FileSize
761 bytes

Here's a patch that fixed it for me, but I need some help from testbot.

rocketeerbkw’s picture

Assigned: rocketeerbkw » Unassigned

Tests passed!

+++ b/core/lib/Drupal/Core/Entity/ContentEntityForm.php
@@ -222,8 +222,8 @@ protected function copyFormValuesToEntity(EntityInterface $entity, array $form,
-      if ($entity->hasField($name) && !isset($extracted[$name])) {
-        $entity->set($name, $values);
+      if (!isset($extracted[$name])) {
+        $entity->$name = $values;

$entity->set() looks like it only works for saving Field API values. But if you look at __set() on Drupal\Core\Entity\ContentEntityBase you'll see it already handles setting values for fields or, if no fields match, setting it directly on the object. This makes __get() able to retrieve the value off the object when it's used in hook_user_insert().

I also tested loading the user entity after all the hooks are done, in a different part of the system. $account->some_extra_field was not available so it's not being saved as extra data, just being persisted to hook_user_insert().

rocketeerbkw’s picture

This is a soft blocker for https://www.drupal.org/project/restrict_by_ip. It breaks an admin only convenience when adding a user, but won't keep the module from working as intended.

I'm not sure how to write a simpletest that checks the interaction between two hook functions. If someone can help with methodology, I can write the tests.

Berdir’s picture

Yes, this is by design.

You can either use an entity_builder callback or just store your value through a submit callback.

See menu_ui_form_node_form_alter() + its submit callback menu_ui_form_node_form_submit() or menu_ui_form_node_type_form_alter() and the entity builder menu_ui_form_node_type_form_builder() for examples.

Entities are typed, defined objects. We no longer put arbitrary stuff on them.

rocketeerbkw’s picture

Status: Needs review » Closed (works as designed)

I needed the data from the form I added to the registration field AND the final users ID to do what I needed.

It turns out that the user ID is available in a submit callback after all. You can get the entity from $form_state:

$user = $form_state->getFormObject()->getEntity();