Problem/Motivation

Just another issue of the series of issues for entity cloning. Discovered by @dawehner and @tstoeckler.

The default revision flag does not propagate to all entity translation objects, which means that e.g. if you set the default revision to FALSE and call save on another translation no forward revision will be created.

Proposed resolution

In CEB::__clone break the reference when not initializing translation and in CEB::initializeTranslation ensure that all translation objects share one reference to the property.

Remaining tasks

Review & Commit.

User interface changes

API changes

Data model changes

Comments

hchonov created an issue. See original summary.

hchonov’s picture

StatusFileSize
new2.92 KB
hchonov’s picture

StatusFileSize
new3.88 KB
new858 bytes

And the fix for the issue.

The last submitted patch, 2: 2863336-2-test-only.patch, failed testing.

tstoeckler’s picture

Status: Needs review » Reviewed & tested by the community

Awesome, thank you!

imiksu’s picture

Is there any way to confirm the problem and fix in UI?

hchonov’s picture

The problem could not be reproduced through the default UI, only if you have custom or contrib module which is doing what is being done in the test. As the test is a valid use case I think that it is enough example for what is wrong. This is one issue of series of issues about problems with entity cloning.
For common mistakes regarding this you could take a look at the following issues:

fmrodriguez’s picture

Assigned: Unassigned » fmrodriguez
fmrodriguez’s picture

Assigned: fmrodriguez » Unassigned

Assigning by mistake, sorry.

alexpott’s picture

Just thinking out aloud - but is there anyway we can add a definite test for cloning an entity and ensuring that the clone doesn't have any references back to the original object?

hchonov’s picture

StatusFileSize
new5.94 KB
new6.89 KB
new3.33 KB

@alexpott, yes this is possible at least at the moment as we don't have properties which are referencing objects directly - we have only string and array properties currently. If we introduce later a property referencing an object directly we'll have to decide how we'll deal with this case. But this is really out of scope for now and the test is best effort. The good thing about the test is that it will fail each time a new property is defined and we haven't defined its behavior - shared or non shared reference between the translation objects :).

I've discussed this with @tstoeckler as well and here is the patch asserting the properly cloning of entity properties and it tests the use case of entity cloning when initiazing translations and also when simply cloning the entity.

In the test I've defined the properties that are currently not shared between the translation objects, but some of them should be shared in order for consistency or also prevent further problems with entity cloning. Here I only test them accordingly, but in a follow up(s) I am going to make those properties shared across entity translation objects.

The last submitted patch, 11: 2863336-11-test-only.patch, failed testing.

alexpott’s picture

Status: Reviewed & tested by the community » Needs review

Given the size of the test changes to #11 I think it is worth getting another community review before committing.

  1. It is really really great to see this generic test. hchonov++
  2. +++ b/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityCloneTest.php
    @@ -251,4 +251,82 @@ public function testFieldValuesAfterSerialize() {
    +    // Retrieve the entity properties.
    

    Retrieve the cloned entity's properties.

  3. +++ b/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityCloneTest.php
    @@ -251,4 +251,82 @@ public function testFieldValuesAfterSerialize() {
    +    $properties_filter = \ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED | \ReflectionProperty::IS_PRIVATE;
    +    $properties_clone = $reflection->getProperties($properties_filter);
    

    Just a point of style. I wouldn't bother creating the $properties_filter it doesn't make the code any more readable and is not re-used. Also $properties_clone make it sound like a clone of the properties which this is not. It's the properties of the $clone entity. So I think $clone_properties makes more sense. I realise that these thought might be a language thing as the implications of different word orders is different in different languages. So it might be simpler just to call it $properties

  4. +++ b/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityCloneTest.php
    @@ -251,4 +251,82 @@ public function testFieldValuesAfterSerialize() {
    +      $translation_unique_properties = ['activeLangcode', 'translationInitialize', 'fieldDefinitions', 'languages', 'langcodeKey', 'defaultLangcode', 'defaultLangcodeKey', 'validated', 'validationRequired', 'entityTypeId', 'typedData', 'cacheContexts', 'cacheTags', 'cacheMaxAge', '_serviceIds'];
    

    This can be set outside the loop no?

hchonov’s picture

StatusFileSize
new6.83 KB
new1.99 KB

Re #13:
2.
My bad.. the reflection properties of the entity and its clone are the same and it does not matter if we retrieve them from the entity or from its clone. Renaming to $properties.
3.
Yes, $translation_unique_properties could be defined outside of the loop. I've defined it there so that the code that tests the translation clone is at one place, but I've moved the array definition outside of the loop now.

tstoeckler’s picture

I think the test looks great. Just some minor notes:

  1. +++ b/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityCloneTest.php
    @@ -251,4 +251,81 @@ public function testFieldValuesAfterSerialize() {
    +    $properties = $reflection->getProperties(\ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED | \ReflectionProperty::IS_PRIVATE);
    

    So per http://php.net/manual/de/class.reflectionproperty.php#reflectionproperty... this is equivalent to ~\ReflectionProperty::IS_STATIC, no? That would be both shorter and clearer IMO.

  2. +++ b/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityCloneTest.php
    @@ -251,4 +251,81 @@ public function testFieldValuesAfterSerialize() {
    +        $this->assertFalse($property->getValue($entity) === $property->getValue($translation), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
    ...
    +        $this->assertTrue($property->getValue($entity) === $property->getValue($translation), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
    

    Can we not instead test for the actual values? I.e.:

    $this->assertEquals('test-translation-cloning', $property->getValue($translation);
    

    etc. We could do an assertion on both the $entity and the $translation in both cases. What do you think?

hchonov’s picture

@tstoeckler, no, the filter like this will return all the properties without the static ones :). This means private, protected and public properties.

Yes, we could make the test more explicit and test for actual values instead. Will cover that later.

hchonov’s picture

@tstoeckler, my mistake.. I've overseen that you've meant negation of the static properties. This should work as well.

hchonov’s picture

StatusFileSize
new8.15 KB
new4.72 KB

Re #15:
1. Done.
2. Now testing at all places explicitly against an expected value - there was one more place - @see interdiff.

I've also noticed that we haven't extended the comment in CEB::__clone, that we break the reference for isDefaultRevision like we mention all the other properties. I've covered this in the new patch as well.

tstoeckler’s picture

Status: Needs review » Reviewed & tested by the community

Thank you. You know how much I like to nitpick, but as hard as I tried, I couldn't find anything ;-)

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed and pushed ce2aae0 to 8.4.x and a2d2c19 to 8.3.x. Thanks!

Backported to 8.3.x as this change does not have BC implications and is a straight up bug fix.

  • alexpott committed ce2aae0 on 8.4.x
    Issue #2863336 by hchonov, tstoeckler, alexpott: Default revision flag...

  • alexpott committed a2d2c19 on 8.3.x
    Issue #2863336 by hchonov, tstoeckler, alexpott: Default revision flag...

Status: Fixed » Closed (fixed)

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