Problem/Motivation

At the moment the ChangedItem will check all fields for changes in its preSave function, but if there are other fields defined which are updating the field value in the preSave function then the ChangedItem::preSave is not going to detect this change, because it will be one of the first that is called.

Proposed resolution

Make sure ChangedItem::preSave is called as the last one of all other FieldItems::preSave.. This could be done by ensuring that EntityFieldManager::getFieldDefinitions will return an array of field definitions, where the changed field definition is at the end.

Remaining tasks

none

User interface changes

none

API changes

none

Data model changes

none

CommentFileSizeAuthor
#78 interdiff_2617628_77-78.txt6.69 KBankithashetty
#78 reroll_diff_2617628_69-78.txt7.21 KBankithashetty
#78 2617628-78.patch13.42 KBankithashetty
#77 2617628-76.patch12.18 KBmrweiner
#69 interdiff-67-69.txt1.47 KBgease
#69 2617628-69.patch13.36 KBgease
#67 2617628-67.patch11.62 KBgease
#64 2617628-64.patch11.55 KBtstoeckler
#64 2617628-62-64-interdiff.txt1.01 KBtstoeckler
#62 2617628-62_.patch10.92 KBtstoeckler
#60 2617628-60.patch10.94 KBtstoeckler
#60 2617628-58-60-interdiff.txt3.11 KBtstoeckler
#58 2617628-58.patch7.92 KBhchonov
#54 interdiff-48-54.txt9.53 KBhchonov
#54 2617628-54.patch7.78 KBhchonov
#54 2617628-54-test-only.patch4.19 KBhchonov
#48 2617628-48.patch7.87 KBhchonov
#48 2617628-48-test-only.patch3.68 KBhchonov
#47 interdiff-46-47.txt2.29 KBhchonov
#47 2617628-47.patch7.87 KBhchonov
#46 interdiff-43-46.txt901 byteshchonov
#46 2617628-46.patch7.45 KBhchonov
#43 interdiff-38-43.txt1.18 KBhchonov
#43 2617628-43.patch7.27 KBhchonov
#38 interdiff-36-38.txt1.24 KBhchonov
#38 2617628-38.patch7.1 KBhchonov
#36 interdiff-34-36.txt782 byteshchonov
#36 2617628-36.patch7.07 KBhchonov
#34 2617628-34.patch7.13 KBMunavijayalakshmi
#31 interdiff-28-31.txt2.4 KBhchonov
#31 2617628-31.patch7.15 KBhchonov
#28 interdiff-25-28.txt3.13 KBhchonov
#28 2617628-28.patch4.58 KBhchonov
#25 interdiff-23-25.txt2.6 KBhchonov
#25 2617628-25.patch5.56 KBhchonov
#23 interdiff-14-23.txt1.17 KBhchonov
#23 2617628-23.patch6.08 KBhchonov
#14 interdiff-11-14.txt3.68 KBhchonov
#14 2617628-14.patch6.03 KBhchonov
#11 9-11-interdiff.txt1.14 KBhchonov
#11 2617628-11.patch6.01 KBhchonov
#9 6-9-interdiff.txt4.53 KBhchonov
#9 2617628-9.patch4.73 KBhchonov
#6 3-6-interdiff.txt2.1 KBhchonov
#6 2617628-6.patch6.8 KBhchonov
#3 2-3-interdiff.txt653 byteshchonov
#3 2617628-3.patch4.56 KBhchonov
#2 2617628-2.patch4.53 KBhchonov
changed_item_presave_not_called_as_last.patch3.21 KBhchonov

Issue fork drupal-2617628

Command icon 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:

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

hchonov created an issue. See original summary.

hchonov’s picture

Here the proposed resolution with the test together.

hchonov’s picture

added missing function documentation...

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

The last submitted patch, 2: 2617628-2.patch, failed testing.

hchonov’s picture

The last submitted patch, 3: 2617628-3.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 6: 2617628-6.patch, failed testing.

hchonov’s picture

Issue summary: View changes
Status: Needs work » Needs review
FileSize
4.73 KB
4.53 KB

Status: Needs review » Needs work

The last submitted patch, 9: 2617628-9.patch, failed testing.

hchonov’s picture

Status: Needs work » Needs review
FileSize
6.01 KB
1.14 KB
xjm’s picture

Issue tags: -rc target triage

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

hchonov’s picture

Rerolling for 8.1.x.

fago’s picture

Make sure ChangedItem::preSave is called as the last one of all other FieldItems::preSave.. This could be done by ensuring that EntityFieldManager::getFieldDefinitions will return an array of field definitions, where the changed field definition is at the end.

That sounds brittle to me. I'd rather convert the code to a differnt approach taking onChange() into account for figuring out changed fields - as discussed in https://www.drupal.org/node/2456257

hchonov’s picture

@fago but like discussed in the other issue onChange is not a solution as well, because it fires even if you set the same value again ...

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

fago’s picture

@fago but like discussed in the other issue onChange is not a solution as well, because it fires even if you set the same value again ...

Well, in generally it does not solve the timing/order issue :/

Still, I'd favour some explicit instead of magic ordering of field definitions. E.g., why not let the storage update the changed field based upon EntityChangedInterface? It might be tricky to get this done in a BC manner though.

Berdir’s picture

+++ b/core/lib/Drupal/Core/Entity/EntityFieldManager.php
@@ -304,6 +304,19 @@ public function getFieldDefinitions($entity_type_id, $bundle) {
+    if ($this->entityTypeManager->getDefinition($entity_type_id)->isSubclassOf(EntityChangedInterface::class) && isset($this->fieldDefinitions[$entity_type_id][$bundle]['changed'])) {

we deprecate isSubclassOf(), I suggest you just use getClass() check that directly.

hchonov’s picture

Version: 8.2.x-dev » 8.3.x-dev
plach’s picture

Issue tags: +Triaged core major

This was discussed with @alexpott, @amateescu, @berdir, @cilefen, @fago and @xjm. We agreed this is a major issue because, even it may happen under not so common circumstances, the level of breakage it may imply is significant.

hchonov’s picture

@plach do you have any opinion about the proposed resolution?

hchonov’s picture

@fago I am not sure why we should use the storage and what do you mean by "let the storage update the changed field based upon EntityChangedInterface"? We are dealing here only with the base field definitions.

@Berdir now checking if it is subclass manually.

Status: Needs review » Needs work

The last submitted patch, 23: 2617628-23.patch, failed testing.

hchonov’s picture

Status: Needs work » Needs review
FileSize
5.56 KB
2.6 KB

So I had to update the rest test, as it was not considering that fields having default value could not be set to NULL.

tstoeckler’s picture

This patch is great. It's not the coolest thing in the world that we have to rely on the order of field definitions but it is certainly completely backwards compatible and I cannot see any other way to solve this issue generically without significantly more maintenance overhead. So I think we can get this in in its current form. Also, since it comes with tests, we an always improve the implementation if someone thinks of something better without regressing.

I do have some notes before RTBC, though.

  1. +++ b/core/lib/Drupal/Core/Entity/EntityFieldManager.php
    @@ -304,6 +304,20 @@ public function getFieldDefinitions($entity_type_id, $bundle) {
    +    // If the entity type is implementing the EntityChangedInterface and
    +    // has a changed field, then this field should be returned at the end
    +    // of the list, so that when iterating over the fields and calling
    +    // preSave on them the preSave of the changed item will be called as
    +    // the last one to ensure that if the other fields changed their values
    +    // in the preSave the changed item is going to detect this change.
    

    Here and below: Wraps too early.

    I also think the text is a bit hard to read, here is my suggestion (I initially had a bunch of individual notes, but I think that would have been harder to follow than this):

    If the entity type class implements EntityChangedInterface and has a changed field then this field should be at the end of the list so that when saving an entity the preSave() method will be called on the changed field last. This ensures that the changed field detects if other fields change their values in their own preSave() implementation.

    As always, feel free to improve on this further.

  2. +++ b/core/lib/Drupal/Core/Entity/EntityFieldManager.php
    @@ -304,6 +304,20 @@ public function getFieldDefinitions($entity_type_id, $bundle) {
    +    if (is_subclass_of($entity_class, EntityChangedInterface::class) && isset($this->fieldDefinitions[$entity_type_id][$bundle]['changed'])) {
    

    I was going to complain about hardcoding the name of the changed field here, but there's already a precedent for that in EntityChangedTrait so that's fine here. Re-purposed #2209971: Automatically provide a changed field definition to fix that.

  3. +++ b/core/modules/system/tests/modules/entity_test/src/Plugin/Field/FieldType/ChangedCustomTestItem.php
    @@ -0,0 +1,36 @@
    +/**
    + * @file
    + * Contains \Drupal\entity_test\Plugin\Field\FieldType\ChangedCustomTestItem.
    + */
    

    No longer necessary.

  4. +++ b/core/modules/system/tests/modules/entity_test/src/Plugin/Field/FieldType/ChangedCustomTestItem.php
    @@ -0,0 +1,36 @@
    +class ChangedCustomTestItem extends ChangedItem {
    ...
    +  public function preSave() {
    

    ChangedItem only defines preSave() so if we're overriding that without calling the parent, it seems pointless to extend it, so extending either CreatedItem or TimestampItem directly would be better, I think.

tstoeckler’s picture

Status: Needs review » Needs work

Warrants a "needs work" I guess.

hchonov’s picture

Status: Needs work » Needs review
FileSize
4.58 KB
3.13 KB

I've addressed all the remarks from #26 and also re-rolled the patch as the test class core/modules/rest/src/Tests/UpdateTest.php has been removed.

tstoeckler’s picture

Status: Needs review » Reviewed & tested by the community

OK, thanks! Going to mark this RTBC.

As noted above this solution is not very "elegant", but solving it "properly" would need some sort of field dependency/weight system, which would be nice for a couple reasons, but we simply don't have and I don't think is something we are realistically going to get in D8. And while not being elegant, this still is a "minimally invasive" fix, with not much maintenance overhead from my point of view. Especially for fixing a major bug, this is really a very good tradeoff. Of course committers may disagree, but for me this is good to go.

I had one other remark namely that the test seems to be a bit brittle in that - as far as I can tell - it relies on the fact that between the first and second entity save one second or more of time have passed. However, that's a pre-existing issue with ContentEntityChangedTest all over the place so it doesn't make sense to hold this up on that, because apparently that hasn't been causing issues or we would know about it.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 28: 2617628-28.patch, failed testing.

hchonov’s picture

Status: Needs work » Needs review
FileSize
7.15 KB
2.4 KB

In the previous re-roll I've removed the change to the rest update test as the test has been removed, but it looks like it has been re-worked instead and the logic of having a hard-coded ordered list of the entity fields that are being returned by the entity field manager is still there and that is why the patch is now failing - so I've adjusted the test to make it independent of the returned entity fields order.

tstoeckler’s picture

OK, awesome that it's green again. However, I do not feel comfortable RTBCing the REST test changes, I think someone from the REST team should have a look that. I.e. I'm not sure if we are intentionally sending multiple fields across the wire to make sure that the validation does not fail on missing fields or something like that.

I will try to summon @WimLeers.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Munavijayalakshmi’s picture

+++ b/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityChangedTest.php
@@ -52,6 +54,42 @@ protected function setUp() {
+    $field_storage = FieldStorageConfig::create(array(
+      'field_name' => 'field_changed_custom',
+      'entity_type' => 'entity_test_mul_changed',
...
+      'cardinality' => 1,
+      'translatable' => FALSE,
+    ));
...
+    FieldConfig::create(array(
+      'field_storage' => $field_storage,
+      'bundle' => 'entity_test_mul_changed',
+    ))->save();
...
+    $entity = EntityTestMulChanged::create(array(
+      'name' => $this->randomString(),
+    ));

use short array syntax (new coding standard).

Fixed the short array syntax error and attached new patch.

Status: Needs review » Needs work

The last submitted patch, 34: 2617628-34.patch, failed testing.

hchonov’s picture

Status: Needs work » Needs review
FileSize
7.07 KB
782 bytes

The reason why the test is now failing is because we started skipping the checking of changed field item lists for changes in #2615016: ContentEntityBase::hasTranslationChanges should exclude the "changed" fields from the comparison, so what here is failing is only the custom changed field added for the test. We simply have to use the base field item lists instead the one for changed field items and then the tests passes again.

The current change only adapts the patch because of #2615016: ContentEntityBase::hasTranslationChanges should exclude the "changed" fields from the comparison and is irrelevant.

The patch has been approved by @tstoeckler as RTBC, but we've wanted that @WimLeers also agrees with the changes.

Wim Leers’s picture

Status: Needs review » Reviewed & tested by the community

+1

Just one nit, to clarify an otherwise kinda confusing comment:

+++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php
@@ -901,17 +901,20 @@ public function testPatch() {
+      // testing with in order to make the test independent of the order of the

s/order/validation order/

hchonov’s picture

I've changed the sentence to make it less confusing :). I hope it will be accepted like this.

tstoeckler’s picture

Looks good to me.

Wim Leers’s picture

Issue tags: +DevDaysSeville

+1!

alexpott’s picture

Status: Reviewed & tested by the community » Needs review
+++ b/core/lib/Drupal/Core/Entity/EntityFieldManager.php
@@ -304,6 +304,19 @@ public function getFieldDefinitions($entity_type_id, $bundle) {
+    // If the entity type class implements EntityChangedInterface and has a
+    // changed field then this field should be at the end of the list so that
+    // when saving an entity the preSave() method will be called on the changed
+    // field last. This ensures that the changed field detects if other fields
+    // change their values in their own preSave() implementation.
+    $entity_class = $this->entityTypeManager->getDefinition($entity_type_id)->getClass();
+    if (is_subclass_of($entity_class, EntityChangedInterface::class) && isset($this->fieldDefinitions[$entity_type_id][$bundle]['changed'])) {
+      $changed = $this->fieldDefinitions[$entity_type_id][$bundle]['changed'];
+      unset($this->fieldDefinitions[$entity_type_id][$bundle]['changed']);
+      $this->fieldDefinitions[$entity_type_id][$bundle]['changed'] = $changed;
+    }

This is clever fix but one that in the long run might not pay off. What happens when something else needs to come last in preSave? We have this problem all over the place especially in the Entity API. I think we need to consider that preSave's that change values are fundamentally different to preSave's that react to values being saved. What is super awkward here is that the changed field wants to be in both camps - reacting to other values changes and changing its own value. This makes me question the architecture.

The onChange() solution does seem like the proper fix but as @hchonov points out...

but like discussed in the other issue onChange is not a solution as well, because it fires even if you set the same value again

But isn't that a bug too? Perhaps a way forward here is to go with this fix for now but open followup to fix onChange firing when there is no change (if possible) and then refactor the changed field to use it instead of this?

Setting back to needs review because I think we should file the followups before committing this and probably add a @todo.

hchonov’s picture

This is clever fix but one that in the long run might not pay off. What happens when something else needs to come last in preSave?

Yes, this might happen. But if we want to support such cases I think we could think of some approach where we define content and metadata fields e.g. title is a content field and changed is a metadata field. Currently we've already adjusted hasTranslationChanges to skip revision metadata fields and changed fields from the comparison. We might go further define the changed field in similar way in the annotation - entity_metadata_keys. Having such separation we could simply first execute the preSave on content fields and afterwards on metadata fields.

But isn't that a bug too? Perhaps a way forward here is to go with this fix for now but open followup to fix onChange firing when there is no change (if possible) and then refactor the changed field to use it instead of this?

Setting back to needs review because I think we should file the followups before committing this and probably add a @todo.

I agree that we need the follow up issues, but don't think we should mention them explicitly in the code.

I've created an issue for the problem with onChange - #2863818: Fix notification issues in the entity/field on-change system. We would need that fix, while not necessary, also to improve the dirty fields tracking being implemented in #2862574: Add ability to track an entity object's dirty fields (and see if it has changed).

hchonov’s picture

I've opened an issue to find a better way of updating the changed field and mentioned this in the code - #2863844: Ensure that the changed field will update its value even if it is not the last field in pre save.

tstoeckler’s picture

Status: Needs review » Reviewed & tested by the community

Looks good to me. I think it's a good idea to try to improve this in a follow-up, but would also nice to fix this for now at least.

alexpott’s picture

Status: Reviewed & tested by the community » Needs review
+++ b/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityChangedTest.php
@@ -52,6 +54,42 @@ protected function setUp() {
+    $this->assertTrue($entity->field_changed_custom->value > $changed_custom_time, 'The custom changed field item updated its value');
+    $this->assertTrue($entity->getChangedTime() > $changed_time, 'The changed field item updated its value');

These greater thans make me nervous of random fails. What happens if the whole test is quicker that 1 second?

hchonov’s picture

Well in order to ensure there will never be any race conditions regarding the changed timestamps then we have to wait one second before re-saving the entity. I've added this to the new patch.

hchonov’s picture

We've talked with @alexpott in IRC and a solution where we could avoid sleep in a test is preferable. We've also talked about working with timestamps in tests and we've realized that we should be using the time service instead of time(). So the new implementation of the changed test field allows that a value is provided when the entity is created and will not update it in this case and now we create the entity with changed timestamps defined and older than the current request timestamp.

hchonov’s picture

The last submitted patch, 48: 2617628-48-test-only.patch, failed testing.

tstoeckler’s picture

Status: Needs review » Reviewed & tested by the community

Read through the test again and it looks good to me... and still fails at the expected fails without the fix. Thanks!

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
  1. Sorry I had some more thoughts on this patch whilst reading it again
  2. +++ b/core/lib/Drupal/Core/Entity/EntityFieldManager.php
    @@ -304,6 +304,21 @@ public function getFieldDefinitions($entity_type_id, $bundle) {
    +    // If the entity type class implements EntityChangedInterface and has a
    +    // changed field then this field should be at the end of the list so that
    +    // when saving an entity the preSave() method will be called on the changed
    +    // field last. This ensures that the changed field detects if other fields
    +    // change their values in their own preSave() implementation. A better
    +    // implementation where the updating of the changed field should not depend
    +    // on the field order will be fixed by https://www.drupal.org/node/2863844.
    

    Here is a suggested refactor for shorter sentences:

        // If the entity type class implements EntityChangedInterface and has a
        // changed field, the change field's definition should be last. This is so
        // that the changed field's preSave() method will be called last. This
        // ensures that, if other field values change as a result of preSave()
        // implementations, the changed field value is correct.
        // @todo https://www.drupal.org/node/2863844 Implement a solution where the
        //   field definition list does not need to be manipulated.
    
  3. +++ b/core/lib/Drupal/Core/Entity/EntityFieldManager.php
    @@ -304,6 +304,21 @@ public function getFieldDefinitions($entity_type_id, $bundle) {
    +    if (is_subclass_of($entity_class, EntityChangedInterface::class) && isset($this->fieldDefinitions[$entity_type_id][$bundle]['changed'])) {
    

    Reading this again it makes me ponder something. Whether or not EntityChangeInterface means there has to be a field called 'changed'. Or could it have another name. As far as I can see you could implement the interface and not call the field that. This might be a can of worms.

  4. +++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php
    @@ -901,17 +901,20 @@ public function testPatch() {
         // error message for the first PATCH-protected field. Remove that field from
         // the normalization, send another request, assert the next PATCH-protected
         // field error message. And so on.
    -    $max_normalization = $this->getNormalizedPatchEntity() + $this->serializer->normalize($this->entity, static::$format);
    +    $max_normalization_complete = $this->getNormalizedPatchEntity() + $this->serializer->normalize($this->entity, static::$format);
         for ($i = 0; $i < count(static::$patchProtectedFieldNames); $i++) {
    -      $max_normalization = $this->removeFieldsFromNormalization($max_normalization, array_slice(static::$patchProtectedFieldNames, 0, $i));
    -      $request_options[RequestOptions::BODY] = $this->serializer->serialize($max_normalization, static::$format);
    +      // From all the protected fields leave only the field we are currently
    +      // testing with so that the test doesn't depend on the order in which the
    +      // entity fields are returned and validated.
    +      $max_normalization_reduced = $this->removeFieldsFromNormalization($max_normalization_complete, array_diff(static::$patchProtectedFieldNames, [static::$patchProtectedFieldNames[$i]]));
    +      $request_options[RequestOptions::BODY] = $this->serializer->serialize($max_normalization_reduced, static::$format);
           $response = $this->request('PATCH', $url, $request_options);
           $this->assertResourceErrorResponse(403, "Access denied on updating field '" . static::$patchProtectedFieldNames[$i] . "'.", $response);
         }
     
         // 200 for well-formed request that sends the maximum number of fields.
    -    $max_normalization = $this->removeFieldsFromNormalization($max_normalization, static::$patchProtectedFieldNames);
    -    $request_options[RequestOptions::BODY] = $this->serializer->serialize($max_normalization, static::$format);
    +    $max_normalization_reduced = $this->removeFieldsFromNormalization($max_normalization_complete, static::$patchProtectedFieldNames);
    +    $request_options[RequestOptions::BODY] = $this->serializer->serialize($max_normalization_reduced, static::$format);
         $response = $this->request('PATCH', $url, $request_options);
         $this->assertResourceResponse(200, FALSE, $response);
    

    I think all of this needs a bit of work and hopefully @Wim Leers can provide a bit of backgroup of whether the changes are acceptable because we're changing the test quite fundamentally here.

    I think it should be something like:

        // DX: 403 when sending PATCH request with read-only fields.
        // Assert the expected error messages for each read-only field in the
        // normalization.
        $max_normalization = $this->getNormalizedPatchEntity() + $this->serializer->normalize($this->entity, static::$format);
        foreach (static::$patchProtectedFieldNames as $field_name) {
          $normalization_to_test = $this->removeFieldsFromNormalization($max_normalization, array_diff(static::$patchProtectedFieldNames, [$field_name]));
          $request_options[RequestOptions::BODY] = $this->serializer->serialize($normalization_to_test, static::$format);
          $response = $this->request('PATCH', $url, $request_options);
          $this->assertResourceErrorResponse(403, "Access denied on updating field '" . $field_name . "'.", $response);
        }
    
        // 200 for well-formed request that sends the maximum number of fields.
        $max_normalization = $this->removeFieldsFromNormalization($max_normalization, static::$patchProtectedFieldNames);
        $request_options[RequestOptions::BODY] = $this->serializer->serialize($max_normalization, static::$format);
        $response = $this->request('PATCH', $url, $request_options);
        $this->assertResourceResponse(200, FALSE, $response);
    
  5. +++ b/core/modules/system/tests/modules/entity_test/src/Plugin/Field/FieldType/ChangedCustomTestItem.php
    @@ -0,0 +1,35 @@
    +    // If an initial value is provided when the entity is being created then
    +    // skip updating the field value.
    +    if ($this->getEntity()->isNew() && isset($this->value)) {
    +      return;
    +    }
    +    $this->value = \Drupal::time()->getCurrentTime() + 1;
    

    I think the fact that this is a time() makes the test harder to understand. The main point is that it always updates on save - it would be simpler to understand it is was just a counter that added one on every save. And then you don't even really need the isNew() complexity. You can just check what the count equals.

tstoeckler’s picture

+++ b/core/lib/Drupal/Core/Entity/EntityFieldManager.php
@@ -304,6 +304,21 @@ public function getFieldDefinitions($entity_type_id, $bundle) {
+    if (is_subclass_of($entity_class, EntityChangedInterface::class) && isset($this->fieldDefinitions[$entity_type_id][$bundle]['changed'])) {

Reading this again it makes me ponder something. Whether or not EntityChangeInterface means there has to be a field called 'changed'. Or could it have another name. As far as I can see you could implement the interface and not call the field that. This might be a can of worms.

The thing is that we actually do hardcode that the field is called changed in EntityChangedTrait. Of course people are not required to use that trait, but it is kind of an API that we provide and that does hardcode the field name. There is already the (not so aptly named) #2209971: Automatically provide a changed field definition for fixing that and making it into a proper entity key.

So my personal view is that we should leave it like it is in the patch currently and if people use a different field name they will not benefit from this optimization until #2209971: Automatically provide a changed field definition lands, but it's not that something explicitly breaks.

Thoughts?

Wim Leers’s picture

#52.4: I was explicitly asked by @tstoeckler and @hchonov to review the REST changes. I did that in #37.
This is not changing the tests fundamentally; this is only ensuring that the validation order no longer matters.
That being said, your proposed changes have exactly the same effect (they're functionally equivalent) and are much easier to read/understand. So, +1 for adopting your proposed changes instead.

hchonov’s picture

Re #51:
2.
Done :).
3.
We have the problem with the changed field all over the core and we always explicitly hard code it, as we currently have no other option. But this is going to change in #2209971: Automatically provide a changed field definition where the changed field will be made part of the entity annotation, but until then we have to hard code it like @tstoeckler have said in #52.
4.
Using the proposed code now.
5.
I've turned the field into a save counter field and it increments its value on each pre-save now.

The last submitted patch, 54: 2617628-54-test-only.patch, failed testing.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

hchonov’s picture

Re-roll only.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

tstoeckler’s picture

Needed a re-roll due to a change in EntityResourceTestBase since I had to merge the code manually anyway, I had a look whether I can simplify the diff and I think I found a way to not only simplify the diff, but actually simplify the test itself relative to the current code. The "interdiff" is relative to the code in current 8.7.x it's what git diff -w showed during the rebase of the re-roll.

Note that the method whose signature I am changing is internal and only used in that one place, so I think we are good in terms of BC.

matsbla’s picture

I've tested the patch in #60 to see if it solves #2912318: Changing field value in an entity presave hook will not update the entity changed timestamp where the problem is that the timestamp is not updated for image fields with translatable title / alt attributes when they are edited concurrently. However, after I apply the patch I'm still not able to get the lock to work and it seem like it is not detected that the image field have been changed.

Steps to reproduce:

  • Set up an image field. Make the title and alt attribute translatable, but make the image file non-tranlatable
  • Edit the a node concurrently in 2 different languages: In the first language you change the image file, then after you simply save the other language without doing anything
  • Result; There is no lock saying that the content has been changed. The new image added is gone as saving the second language overrides the new image added in the first language, and this happens without any notice.
tstoeckler’s picture

FileSize
10.92 KB

Needed a rebase, but Git was able to handle it.

Status: Needs review » Needs work

The last submitted patch, 62: 2617628-62_.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

tstoeckler’s picture

Status: Needs work » Needs review
FileSize
1.01 KB
11.55 KB

So this is one of those cases where Git will successfully rebase into a broken state. The variable I am removing here received another usage below, outside of the patch scope...

Let's see if this fixes it.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

gease’s picture

Just a re-roll.

Status: Needs review » Needs work

The last submitted patch, 67: 2617628-67.patch, failed testing. View results

gease’s picture

This should fix the tests, but raises questions.

hchonov’s picture

What questions? It is expected that the order changes, because this is what we do. And the field order does not affect the functionality in any way.

gease’s picture

Status: Needs work » Needs review

The main question was - why in the updated list of protected fields 'changed' field is not the last one? After some debugging I found out, that the fields that go after 'changed' in this updated list of protected fields are just missing from the request. Which, imho, is the issue for the test (as well as relying on fields order), but is outside of the scope of the current issue.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

mrweiner made their first commit to this issue’s fork.

mrweiner’s picture

Rerolled for 9.4.x. Should also apply to 9.3.x.

ankithashetty’s picture

Fixed all the errors from the patch in #77. Attached a diff file between #69 - #78 and between #77 - #78.

Thanks!

Status: Needs review » Needs work

The last submitted patch, 78: 2617628-78.patch, failed testing. View results

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.