ContentEntityCloneTest.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. <?php
  2. namespace Drupal\KernelTests\Core\Entity;
  3. use Drupal\Component\Render\FormattableMarkup;
  4. use Drupal\entity_test\Entity\EntityTestMul;
  5. use Drupal\entity_test\Entity\EntityTestMulRev;
  6. use Drupal\language\Entity\ConfigurableLanguage;
  7. /**
  8. * Tests proper cloning of content entities.
  9. *
  10. * @group Entity
  11. */
  12. class ContentEntityCloneTest extends EntityKernelTestBase {
  13. /**
  14. * {@inheritdoc}
  15. */
  16. public static $modules = ['language', 'entity_test'];
  17. /**
  18. * {@inheritdoc}
  19. */
  20. protected function setUp() {
  21. parent::setUp();
  22. // Enable an additional language.
  23. ConfigurableLanguage::createFromLangcode('de')->save();
  24. $this->installEntitySchema('entity_test_mul');
  25. $this->installEntitySchema('entity_test_mulrev');
  26. }
  27. /**
  28. * Tests if entity references on fields are still correct after cloning.
  29. */
  30. public function testFieldEntityReferenceAfterClone() {
  31. $user = $this->createUser();
  32. // Create a test entity.
  33. $entity = EntityTestMul::create([
  34. 'name' => $this->randomString(),
  35. 'user_id' => $user->id(),
  36. 'language' => 'en',
  37. ]);
  38. $translation = $entity->addTranslation('de');
  39. // Initialize the fields on the translation objects in order to check that
  40. // they are properly cloned and have a reference to the cloned entity
  41. // object and not to the original one.
  42. $entity->getFields();
  43. $translation->getFields();
  44. $clone = clone $translation;
  45. $this->assertEqual($entity->getTranslationLanguages(), $clone->getTranslationLanguages(), 'The entity and its clone have the same translation languages.');
  46. $default_langcode = $entity->getUntranslated()->language()->getId();
  47. foreach (array_keys($clone->getTranslationLanguages()) as $langcode) {
  48. $translation = $clone->getTranslation($langcode);
  49. foreach ($translation->getFields() as $field_name => $field) {
  50. if ($field->getFieldDefinition()->isTranslatable()) {
  51. $args = ['%field_name' => $field_name, '%langcode' => $langcode];
  52. $this->assertEqual($langcode, $field->getEntity()->language()->getId(), new FormattableMarkup('Translatable field %field_name on translation %langcode has correct entity reference in translation %langcode after cloning.', $args));
  53. $this->assertSame($translation, $field->getEntity(), new FormattableMarkup('Translatable field %field_name on translation %langcode has correct reference to the cloned entity object.', $args));
  54. }
  55. else {
  56. $args = ['%field_name' => $field_name, '%langcode' => $langcode, '%default_langcode' => $default_langcode];
  57. $this->assertEqual($default_langcode, $field->getEntity()->language()->getId(), new FormattableMarkup('Non translatable field %field_name on translation %langcode has correct entity reference in the default translation %default_langcode after cloning.', $args));
  58. $this->assertSame($translation->getUntranslated(), $field->getEntity(), new FormattableMarkup('Non translatable field %field_name on translation %langcode has correct reference to the cloned entity object in the default translation %default_langcode.', $args));
  59. }
  60. }
  61. }
  62. }
  63. /**
  64. * Tests that the flag for enforcing a new entity is not shared.
  65. */
  66. public function testEnforceIsNewOnClonedEntityTranslation() {
  67. // Create a test entity.
  68. $entity = EntityTestMul::create([
  69. 'name' => $this->randomString(),
  70. 'language' => 'en',
  71. ]);
  72. $entity->save();
  73. $entity_translation = $entity->addTranslation('de');
  74. $entity->save();
  75. // The entity is not new anymore.
  76. $this->assertFalse($entity_translation->isNew());
  77. // The clone should not be new either.
  78. $clone = clone $entity_translation;
  79. $this->assertFalse($clone->isNew());
  80. // After forcing the clone to be new only it should be flagged as new, but
  81. // the original entity should not.
  82. $clone->enforceIsNew();
  83. $this->assertTrue($clone->isNew());
  84. $this->assertFalse($entity_translation->isNew());
  85. }
  86. /**
  87. * Tests if the entity fields are properly cloned.
  88. */
  89. public function testClonedEntityFields() {
  90. $user = $this->createUser();
  91. // Create a test entity.
  92. $entity = EntityTestMul::create([
  93. 'name' => $this->randomString(),
  94. 'user_id' => $user->id(),
  95. 'language' => 'en',
  96. ]);
  97. $entity->addTranslation('de');
  98. $entity->save();
  99. $fields = array_keys($entity->getFieldDefinitions());
  100. // Reload the entity, clone it and check that both entity objects reference
  101. // different field instances.
  102. $entity = $this->reloadEntity($entity);
  103. $clone = clone $entity;
  104. $different_references = TRUE;
  105. foreach ($fields as $field_name) {
  106. if ($entity->get($field_name) === $clone->get($field_name)) {
  107. $different_references = FALSE;
  108. }
  109. }
  110. $this->assertTrue($different_references, 'The entity object and the cloned entity object reference different field item list objects.');
  111. // Reload the entity, initialize one translation, clone it and check that
  112. // both entity objects reference different field instances.
  113. $entity = $this->reloadEntity($entity);
  114. $entity->getTranslation('de');
  115. $clone = clone $entity;
  116. $different_references = TRUE;
  117. foreach ($fields as $field_name) {
  118. if ($entity->get($field_name) === $clone->get($field_name)) {
  119. $different_references = FALSE;
  120. }
  121. }
  122. $this->assertTrue($different_references, 'The entity object and the cloned entity object reference different field item list objects if the entity is cloned after an entity translation has been initialized.');
  123. }
  124. /**
  125. * Tests that the flag for enforcing a new revision is not shared.
  126. */
  127. public function testNewRevisionOnCloneEntityTranslation() {
  128. // Create a test entity.
  129. $entity = EntityTestMulRev::create([
  130. 'name' => $this->randomString(),
  131. 'language' => 'en',
  132. ]);
  133. $entity->save();
  134. $entity->addTranslation('de');
  135. $entity->save();
  136. // Reload the entity as ContentEntityBase::postCreate() forces the entity to
  137. // be a new revision.
  138. $entity = EntityTestMulRev::load($entity->id());
  139. $entity_translation = $entity->getTranslation('de');
  140. // The entity is not set to be a new revision.
  141. $this->assertFalse($entity_translation->isNewRevision());
  142. // The clone should not be set to be a new revision either.
  143. $clone = clone $entity_translation;
  144. $this->assertFalse($clone->isNewRevision());
  145. // After forcing the clone to be a new revision only it should be flagged
  146. // as a new revision, but the original entity should not.
  147. $clone->setNewRevision();
  148. $this->assertTrue($clone->isNewRevision());
  149. $this->assertFalse($entity_translation->isNewRevision());
  150. }
  151. /**
  152. * Tests modifications on entity keys of a cloned entity object.
  153. */
  154. public function testEntityKeysModifications() {
  155. // Create a test entity with a translation, which will internally trigger
  156. // entity cloning for the new translation and create references for some of
  157. // the entity properties.
  158. $entity = EntityTestMulRev::create([
  159. 'name' => 'original-name',
  160. 'uuid' => 'original-uuid',
  161. 'language' => 'en',
  162. ]);
  163. $entity->addTranslation('de');
  164. $entity->save();
  165. // Clone the entity.
  166. $clone = clone $entity;
  167. // Alter a non-translatable and a translatable entity key fields of the
  168. // cloned entity and assert that retrieving the value through the entity
  169. // keys local cache will be different for the cloned and the original
  170. // entity.
  171. // We first have to call the ::uuid() and ::label() method on the original
  172. // entity as it is going to cache the field values into the $entityKeys and
  173. // $translatableEntityKeys properties of the entity object and we want to
  174. // check that the cloned and the original entity aren't sharing the same
  175. // reference to those local cache properties.
  176. $uuid_field_name = $entity->getEntityType()->getKey('uuid');
  177. $this->assertFalse($entity->getFieldDefinition($uuid_field_name)->isTranslatable());
  178. $clone->$uuid_field_name->value = 'clone-uuid';
  179. $this->assertEquals('original-uuid', $entity->uuid());
  180. $this->assertEquals('clone-uuid', $clone->uuid());
  181. $label_field_name = $entity->getEntityType()->getKey('label');
  182. $this->assertTrue($entity->getFieldDefinition($label_field_name)->isTranslatable());
  183. $clone->$label_field_name->value = 'clone-name';
  184. $this->assertEquals('original-name', $entity->label());
  185. $this->assertEquals('clone-name', $clone->label());
  186. }
  187. /**
  188. * Tests the field values after serializing an entity and its clone.
  189. */
  190. public function testFieldValuesAfterSerialize() {
  191. // Create a test entity with a translation, which will internally trigger
  192. // entity cloning for the new translation and create references for some of
  193. // the entity properties.
  194. $entity = EntityTestMulRev::create([
  195. 'name' => 'original',
  196. 'language' => 'en',
  197. ]);
  198. $entity->addTranslation('de');
  199. $entity->save();
  200. // Clone the entity.
  201. $clone = clone $entity;
  202. // Alter the name field value of the cloned entity object.
  203. $clone->setName('clone');
  204. // Serialize the entity and the cloned object in order to destroy the field
  205. // objects and put the field values into the entity property $values, so
  206. // that on accessing a field again it will be newly created with the value
  207. // from the $values property.
  208. serialize($entity);
  209. serialize($clone);
  210. // Assert that the original and the cloned entity both have different names.
  211. $this->assertEquals('original', $entity->getName());
  212. $this->assertEquals('clone', $clone->getName());
  213. }
  214. /**
  215. * Tests changing the default revision flag.
  216. */
  217. public function testDefaultRevision() {
  218. // Create a test entity with a translation, which will internally trigger
  219. // entity cloning for the new translation and create references for some of
  220. // the entity properties.
  221. $entity = EntityTestMulRev::create([
  222. 'name' => 'original',
  223. 'language' => 'en',
  224. ]);
  225. $entity->addTranslation('de');
  226. $entity->save();
  227. // Assert that the entity is in the default revision.
  228. $this->assertTrue($entity->isDefaultRevision());
  229. // Clone the entity and modify its default revision flag.
  230. $clone = clone $entity;
  231. $clone->isDefaultRevision(FALSE);
  232. // Assert that the clone is not in default revision, but the original entity
  233. // is still in the default revision.
  234. $this->assertFalse($clone->isDefaultRevision());
  235. $this->assertTrue($entity->isDefaultRevision());
  236. }
  237. /**
  238. * Tests references of entity properties after entity cloning.
  239. */
  240. public function testEntityPropertiesModifications() {
  241. // Create a test entity with a translation, which will internally trigger
  242. // entity cloning for the new translation and create references for some of
  243. // the entity properties.
  244. $entity = EntityTestMulRev::create([
  245. 'name' => 'original',
  246. 'language' => 'en',
  247. ]);
  248. $translation = $entity->addTranslation('de');
  249. $entity->save();
  250. // Clone the entity.
  251. $clone = clone $entity;
  252. // Retrieve the entity properties.
  253. $reflection = new \ReflectionClass($entity);
  254. $properties = $reflection->getProperties(~\ReflectionProperty::IS_STATIC);
  255. $translation_unique_properties = ['activeLangcode', 'translationInitialize', 'fieldDefinitions', 'languages', 'langcodeKey', 'defaultLangcode', 'defaultLangcodeKey', 'revisionTranslationAffectedKey', 'validated', 'validationRequired', 'entityTypeId', 'typedData', 'cacheContexts', 'cacheTags', 'cacheMaxAge', '_serviceIds', '_entityStorages'];
  256. foreach ($properties as $property) {
  257. // Modify each entity property on the clone and assert that the change is
  258. // not propagated to the original entity.
  259. $property->setAccessible(TRUE);
  260. $property->setValue($entity, 'default-value');
  261. $property->setValue($translation, 'default-value');
  262. $property->setValue($clone, 'test-entity-cloning');
  263. // Static properties remain the same across all instances of the class.
  264. if ($property->isStatic()) {
  265. $this->assertEquals('test-entity-cloning', $property->getValue($entity), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
  266. $this->assertEquals('test-entity-cloning', $property->getValue($translation), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
  267. $this->assertEquals('test-entity-cloning', $property->getValue($clone), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
  268. }
  269. else {
  270. $this->assertEquals('default-value', $property->getValue($entity), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
  271. $this->assertEquals('default-value', $property->getValue($translation), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
  272. $this->assertEquals('test-entity-cloning', $property->getValue($clone), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
  273. }
  274. // Modify each entity property on the translation entity object and assert
  275. // that the change is propagated to the default translation entity object
  276. // except for the properties that are unique for each entity translation
  277. // object.
  278. $property->setValue($translation, 'test-translation-cloning');
  279. // Using assertEquals or assertNotEquals here is dangerous as if the
  280. // assertion fails and the property for some reasons contains the entity
  281. // object e.g. the "typedData" property then the property will be
  282. // serialized, but this will cause exceptions because the entity is
  283. // modified in a non-consistent way and ContentEntityBase::__sleep() will
  284. // not be able to properly access all properties and this will cause
  285. // exceptions without a proper backtrace.
  286. if (in_array($property->getName(), $translation_unique_properties)) {
  287. $this->assertEquals('default-value', $property->getValue($entity), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
  288. $this->assertEquals('test-translation-cloning', $property->getValue($translation), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
  289. }
  290. else {
  291. $this->assertEquals('test-translation-cloning', $property->getValue($entity), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
  292. $this->assertEquals('test-translation-cloning', $property->getValue($translation), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
  293. }
  294. }
  295. }
  296. }