3 namespace Drupal\KernelTests\Core\Entity;
5 use Drupal\Component\Render\FormattableMarkup;
6 use Drupal\entity_test\Entity\EntityTestMul;
7 use Drupal\entity_test\Entity\EntityTestMulRev;
8 use Drupal\language\Entity\ConfigurableLanguage;
11 * Tests proper cloning of content entities.
15 class ContentEntityCloneTest extends EntityKernelTestBase {
20 public static $modules = ['language', 'entity_test'];
25 protected function setUp() {
28 // Enable an additional language.
29 ConfigurableLanguage::createFromLangcode('de')->save();
31 $this->installEntitySchema('entity_test_mul');
32 $this->installEntitySchema('entity_test_mulrev');
36 * Tests if entity references on fields are still correct after cloning.
38 public function testFieldEntityReferenceAfterClone() {
39 $user = $this->createUser();
41 // Create a test entity.
42 $entity = EntityTestMul::create([
43 'name' => $this->randomString(),
44 'user_id' => $user->id(),
47 $translation = $entity->addTranslation('de');
49 // Initialize the fields on the translation objects in order to check that
50 // they are properly cloned and have a reference to the cloned entity
51 // object and not to the original one.
53 $translation->getFields();
55 $clone = clone $translation;
57 $this->assertEqual($entity->getTranslationLanguages(), $clone->getTranslationLanguages(), 'The entity and its clone have the same translation languages.');
59 $default_langcode = $entity->getUntranslated()->language()->getId();
60 foreach (array_keys($clone->getTranslationLanguages()) as $langcode) {
61 $translation = $clone->getTranslation($langcode);
62 foreach ($translation->getFields() as $field_name => $field) {
63 if ($field->getFieldDefinition()->isTranslatable()) {
64 $args = ['%field_name' => $field_name, '%langcode' => $langcode];
65 $this->assertEqual($langcode, $field->getEntity()->language()->getId(), format_string('Translatable field %field_name on translation %langcode has correct entity reference in translation %langcode after cloning.', $args));
66 $this->assertSame($translation, $field->getEntity(), new FormattableMarkup('Translatable field %field_name on translation %langcode has correct reference to the cloned entity object.', $args));
69 $args = ['%field_name' => $field_name, '%langcode' => $langcode, '%default_langcode' => $default_langcode];
70 $this->assertEqual($default_langcode, $field->getEntity()->language()->getId(), format_string('Non translatable field %field_name on translation %langcode has correct entity reference in the default translation %default_langcode after cloning.', $args));
71 $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));
78 * Tests that the flag for enforcing a new entity is not shared.
80 public function testEnforceIsNewOnClonedEntityTranslation() {
81 // Create a test entity.
82 $entity = EntityTestMul::create([
83 'name' => $this->randomString(),
87 $entity_translation = $entity->addTranslation('de');
90 // The entity is not new anymore.
91 $this->assertFalse($entity_translation->isNew());
93 // The clone should not be new either.
94 $clone = clone $entity_translation;
95 $this->assertFalse($clone->isNew());
97 // After forcing the clone to be new only it should be flagged as new, but
98 // the original entity should not.
99 $clone->enforceIsNew();
100 $this->assertTrue($clone->isNew());
101 $this->assertFalse($entity_translation->isNew());
105 * Tests if the entity fields are properly cloned.
107 public function testClonedEntityFields() {
108 $user = $this->createUser();
110 // Create a test entity.
111 $entity = EntityTestMul::create([
112 'name' => $this->randomString(),
113 'user_id' => $user->id(),
117 $entity->addTranslation('de');
119 $fields = array_keys($entity->getFieldDefinitions());
121 // Reload the entity, clone it and check that both entity objects reference
122 // different field instances.
123 $entity = $this->reloadEntity($entity);
124 $clone = clone $entity;
126 $different_references = TRUE;
127 foreach ($fields as $field_name) {
128 if ($entity->get($field_name) === $clone->get($field_name)) {
129 $different_references = FALSE;
132 $this->assertTrue($different_references, 'The entity object and the cloned entity object reference different field item list objects.');
134 // Reload the entity, initialize one translation, clone it and check that
135 // both entity objects reference different field instances.
136 $entity = $this->reloadEntity($entity);
137 $entity->getTranslation('de');
138 $clone = clone $entity;
140 $different_references = TRUE;
141 foreach ($fields as $field_name) {
142 if ($entity->get($field_name) === $clone->get($field_name)) {
143 $different_references = FALSE;
146 $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.');
150 * Tests that the flag for enforcing a new revision is not shared.
152 public function testNewRevisionOnCloneEntityTranslation() {
153 // Create a test entity.
154 $entity = EntityTestMulRev::create([
155 'name' => $this->randomString(),
159 $entity->addTranslation('de');
162 // Reload the entity as ContentEntityBase::postCreate() forces the entity to
163 // be a new revision.
164 $entity = EntityTestMulRev::load($entity->id());
165 $entity_translation = $entity->getTranslation('de');
167 // The entity is not set to be a new revision.
168 $this->assertFalse($entity_translation->isNewRevision());
170 // The clone should not be set to be a new revision either.
171 $clone = clone $entity_translation;
172 $this->assertFalse($clone->isNewRevision());
174 // After forcing the clone to be a new revision only it should be flagged
175 // as a new revision, but the original entity should not.
176 $clone->setNewRevision();
177 $this->assertTrue($clone->isNewRevision());
178 $this->assertFalse($entity_translation->isNewRevision());
182 * Tests modifications on entity keys of a cloned entity object.
184 public function testEntityKeysModifications() {
185 // Create a test entity with a translation, which will internally trigger
186 // entity cloning for the new translation and create references for some of
187 // the entity properties.
188 $entity = EntityTestMulRev::create([
189 'name' => 'original-name',
190 'uuid' => 'original-uuid',
193 $entity->addTranslation('de');
197 $clone = clone $entity;
199 // Alter a non-translatable and a translatable entity key fields of the
200 // cloned entity and assert that retrieving the value through the entity
201 // keys local cache will be different for the cloned and the original
203 // We first have to call the ::uuid() and ::label() method on the original
204 // entity as it is going to cache the field values into the $entityKeys and
205 // $translatableEntityKeys properties of the entity object and we want to
206 // check that the cloned and the original entity aren't sharing the same
207 // reference to those local cache properties.
208 $uuid_field_name = $entity->getEntityType()->getKey('uuid');
209 $this->assertFalse($entity->getFieldDefinition($uuid_field_name)->isTranslatable());
210 $clone->$uuid_field_name->value = 'clone-uuid';
211 $this->assertEquals('original-uuid', $entity->uuid());
212 $this->assertEquals('clone-uuid', $clone->uuid());
214 $label_field_name = $entity->getEntityType()->getKey('label');
215 $this->assertTrue($entity->getFieldDefinition($label_field_name)->isTranslatable());
216 $clone->$label_field_name->value = 'clone-name';
217 $this->assertEquals('original-name', $entity->label());
218 $this->assertEquals('clone-name', $clone->label());
222 * Tests the field values after serializing an entity and its clone.
224 public function testFieldValuesAfterSerialize() {
225 // Create a test entity with a translation, which will internally trigger
226 // entity cloning for the new translation and create references for some of
227 // the entity properties.
228 $entity = EntityTestMulRev::create([
229 'name' => 'original',
232 $entity->addTranslation('de');
236 $clone = clone $entity;
238 // Alter the name field value of the cloned entity object.
239 $clone->setName('clone');
241 // Serialize the entity and the cloned object in order to destroy the field
242 // objects and put the field values into the entity property $values, so
243 // that on accessing a field again it will be newly created with the value
244 // from the $values property.
248 // Assert that the original and the cloned entity both have different names.
249 $this->assertEquals('original', $entity->getName());
250 $this->assertEquals('clone', $clone->getName());
254 * Tests changing the default revision flag.
256 public function testDefaultRevision() {
257 // Create a test entity with a translation, which will internally trigger
258 // entity cloning for the new translation and create references for some of
259 // the entity properties.
260 $entity = EntityTestMulRev::create([
261 'name' => 'original',
264 $entity->addTranslation('de');
267 // Assert that the entity is in the default revision.
268 $this->assertTrue($entity->isDefaultRevision());
270 // Clone the entity and modify its default revision flag.
271 $clone = clone $entity;
272 $clone->isDefaultRevision(FALSE);
274 // Assert that the clone is not in default revision, but the original entity
275 // is still in the default revision.
276 $this->assertFalse($clone->isDefaultRevision());
277 $this->assertTrue($entity->isDefaultRevision());
281 * Tests references of entity properties after entity cloning.
283 public function testEntityPropertiesModifications() {
284 // Create a test entity with a translation, which will internally trigger
285 // entity cloning for the new translation and create references for some of
286 // the entity properties.
287 $entity = EntityTestMulRev::create([
288 'name' => 'original',
291 $translation = $entity->addTranslation('de');
295 $clone = clone $entity;
297 // Retrieve the entity properties.
298 $reflection = new \ReflectionClass($entity);
299 $properties = $reflection->getProperties(~\ReflectionProperty::IS_STATIC);
300 $translation_unique_properties = ['activeLangcode', 'translationInitialize', 'fieldDefinitions', 'languages', 'langcodeKey', 'defaultLangcode', 'defaultLangcodeKey', 'validated', 'validationRequired', 'entityTypeId', 'typedData', 'cacheContexts', 'cacheTags', 'cacheMaxAge', '_serviceIds'];
302 foreach ($properties as $property) {
303 // Modify each entity property on the clone and assert that the change is
304 // not propagated to the original entity.
305 $property->setAccessible(TRUE);
306 $property->setValue($entity, 'default-value');
307 $property->setValue($translation, 'default-value');
308 $property->setValue($clone, 'test-entity-cloning');
309 $this->assertEquals('default-value', $property->getValue($entity), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
310 $this->assertEquals('default-value', $property->getValue($translation), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
311 $this->assertEquals('test-entity-cloning', $property->getValue($clone), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
313 // Modify each entity property on the translation entity object and assert
314 // that the change is propagated to the default translation entity object
315 // except for the properties that are unique for each entity translation
317 $property->setValue($translation, 'test-translation-cloning');
318 // Using assertEquals or assertNotEquals here is dangerous as if the
319 // assertion fails and the property for some reasons contains the entity
320 // object e.g. the "typedData" property then the property will be
321 // serialized, but this will cause exceptions because the entity is
322 // modified in a non-consistent way and ContentEntityBase::__sleep() will
323 // not be able to properly access all properties and this will cause
324 // exceptions without a proper backtrace.
325 if (in_array($property->getName(), $translation_unique_properties)) {
326 $this->assertEquals('default-value', $property->getValue($entity), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
327 $this->assertEquals('test-translation-cloning', $property->getValue($translation), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
330 $this->assertEquals('test-translation-cloning', $property->getValue($entity), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));
331 $this->assertEquals('test-translation-cloning', $property->getValue($translation), (string) new FormattableMarkup('Entity property %property_name is not cloned properly.', ['%property_name' => $property->getName()]));