Problem/Motivation

If you add a list field (any data type), set it to multiple and provide a default value, it is impossible to deselect all options for the field. The field system sees that that no values are supplied and supplies the default value on save. The default value populates the form for the field, so if the user explicitly unchecks them, the field system should not inject it's own defaults instead.

Proposed resolution

Indeed, I can reproduce with radios, checkboxes, but actually also for any field type and widget (e.g simple textfield widget for text or number fields).
1. Set a default value on a field
2. Go to the creation form for an entity using that field
3. Remove the pre-filled value (FAPI #default) and leave the field empty, save
4. The entity has the default value for the field.

This happens in field_default_insert(). Since #392696: Save default values on insert, default values get added on "entity insert" when the incoming $entity has no $entity->field_name property. That was to save default values on programmatic entity saves.
Later on, the 'Translatable fields' patch changed the condition, and default values get added also when
isset($entity->field_name[$langcode]) && count($entity->field_name[$langcode]) == 0
Yet $entity->field_name = array($langcode => array()) is exactly what comes out of any field that was left empty.

I cannot say I specifically remember the reasoning behind the code added by the TF patch, but it looks like we should rather check for !isset($entity->field_name[$langcode]).

We'll also need tests for this :-/.

Attachment Size Status Test result Operations
field_default_value-1253820-1.patchREVIEWSIMPLYTEST.ME 751 bytes Idle FAILED: [[SimpleTest]]: [MySQL] 33,661 pass(es), 1 fail(s), and 1 exception(es). View details | Re-test

Remaining tasks

(reviews needed, tests to be written or run, documentation to be written, etc.)

User interface changes

(New or changed features/functionality in the user interface, modules added or removed, changes to URL paths, changes to user interface text.)

API changes

(API changes/additions that would affect module, install profile, and theme developers, including examples of before/after code if appropriate.)

hook_entity_create() is invoked after creating a new entity object

Original report by fearlsgroove

Files: 
CommentFileSizeAuthor
#101 field-default_value-1253820-100.interdiff.do_not_test.patch7.86 KBplach
#100 field-default_value-1253820-100.interdiff.do_not_test.patch7.32 KBplach
#100 field-default_value-1253820-100.test.patch7.3 KBplach
FAILED: [[SimpleTest]]: [MySQL] 50,537 pass(es), 7 fail(s), and 0 exception(s).
[ View ]
#100 field-default_value-1253820-100.patch9.82 KBplach
PASSED: [[SimpleTest]]: [MySQL] 50,621 pass(es).
[ View ]
#96 field-default_value-1253820-96.test.patch1.33 KBplach
FAILED: [[SimpleTest]]: [MySQL] 50,627 pass(es), 2 fail(s), and 0 exception(s).
[ View ]
#96 field-default_value-1253820-96.patch1.96 KBplach
PASSED: [[SimpleTest]]: [MySQL] 50,635 pass(es).
[ View ]
#88 field-default_value-1253820-88.patch648 bytesplach
PASSED: [[SimpleTest]]: [MySQL] 50,772 pass(es).
[ View ]
#82 field-default_value-1253820-82.patch27.7 KBplach
PASSED: [[SimpleTest]]: [MySQL] 50,956 pass(es).
[ View ]
#81 field-default_value-1253820-81.patch0 bytesplach
PASSED: [[SimpleTest]]: [MySQL] 50,844 pass(es).
[ View ]
#75 field-default_value-1253820-75.patch29.43 KBeffulgentsia
FAILED: [[SimpleTest]]: [MySQL] Unable to apply patch field-default_value-1253820-75.patch. Unable to apply patch. See the log in the details link for more information.
[ View ]
#75 interdiff.txt2.96 KBeffulgentsia
#74 field-default_value-1253820-74.interdiff.do_not_test.patch668 bytesplach
#74 field-default_value-1253820-74.patch29.5 KBplach
PASSED: [[SimpleTest]]: [MySQL] 49,752 pass(es).
[ View ]
#69 field-default_value-1253820-69.interdiff.do_not_test.patch782 bytesplach
#69 field-default_value-1253820-69.patch29.23 KBplach
PASSED: [[SimpleTest]]: [MySQL] 49,555 pass(es).
[ View ]
#65 field-default_value-1253820-65.interdiff.do_not_test.patch3.56 KBplach
#65 field-default_value-1253820-65.patch29.22 KBplach
PASSED: [[SimpleTest]]: [MySQL] 50,705 pass(es).
[ View ]
#63 field-default_value-1253820-63.interdiff.do_not_test.patch4.9 KBplach
#63 field-default_value-1253820-63.patch28.58 KBplach
PASSED: [[SimpleTest]]: [MySQL] 50,663 pass(es).
[ View ]
#61 field-default_value-1253820-61.interdiff.do_not_test.patch1.48 KBplach
#61 field-default_value-1253820-61.patch28.83 KBplach
PASSED: [[SimpleTest]]: [MySQL] 50,562 pass(es).
[ View ]
#59 field-default_value-1253820-59.patch28.58 KBplach
PASSED: [[SimpleTest]]: [MySQL] 50,637 pass(es).
[ View ]
#58 field-default_value-1253820-58.interdiff.do_not_test.patch6.16 KBplach
#58 field-default_value-1253820-58.patch28.58 KBplach
PASSED: [[SimpleTest]]: [MySQL] 50,564 pass(es).
[ View ]
#52 field-default_value-1253820-52.interdiff.do_not_test.patch1.1 KBplach
#52 field-default_value-1253820-52.patch23.06 KBplach
PASSED: [[SimpleTest]]: [MySQL] 50,567 pass(es).
[ View ]
#48 field-default_value-1253820-46-tests.patch15.59 KBxjm
FAILED: [[SimpleTest]]: [MySQL] 50,509 pass(es), 3 fail(s), and 4 exception(s).
[ View ]
#48 field-default_value-1253820-46.patch23.21 KBxjm
PASSED: [[SimpleTest]]: [MySQL] 50,601 pass(es).
[ View ]
#46 field-default_value-1253820-46.interdiff.do_not_test.patch7.77 KBplach
#46 field-default_value-1253820-46.patch23.21 KBplach
PASSED: [[SimpleTest]]: [MySQL] 50,117 pass(es).
[ View ]
#44 field-default_value-1253820-44.interdiff.do_not_test.patch4.48 KBplach
#44 field-default_value-1253820-44.patch16.11 KBplach
FAILED: [[SimpleTest]]: [MySQL] 49,447 pass(es), 7 fail(s), and 260 exception(s).
[ View ]
#43 field-default_value-1253820-43.interdiff.do_not_test.patch3.64 KBplach
#43 field-default_value-1253820-43.patch15.27 KBplach
FAILED: [[SimpleTest]]: [MySQL] 49,561 pass(es), 7 fail(s), and 260 exception(s).
[ View ]
#35 field-default_value-1253820-35.patch13.81 KBplach
FAILED: [[SimpleTest]]: [MySQL] 50,195 pass(es), 11 fail(s), and 404 exception(s).
[ View ]
#31 field-default_value-1253820-31.patch7.49 KBplach
FAILED: [[SimpleTest]]: [MySQL] Unable to apply patch field-default_value-1253820-31.patch. Unable to apply patch. See the log in the details link for more information.
[ View ]
#27 default-value-1253820-27.patch2.34 KBtim.plunkett
FAILED: [[SimpleTest]]: [MySQL] 49,380 pass(es), 2 fail(s), and 0 exception(s).
[ View ]
#24 field_default_value-1253820-24.patch2.35 KBzserno
FAILED: [[SimpleTest]]: [MySQL] 40,645 pass(es), 1 fail(s), and 0 exception(s).
[ View ]
#22 field_default_value.patch2.32 KBxjm
FAILED: [[SimpleTest]]: [MySQL] 36,170 pass(es), 1 fail(s), and 0 exception(s).
[ View ]
#1 field_default_value-1253820-1.patch751 bytesyched
FAILED: [[SimpleTest]]: [MySQL] 33,661 pass(es), 1 fail(s), and 1 exception(es).
[ View ]

Comments

yched’s picture

Title:Cannot select no values for a multiple value (radio) list field when a default value is set» entity creation forms : Cannot set "no value" when field has a default value
Version:7.x-dev» 8.x-dev
Priority:Normal» Major
Status:Active» Needs review
StatusFileSize
new751 bytes
FAILED: [[SimpleTest]]: [MySQL] 33,661 pass(es), 1 fail(s), and 1 exception(es).
[ View ]

Indeed, I can reproduce with radios, checkboxes, but actually also for any field type and widget (e.g simple textfield widget for text or number fields).

1. Set a default value on a field
2. Go to the creation form for an entity using that field
3. Remove the pre-filled value (FAPI #default) and leave the field empty, save
4. The entity has the default value for the field.

This happens in field_default_insert(). Since #392696: Save default values on insert, default values get added on "entity insert" when the incoming $entity has no $entity->field_name property. That was to save default values on programmatic entity saves.
Later on, the 'Translatable fields' patch changed the condition, and default values get added also when
isset($entity->field_name[$langcode]) && count($entity->field_name[$langcode]) == 0

Yet $entity->field_name = array($langcode => array()) is exactly what comes out of any field that was left empty.

I cannot say I specifically remember the reasoning behind the code added by the TF patch, but it looks like we should rather check for !isset($entity->field_name[$langcode]).

We'll also need tests for this :-/.

sun’s picture

Status:Needs review» Needs work

The last submitted patch, field_default_value-1253820-1.patch, failed testing.

yched’s picture

Wait, doesn't work. The code comment specifically says "We also check that the current field translation is actually defined before assigning it a default value. This way we ensure that only the intended languages get a default value. Otherwise we could have default values for not yet open languages."

Then I think I'm a bit lost :
- if $entity->field_name[$some_lang] === array() (explicit empty field), we *don't* want a default value (currently broken in D8/D7)
- if $entity->field_name[$some_lang] === NULL, I'd say we *don't* want a default value either - an explicit NULL is understood as 'empty' in other parts of the code.
- if $entity->field_name[$some_lang] is not present, according to the code comment, we wouldn't want a default value, but then again that's precisely the case we wanted to address in #392696: Save default values on insert :

$new_node = (object) array(
  'type' => 'article',
  'title' => 'foobar',
  'body' => array('und' => array(array('value' => 'my body', 'format' => 'filtered_html'))),
);
node_save($new_node);

The code specifies nothing for field_foo, which happens to be configured with a default value, which should then be inserted...

plach’s picture

Aw, this is pretty tricky: while introducing the new language key level the rule of thumb was "if in the old code !isset($entity->field_foo) is valid in the new code, then !isset($entity->field_foo[$langcode]) should be equally valid". Clearly this does not work here.

The code specifies nothing for field_foo, which happens to be configured with a default value, which should then be inserted...

This cannot work unless we are able to define a default value for the field language too:

<?php
$new_node
= (object) array(
 
'type' => 'article',
 
'title' => 'foobar',
 
'body' => array('und' => array(array('value' => 'my body', 'format' => 'filtered_html'))),
);
node_save($new_node);
$new_node->field_foo == array($default_language => array('value' => 'baz'));
?>

I don't think this needs to be a configurable value like the actual value, but it cannot be the default site language. It should be the entity language but we have no such concept in core atm. Anyway I'm going to post a clean up task issue, in order to respond to the many requests for a reliable field_get_items()/field_set_items() pair, that should introduce it. I'll crosspost it here as soon as I write it.

The alternative in the case of a non existing field property might be creating a default value for every available language, but I ain't sure this is a desirable default behavior.

yched’s picture

Maybe : create a default value for each language that is explicitly present in any of the other fields in the incoming entity ?

plach’s picture

I am afraid this cannot always work: suppose only untranslatable fields were provided, we would have no clue about which language pick up. IMO the test should be changed in the following way:

<?php
  
if ((empty($entity) || !property_exists($entity, $field['field_name']) ||
    empty(
$entity->{$field['field_name']}[$langcode])) && $langcode == entity_language($entity_type, $entity)->language) {
?>
plach’s picture

yched’s picture

I am afraid this cannot always work: suppose only untranslatable fields were provided, we would have no clue about which language pick up.

I might be missing something, but I'm not sure that's an issue - we're only dealing with programmatic creation of new entities here. The incoming $entity definition, containing the explicit data provided by the caller, is all we need to care about.

Proposal (assuming we're dealing with 'field_a', configured with a default value) :
- If field_a is not translatable, make sure it gets a value for 'und' language (filling in the default value if not)
- If field_a is translatable, make sure it has a value (filling in the default value if not) in every language present in any of the explicit field values present in the $entity (that is, every language explicitly 'opened' by the incoming $entity definition) - including 'und'.
If no language open (or if $entity has no explicit field value whatsoever) , make sure at least 'und' gets a value.

plach’s picture

- If field_a is translatable, make sure it has a value (filling in the default value if not) in every language present in any of the explicit field values present in the $entity (that is, every language explicitly 'opened' by the incoming $entity definition) - including 'und'.
If no language open (or if $entity has no explicit field value whatsoever) , make sure at least 'und' gets a value.

I ain't sure this would fly: if a field is translatable it's not supposed to hold LANGUAGE_NONE values, unless the entity's language is LANGUAGE_NONE (which atm happens only for nodes). Creating an 'und' value for a translatable field belonging to a node having language 'en' would cause it to disappear from the node form.

IMO introducing an entity_language() function working exactly as entity_label() should give us the default we need and should not be absolutely hard or tricky to implement.

plach’s picture

Issue tags:+translatable fields
webchick’s picture

Issue tags:+needs backport to D7

Fixing tag.

yched’s picture

plach’s picture

@yched: are you ok with #10?

yched’s picture

Sorry for the delay, I had a busy fortnight.

#10 will make it so that on a programmatic creation of an $entity spanning several languages, only the main language will receive default values. That sounds a bit unintuitive / inconsistent. Then again, I'm a bit baffled by the topic, so if that's the best we can do...

What about the following (modified from my #9 to address your #10) :
Assuming we're dealing with 'field_a', configured with a default value,
- If field_a is not translatable, make sure it gets a value for 'und' language (filling in the default value if not)
- If field_a is translatable, make sure it has a value (filling in the default value if not) in every language present in any of the explicit field values present in the $entity (that is, every language explicitly 'opened' by the incoming $entity definition) - not including 'und', but at least including the entity_language().

plach’s picture

I think this can work, any idea which should be the right place to make it happen?

yched’s picture

field_default_insert() is the place. I guess it could use a static var to extract the languages present in the incoming $entity on the 1st call (we can separate different entities because the new $entity being saved gets a nid before field_default_insert() gets called)

I'm leaving tomorrow for 3 weeks away from my coding env, so I probably won't be able to patch this myself, though :-/.

sun’s picture

Title:entity creation forms : Cannot set "no value" when field has a default value» It's impossible to submit no value for a field that has a default value
Issue tags:+Needs tests

Better title. Also, we should really start with tests here.

xjm’s picture

Assigned:Unassigned» xjm

I'll look into a test.

plach’s picture

Here is the entity language issue this one depends on for a proper solution: #1495648: Introduce entity language support.

xjm’s picture

Assigned:xjm» Unassigned
Status:Needs work» Needs review
Issue tags:-Needs tests
StatusFileSize
new2.32 KB
FAILED: [[SimpleTest]]: [MySQL] 36,170 pass(es), 1 fail(s), and 0 exception(s).
[ View ]

Attached exposes the bug.

Status:Needs review» Needs work

The last submitted patch, field_default_value.patch, failed testing.

zserno’s picture

StatusFileSize
new2.35 KB
FAILED: [[SimpleTest]]: [MySQL] 40,645 pass(es), 1 fail(s), and 0 exception(s).
[ View ]

Re-rolled @xjm's test from #22 against HEAD.

zserno’s picture

Status:Needs work» Needs review

Let's ask the bot. It should fail once.

Status:Needs review» Needs work

The last submitted patch, field_default_value-1253820-24.patch, failed testing.

tim.plunkett’s picture

Status:Needs work» Needs review
StatusFileSize
new2.34 KB
FAILED: [[SimpleTest]]: [MySQL] 49,380 pass(es), 2 fail(s), and 0 exception(s).
[ View ]

Rerolled, going to take a look at a fix if I can.

Status:Needs review» Needs work

The last submitted patch, default-value-1253820-27.patch, failed testing.

tim.plunkett’s picture

Yeah, I have no idea how to fix this. I think this one is on yched or fago.

plach’s picture

Assigned:Unassigned» plach

Working on this.

plach’s picture

Status:Needs work» Needs review
StatusFileSize
new7.49 KB
FAILED: [[SimpleTest]]: [MySQL] Unable to apply patch field-default_value-1253820-31.patch. Unable to apply patch. See the log in the details link for more information.
[ View ]

@yched:

I think we have a problem with the proposed solution: it does not consider that empty submitted values are actually filtered out in field_attach_submit(). This makes impossible to skip default values in this case.

The attached patch + tests should fix this bug when doing programmatic insertions, I have no clue how to address this for form-driven insertions (AAMOF the related tests are failing). Any suggestion?

Status:Needs review» Needs work

The last submitted patch, field-default_value-1253820-31.patch, failed testing.

plach’s picture

Status:Needs work» Needs review
yched’s picture

Just thinking out loud, but:
We now have entity_create(), that's used on both programmatic entity creation, and as a preliminary step when showing an "entity add" form.
I'm wondering if this would be the correct place to initialize default values.

Then both flows (programmatic creation & form creation) then become basically the same:
"here's a fresh entity with defaults prepopulated, you can now edit it".

This seems much more sound that what we currently do (populate defaults as default widget values in creation forms + force add them at save time, which clashes).

This being said, I haven't really looked at what the implementation might look like...

plach’s picture

StatusFileSize
new13.81 KB
FAILED: [[SimpleTest]]: [MySQL] 50,195 pass(es), 11 fail(s), and 404 exception(s).
[ View ]

#34 makes sense, although it's hardly not backportable. Here is a patch implementing it. There are some failing tests and API docs are missing, but this needs an architectural review before fixing those issues.

Status:Needs review» Needs work

The last submitted patch, field-default_value-1253820-35.patch, failed testing.

plach’s picture

Status:Needs work» Needs review
fago’s picture

We now have entity_create(), that's used on both programmatic entity creation, and as a preliminary step when showing an "entity add" form.
I'm wondering if this would be the correct place to initialize default values.

I think so as well. For the Entity Field API we have #1777956: Provide a way to define default values for entity fields - let's sure to get it right there. Then for d8 we can inherit the solution by basing field API on the Entity Field API.

plach’s picture

Status:Needs review» Postponed

What do you think about #35? Could we move the approach there in the other issue and expand it to the Entity Field API?

fago’s picture

Status:Postponed» Needs work
+++ b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php
@@ -444,6 +444,10 @@ public function create(array $values) {
+    // Modules might need to add or change the data intially held by the new
+    // entity object, for instance to fill-in default values.
+    drupal_alter(array($this->entityType . '_entity', 'entity'), $entity);
+

I agree that this hook is a good idea - I already thought about doing that also. Imho it should be hook_entity_create() and follow the entity+$entity_type hook pattern. "Act on entity creation." ?

There is a typo in "intially".

Still, I think it would be preferable to have default values applied via Entity Field API and keep the hook for any custom alterations of them.
Still, we could apply all default values there meanwhile?

Generally, I think the patch is a good improvement so I guess it's a good idea to do this now, while improving things with Entity Field API in a second step. Thus, setting to needs work for the hook name and docs.

yched’s picture

The logic around default values in WidgetBase::form() will need to be removed too.

plach’s picture

Issue tags:-needs backport to D7

Also, per #31, I think we cannot fix this in D7 unless we do very radical changes to the current behavior, which I guess we don't want to do at this point of D7 lifecycle.

plach’s picture

Status:Needs work» Needs review
StatusFileSize
new15.27 KB
FAILED: [[SimpleTest]]: [MySQL] 49,561 pass(es), 7 fail(s), and 260 exception(s).
[ View ]
new3.64 KB

This should address @fago's review.

plach’s picture

StatusFileSize
new16.11 KB
FAILED: [[SimpleTest]]: [MySQL] 49,447 pass(es), 7 fail(s), and 260 exception(s).
[ View ]
new4.48 KB

Ups, I missed #41. Here's a new one and an interdiff against #35.

Status:Needs review» Needs work

The last submitted patch, field-default_value-1253820-44.patch, failed testing.

plach’s picture

Status:Needs work» Needs review
StatusFileSize
new23.21 KB
PASSED: [[SimpleTest]]: [MySQL] 50,117 pass(es).
[ View ]
new7.77 KB

This should fix the latest failures hopefully.

plach’s picture

I thought we were close to RTBC with this: @fago or @yched can you confirm? This will help with the thresholds.

xjm’s picture

StatusFileSize
new23.21 KB
PASSED: [[SimpleTest]]: [MySQL] 50,601 pass(es).
[ View ]
new15.59 KB
FAILED: [[SimpleTest]]: [MySQL] 50,509 pass(es), 3 fail(s), and 4 exception(s).
[ View ]
+++ b/core/modules/field/field.moduleundefined
@@ -387,6 +388,44 @@ function field_data_type_info() {
+ * @param $entity
+ *   The entity for the operation.
+ * @param $langcode
+ *   (optional) The field language to fill-in with the default value. Defaults
+ *   to the entity language.

These need parameter typehints (no changes yet).

Attached is #46 with a test-only patch including my original test and other test hunks.

yched’s picture

Many thanks @plach !

phpdoc for field_populate_default_values():

+/**
+ * Inserts a default value if no $entity->$field_name entry was provided.
+ *
+ * This can happen with programmatic saves, or on form-based creation where
+ * the current user doesn't have 'edit' permission for the field. This is the
+ * default field 'insert' operation.

This is directly copied from field_default_insert() but doesn't really reflect the new code flow now - this happens on entity_create(), so before and independently of both form submits and programmatic saves, and before an $entity->$field_name can even "be provided" ?

Also, do we really need those checks in the function now ? :

// We need to preserve existing values.
+    if (empty($entity->{$field_name}) || !array_key_exists($field_langcode, $entity->{$field_name})) {

Probably related: I'm not sure about field_populate_default_values() as a standalone function. We only intend to do this on hook_entity_create(), not provide this as a general "you might want to do that too" API function, right ? (at least we currently don't, so I'm not sure we need to bother with this additional "api feature").
So maybe just inline the code in field_entity_create(), or at least use an underscored helper func ? (personal pref for the former I guess)

The last submitted patch, field-default_value-1253820-46.patch, failed testing.

plach’s picture

Status:Needs work» Needs review
Issue tags:+translatable fields

#48: field-default_value-1253820-46.patch queued for re-testing.

plach’s picture

StatusFileSize
new23.06 KB
PASSED: [[SimpleTest]]: [MySQL] 50,567 pass(es).
[ View ]
new1.1 KB

This is directly copied from field_default_insert() but doesn't really reflect the new code flow now - this happens on entity_create(), so before and independently of both form submits and programmatic saves, and before an $entity->$field_name can even "be provided" ?

Right! Removed all that obsolete stuff and simplified the PHP doc as now there is not much to say about it. Fixed also missing type hints.

Also, do we really need those checks in the function now ? :

Yes. When calling entity_create() you can pass an array of initial values, without those checks the default values here might override those. There is a new test covering this.

Probably related: I'm not sure about field_populate_default_values() as a standalone function.

The main use case I have in mind is supporting the ability of populating defaults when adding a translation: in that case you already have an existing entity object, but you need to fill-in the defaults for the new language.

yched’s picture

@plach #52 :

The main use case I have in mind is supporting the ability of populating defaults when adding a translation: in that case you already have an existing entity object, but you need to fill-in the defaults for the new language.

OK, makes sense. Where would that happen though ? In core ? In custom code ?

plach’s picture

I'm guessing in the Entity API, we will need to either introduce entity translation CRUD hooks or make entity translations entities, see #1810370: Entity Translation API improvements.

yched’s picture

OK, thanks for the explanation.

The changes here are fine by me, I'll let @fago vouch for the entity API part and set to RTBC.

plach’s picture

@fago already gave his ok in #40, except for two minor things fixed immediately afterwards :)

Sorry, he didn't review the last entity API changes.

fago’s picture

Status:Needs review» Needs work

The main use case I have in mind is supporting the ability of populating defaults when adding a translation: in that case you already have an existing entity object, but you need to fill-in the defaults for the new language.

OK, makes sense. Where would that happen though ? In core ? In custom code ?

I'm unsure here, as this would only cover defaults set by field module. Once we allow setting them via the hook you won't cover others. While this would be solved by having default-values as part of EntityNG/TypedData though, I think we should really start flushing out entity translation-crud API design now (or leave it).
Summarized, I'd see the in-line variant as the right solution, but we can still inline it once we've solved the translation use-case otherwise.

+++ b/core/includes/entity.api.php
@@ -56,6 +56,18 @@ function hook_entity_info_alter(&$entity_info) {
+ * Act on entity creation.
+ *
+ * @param Drupal\Core\Entity\EntityInterface $entity
+ *   The entity object.

Sounds good. Maybe, should we mention in an extra line that this may be used to set custom default values?

+++ b/core/includes/entity.api.php
@@ -56,6 +56,18 @@ function hook_entity_info_alter(&$entity_info) {
+function hook_entity_create(Drupal\Core\Entity\EntityInterface $entity) {

This, adds hook_node_create() and so on also - thus we should create respective API docs for all entity types also.

+++ b/core/includes/entity.api.php
@@ -56,6 +56,18 @@ function hook_entity_info_alter(&$entity_info) {
+  if (!isset($entity->foo)) {
+    $entity->foo = 'bar';
+  }

Minor, but let's do an example that already uses Entity-NG here? -> Less code to convert.

+++ b/core/lib/Drupal/Core/Entity/Entity.php
@@ -263,7 +263,8 @@ public function access($operation = 'view', \Drupal\user\Plugin\Core\Entity\User
-    return !empty($this->langcode) ? language_load($this->langcode) : new Language(array('langcode' => LANGUAGE_NOT_SPECIFIED));
+    $language = !empty($this->langcode) ? language_load($this->langcode) : FALSE;
+    return $language ?: new Language(array('langcode' => LANGUAGE_NOT_SPECIFIED));

Seems to be an unrelated change? Howsoever, I'm not sure the new code is better than the old one. Maybe just convert it to multiple lines using if()/else()?

+++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentTestBase.php
@@ -145,7 +145,13 @@ function postComment($node, $comment, $subject = '', $contact = NULL) {
-      return entity_create('comment', array('id' => $match[1], 'subject' => $subject, 'comment' => $comment));
+      $values = array(
+        'id' => $match[1],
+        'node_type' => is_object($node) ? 'node_comment_' . $node->bundle() : '',
+        'subject' => $subject,
+        'comment' => $comment,
+      );
+      return entity_create('comment', $values);

This clashes with the comment-conversion patch, which contains a proper fix for it (loading the comment). But, if this one goes in first it should be fine as interim improvement.

Setting to needs-work for the per entity-type API docs.

plach’s picture

Status:Needs work» Needs review
StatusFileSize
new28.58 KB
PASSED: [[SimpleTest]]: [MySQL] 50,564 pass(es).
[ View ]
new6.16 KB

Here is a patch adding the missing PHP docs.

Seems to be an unrelated change? Howsoever, I'm not sure the new code is better than the old one. Maybe just convert it to multiple lines using if()/else()?

It's needed to make a test pass: previously if you created an entity using a non exisiting language code the language() method returned false, which made lots of things go wrong.

plach’s picture

StatusFileSize
new28.58 KB
PASSED: [[SimpleTest]]: [MySQL] 50,637 pass(es).
[ View ]
+++ b/core/modules/file/file.api.php
@@ -7,6 +7,21 @@
+ * This can be used to set initial values for the file, for instance to
+ * provide defaults.

+++ b/core/modules/node/node.api.php
@@ -535,6 +538,23 @@ function hook_node_insert(Drupal\node\Node $node) {
+ *
+ * This can be used to set initial values for the node, for instance to
+ * provide defaults.

+++ b/core/modules/taxonomy/taxonomy.api.php
@@ -110,6 +125,21 @@ function hook_taxonomy_vocabulary_delete(Drupal\taxonomy\Plugin\Core\Entity\Voca
+ * This can be used to set initial values for the term, for instance to
+ * provide defaults.

+++ b/core/modules/user/user.api.php
@@ -13,6 +13,21 @@
+ * This can be used to set initial values for the user, for instance to
+ * provide defaults.

Comment not wrapping correctly.

fago’s picture

+++ b/core/lib/Drupal/Core/Entity/Entity.php
@@ -263,7 +263,8 @@ public function access($operation = 'view', \Drupal\user\Plugin\Core\Entity\User
-    return !empty($this->langcode) ? language_load($this->langcode) : new Language(array('langcode' => LANGUAGE_NOT_SPECIFIED));
+    $language = !empty($this->langcode) ? language_load($this->langcode) : FALSE;
+    return $language ?: new Language(array('langcode' => LANGUAGE_NOT_SPECIFIED));

I see. In entityNG we have this workaround for the same problem:

  /**
   * Implements TranslatableInterface::language().
   */
  public function language() {
    $language = $this->get('langcode')->language;
    if (!$language) {
      // Make sure we return a proper language object.
      // @todo Refactor this, see: http://drupal.org/node/1834542.
      $language = language_default();
    }
    return $language;
  }

Can we unify the work-a-rounds? LANGUAGE_NOT_SPECIFIED sounds reasonable.
Also could we just use if/else instead of ?: - imho having multiple lines of ?: statements is too much.

+ * This can be used to set initial values for the term, for instance to
+ * provide defaults.

Is there a difference between initial values and default values?? If not, let's just use "This can be used to set custom default values for entity fields."

plach’s picture

StatusFileSize
new28.83 KB
PASSED: [[SimpleTest]]: [MySQL] 50,562 pass(es).
[ View ]
new1.48 KB

This should unify Entity::language() and EntityNG::language() as much as possible. We will be able to rely on hook_entity_create() in the Language module to provide an initial value of the langcode key based on the content language setting, thus effectively removing the possibilty of not having a valid langcode in most situations.

Is there a difference between initial values and default values?? If not, let's just use "This can be used to set custom default values for entity fields."

Well, actually "initial" sounds slightly more generic to me: the new hook could be used to provide a value just in a particular circumstance. In this case I wouldn't call it a default value.

fago’s picture

Status:Needs review» Needs work

Thanks, improvements look good!

Well, actually "initial" sounds slightly more generic to me: the new hook could be used to provide a value just in a particular circumstance. In this case I wouldn't call it a default value.

I see, ok.

Sry for not coming up with this previously, but I just realized that the hook description might be confused by devs with hook_comment_insert(). Let's clarify this better. Also, what we are acting on is the entity (object) so what about that:

* Act on a newly created comment.
*
* This hook runs after a new comment object has been created, without
* permanently saving it. It can be used to set initial values for the comment,
* for instance to provide defaults.

?

Then, another thing we missed previously: field_entity_create() should check whether the entity is fieldable.

plach’s picture

Status:Needs work» Needs review
StatusFileSize
new28.58 KB
PASSED: [[SimpleTest]]: [MySQL] 50,663 pass(es).
[ View ]
new4.9 KB

Added the fieldable check and improved PHP docs. I slightly adjusted your suggestion, because I think that the "instantiation" term should give a developer a clear idea of which phase we are talking about. I also streamlined the following sentence a bit to avoid repetitions.

fago’s picture

+++ b/core/modules/comment/comment.api.php
@@ -49,6 +49,21 @@ function hook_comment_update(Drupal\comment\Comment $comment) {
+ * This hook runs after a new comment object has just been instantiated. It can
+ * be used to set initial values, e.g. to provide defaults.

Works for me - I'd just remove the "just" as we do not use it other hook descriptions either.

plach’s picture

StatusFileSize
new29.22 KB
PASSED: [[SimpleTest]]: [MySQL] 50,705 pass(es).
[ View ]
new3.56 KB

Here it is :)

fago’s picture

Status:Needs review» Reviewed & tested by the community

Thanks - looks good.

tim.plunkett’s picture

Status:Reviewed & tested by the community» Needs work
+++ b/core/modules/comment/comment.api.phpundefined
@@ -49,6 +49,21 @@ function hook_comment_update(Drupal\comment\Comment $comment) {

+++ b/core/modules/file/file.api.phpundefined
@@ -7,6 +7,21 @@
+function hook_file_create(\Drupal\file\Plugin\Core\Entity\File $file) {

+++ b/core/modules/node/node.api.phpundefined
@@ -535,6 +538,23 @@ function hook_node_insert(Drupal\node\Node $node) {
+function hook_node_create(\Drupal\node\Plugin\Core\Entity\Node $node) {

+++ b/core/modules/taxonomy/taxonomy.api.phpundefined
@@ -13,6 +13,21 @@
+function hook_taxonomy_vocabulary_create(\Drupal\taxonomy\Plugin\Core\Entity\Vocabulary $vocabulary) {

@@ -110,6 +125,21 @@ function hook_taxonomy_vocabulary_delete(Drupal\taxonomy\Plugin\Core\Entity\Voca
+function hook_taxonomy_term_create(\Drupal\taxonomy\Plugin\Core\Entity\Term $term) {

+++ b/core/modules/user/user.api.phpundefined
@@ -13,6 +13,21 @@
+function hook_user_create(\Drupal\user\Plugin\Core\Entity\User $user) {

I think these should be generically in entity.api.php as hook_ENTITY_TYPE_create()

+++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentTestBase.phpundefined
@@ -145,7 +145,13 @@ function postComment($node, $comment, $subject = '', $contact = NULL) {
+      $values = array(
+        'id' => $match[1],
+        'node_type' => is_object($node) ? 'node_comment_' . $node->bundle() : '',
+        'subject' => $subject,
+        'comment' => $comment,
+      );
+      return entity_create('comment', $values);

You could still avoid the extra $values, just have

entity_create('comment', array(
...
);

on multiple lines

fago’s picture

I think these should be generically in entity.api.php as hook_ENTITY_TYPE_create()

While this is a good suggestion, it's not what we do currently. We should follow the current way of doing things and address this question in a general issue for all entity_* hooks, i.e. #1824184: Remove and replace entity type specific API hook documentation with generic entity API hook documentation.

plach’s picture

Status:Needs work» Needs review
StatusFileSize
new29.23 KB
PASSED: [[SimpleTest]]: [MySQL] 49,555 pass(es).
[ View ]
new782 bytes

I agree with @fago: let's stick with the current way of doing things for now. If/when the documentation policy changes all the docs will be updated together.

The attached patch inlines the $values array, although I don't see it exactly as a readability improvement.

fago’s picture

Status:Needs review» Reviewed & tested by the community

ok, back to RTBC then.

tim.plunkett’s picture

Ah, sorry! I often forget if policy changes like that have been adopted already, I was indeed thinking of that exact issue. Carry on. :)

effulgentsia’s picture

Status:Reviewed & tested by the community» Needs work
+++ b/core/includes/entity.api.php
@@ -56,6 +56,21 @@ function hook_entity_info_alter(&$entity_info) {
/**
+ * Act on a newly created entity.
+ *
+ * This hook runs after a new entity object has just been instantiated. It can
+ * be used to set initial values, e.g. to provide defaults.
+ *
+ * @param \Drupal\Core\Entity\EntityInterface $entity
+ *   The entity object.
+ */
+function hook_entity_create(\Drupal\Core\Entity\EntityInterface $entity) {
+  if (!isset($entity->foo)) {
+    $entity->foo->value = 'bar';
+  }
+}
+

What's not stated in the docs or clear from the name 'foo' is that this is only needed for entity fields/properties that aren't managed by field.module.

Also this example and the ones for the specific entity type hooks (hook_node_create(), etc.) assume EntityNG, but that's not stated anywhere, so if someone copies this documentation into a module they're working on now, it won't work. Perhaps a @todo would help clarify.

Unless someone beats me to it, I'll give the docs here a crack in a couple hours to keep this moving.

plach’s picture

What's not stated in the docs or clear from the name 'foo' is that this is only needed for entity fields/properties that aren't managed by field.module.

Actually this would be false :) As I was saying in #61, the new hook could be used to provide a value just in a particular circumstance. In this case I wouldn't call it a default value and it wouldn't necessarily handled by the Field API.

Agreed with the @todo.

plach’s picture

Status:Needs work» Needs review
StatusFileSize
new29.5 KB
PASSED: [[SimpleTest]]: [MySQL] 49,752 pass(es).
[ View ]
new668 bytes

Added @todo.

effulgentsia’s picture

StatusFileSize
new2.96 KB
new29.43 KB
FAILED: [[SimpleTest]]: [MySQL] Unable to apply patch field-default_value-1253820-75.patch. Unable to apply patch. See the log in the details link for more information.
[ View ]

How about this? Similar spirit on the hook_entity_create() example, but for the specific entity types, matches what's already in HEAD for the _load() hook. Both will need to be converted as part of each entity type's conversion to EntityNG, but it shouldn't be harder to convert both together than just the _load() one alone, and in the meantime, we keep them consistent with each other.

plach’s picture

Status:Needs review» Reviewed & tested by the community

Works for me.

fago’s picture

Yep, indeed the docu should match the current state of the API. +1

Dries’s picture

#75: field-default_value-1253820-75.patch queued for re-testing.

Dries’s picture

I reviewed the patch and it looks good at first sight. Could have done a more in-depth review though. Stopped reviewing this patch because I don't think it currently applies. Asked for a re-test.

Status:Reviewed & tested by the community» Needs work

The last submitted patch, field-default_value-1253820-75.patch, failed testing.

plach’s picture

Status:Needs work» Needs review
StatusFileSize
new0 bytes
PASSED: [[SimpleTest]]: [MySQL] 50,844 pass(es).
[ View ]

Rerolled

plach’s picture

StatusFileSize
new27.7 KB
PASSED: [[SimpleTest]]: [MySQL] 50,956 pass(es).
[ View ]

hm

fago’s picture

Status:Needs review» Reviewed & tested by the community

Reroll looks good. Back to RTBC then.

webchick’s picture

Title:It's impossible to submit no value for a field that has a default value» Change notice: It's impossible to submit no value for a field that has a default value
Category:bug» task
Priority:Major» Critical
Status:Reviewed & tested by the community» Active
Issue tags:+Needs change record

Alex and I actually spent a bit of time walking through this the other day, and I think it's good to go.

Committed and pushed to 8.x. Thanks!

We should add a change notice for this.

plach’s picture

Status:Active» Needs review
Issue tags:-Needs change record

Here is the change notice:

http://drupal.org/node/1891514

tim.plunkett’s picture

Any reason that invokeHook() wasn't added to ConfigStorageController as well?

plach’s picture

I think it's just an oversight :)

Small follow-up?

plach’s picture

Status:Needs work» Needs review
Issue tags:-Needs tests
StatusFileSize
new648 bytes
PASSED: [[SimpleTest]]: [MySQL] 50,772 pass(es).
[ View ]

Here is a fix for #86. Btw, I think we should provide an abstract common ancestry to config and content entities when working on storage-agnostic entities and the Storage API.

Edit: I meant config and content entity storage controllers, obviously :)

chx’s picture

The change notice is confusing:

A new hook has been introduced to allow to alter the initial values an entity object holds when it is instantiated for the first time.

Does that mean that after installing the entity providing module the first ever entity_create('foo') fires this hook or that on every request the first call to entity_create('foo') fires the hook? If the latter...

A new hook has been introduced to allow to alter the initial values an entity object holds. The results of this hook is statically cached so it fires only once per request.

plach’s picture

Status:Reviewed & tested by the community» Needs review

Nope, I just meant that it's fired on every entity_create() but not when instantiating the object on entity load. Tried to make the first paragraph more clear.

tim.plunkett’s picture

Status:Needs review» Reviewed & tested by the community

Thanks for #88

webchick’s picture

Status:Needs review» Needs work
Issue tags:+Needs tests

Hm. Seems like it should be possible to write a test for #86/#88, no?.

chx’s picture

Hm, I do not see a new revision on the change notice.

plach’s picture

chx’s picture

Status:Needs review» Needs work
Issue tags:+Needs tests

That's not helping at all. You need to clarify whether "first time" means install time (more or less) or per request first time.

plach’s picture

Status:Needs work» Needs review
StatusFileSize
new1.96 KB
PASSED: [[SimpleTest]]: [MySQL] 50,635 pass(es).
[ View ]
new1.33 KB
FAILED: [[SimpleTest]]: [MySQL] 50,627 pass(es), 2 fail(s), and 0 exception(s).
[ View ]

Ok, I removed the "first time" terminology since it is misleading. Hope now it looks better.

The attached patch provides test coverage for #88. There's also a test only version to prove it.

fago’s picture

hm, I think the change notice was unnecessarily complex so I revised it. I also removed the field API example and replaced it by a simpler one. So what about that? http://drupal.org/node/1891514

fago’s picture

Status:Needs review» Needs work

Hm. Seems like it should be possible to write a test for #86/#88, no?.

True - that said we already have an EntityCRUDTestCase which tests for hook invocations. We should test this hook there also.

plach’s picture

Title:Change notice: It's impossible to submit no value for a field that has a default value» (Tests for) It's impossible to submit no value for a field that has a default value
Priority:Critical» Normal

So what about that?

I think the new version lost lots of useful information about the entity-type specific version of the hook and the main rationale behind it. Anyway, it seems I couldn't get anyone to like it so let's call that part closed.

plach’s picture

Status:Needs work» Needs review
StatusFileSize
new9.82 KB
PASSED: [[SimpleTest]]: [MySQL] 50,621 pass(es).
[ View ]
new7.3 KB
FAILED: [[SimpleTest]]: [MySQL] 50,537 pass(es), 7 fail(s), and 0 exception(s).
[ View ]
new7.32 KB

Let's see what the bot thinks about this.

plach’s picture

Sorry, this is the correct interdiff.

plach’s picture

Status:Needs review» Reviewed & tested by the community

#100 addresses @webchick's and @fago's concerns so back to RTBC.

webchick’s picture

Status:Reviewed & tested by the community» Fixed

Awesome! Glad to see that the tests helped flesh out a few more places we needed this. Sorry for the hassle.

Committed and pushed to 8.x. Thanks!

plach’s picture

Title:(Tests for) It's impossible to submit no value for a field that has a default value» It's impossible to submit no value for a field that has a default value
Category:task» bug
Priority:Normal» Major

Awesome, thanks!

panche’s picture

Status:Closed (fixed)» Fixed

If a poor soul reaches this thread and really needs a solution for D7, here's what I did:

Implement a hook_form_alter() like this:

<?php
function mymodule_form_node_form_alter(&$form, &$form_state, $form_id) {
 
$is_new = !empty($form['nid']) && $form['nid']['#value'] == NULL;
 
// The node is new and the field exists in that content type
 
if($is_new && !empty($form['field_myfield'])){
   
$form['#submit'][] = 'mymodule_blank_values_submit';
   
array_reverse($form['#submit']); // First call mymodule_blank_values_submit()
 
}
}
?>

And set a flag in the node object to be used later if the value in the $form_state is an empty string.
The submit callback looks like this:

<?php
function mymodule_blank_values_submit(&$form, &$form_state) { 
  if(
$form_state['values']['field_myfield'][LANGUAGE_NONE][0]['value'] === ''){
   
$form_state['node']->data['empty_myfield'] = TRUE;
  } else {
    
$form_state['node']->data['empty_myfield'] = FALSE;
  }
 }
?>

And finally to check if the node object has the flag with hook_node_insert(), and empty the field value:

<?php
function mymodule_node_insert($node){
  if(!empty(
$node->field_myfield) && $node->data['empty_myfield']){
   
$node->field_myfield[LANGUAGE_NONE][0]['value'] = '';
  }
}
?>

Status:Fixed» Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.

ParisLiakos’s picture

i just stepped on this:/
@panche i couldnt make it work with hook_node_insert..even if fields are actually saved after the hook, it wouldnt work, so i used hook_node_presave.
also no need for the form alter, hook_node_submit also works..here is how i did it:

<?php
/**
 * Implements hook_node_submit().
 * Workaround a core bug that wont be fixed in d7
 * @see http://drupal.org/node/1253820
 */
function MYMODULE_node_submit($node, $form, &$form_state) {
   if (
$node->nid === NULL && isset($form_state['values']['field_myfield'])) {
    
$node->temp['empty_field_myfield'] = $form_state['values']['field_myfield'][LANGUAGE_NONE][0]['value'] === NULL;
   }
}

/**
 * Implements hook_node_presave().
 */
function MYMODULE_node_presave($node) {
 
// @see MYMODULE_node_submit().
 
if(!empty($node->field_myfield) && !empty($node->temp['empty_field_myfield'])) {
   
$node->field_myfield = array();
  }
}
?>
YesCT’s picture

David_Rothstein’s picture

Version:8.x-dev» 7.x-dev
Assigned:plach» Unassigned
Status:Closed (fixed)» Active

This is still an active bug for Drupal 7, though sounds like a different approach than the Drupal 8 patch will be needed?

plach’s picture

I thought that per #31 we agreed that we cannot fix this in D7 without making very radical changes.

dcam’s picture

http://drupal.org/node/1427826 contains instructions for updating the issue summary with the summary template.

The summary may need to be updated with information from comments.

Pancho’s picture

Status:Active» Needs review
Issue tags:-Needs tests, -Needs issue summary update, -translatable fields

@plach:
Well, #34 ff. aren't backportable, but #31 should be, though it's currently incomplete. Let's see how far we can get on this road, so:

field-default_value-1253820-31.patch queued for re-testing.

Pancho’s picture

Issue summary:View changes

Updated issue summary.