3 namespace Drupal\Tests\field\Kernel\EntityReference;
5 use Drupal\comment\Entity\Comment;
6 use Drupal\comment\Entity\CommentType;
7 use Drupal\Component\Render\FormattableMarkup;
8 use Drupal\Core\Field\FieldItemListInterface;
9 use Drupal\Core\Field\FieldItemInterface;
10 use Drupal\Core\Field\FieldStorageDefinitionInterface;
11 use Drupal\Core\StringTranslation\TranslatableMarkup;
12 use Drupal\Core\Language\LanguageInterface;
13 use Drupal\entity_test\Entity\EntityTest;
14 use Drupal\entity_test\Entity\EntityTestStringId;
15 use Drupal\field\Entity\FieldConfig;
16 use Drupal\field\Entity\FieldStorageConfig;
17 use Drupal\node\Entity\NodeType;
18 use Drupal\node\NodeInterface;
19 use Drupal\taxonomy\TermInterface;
20 use Drupal\Tests\field\Kernel\FieldKernelTestBase;
21 use Drupal\file\Entity\File;
22 use Drupal\node\Entity\Node;
23 use Drupal\taxonomy\Entity\Term;
24 use Drupal\taxonomy\Entity\Vocabulary;
25 use Drupal\user\Entity\User;
26 use Drupal\Tests\field\Traits\EntityReferenceTestTrait;
29 * Tests the new entity API for the entity reference field type.
31 * @group entity_reference
33 class EntityReferenceItemTest extends FieldKernelTestBase {
35 use EntityReferenceTestTrait;
42 public static $modules = ['node', 'comment', 'file', 'taxonomy', 'text', 'filter', 'views', 'field'];
45 * The taxonomy vocabulary to test with.
47 * @var \Drupal\taxonomy\VocabularyInterface
49 protected $vocabulary;
52 * The taxonomy term to test with.
54 * @var \Drupal\taxonomy\TermInterface
59 * The test entity with a string ID.
61 * @var \Drupal\entity_test\Entity\EntityTestStringId
63 protected $entityStringId;
68 protected function setUp() {
71 $this->installEntitySchema('entity_test_string_id');
72 $this->installEntitySchema('taxonomy_term');
73 $this->installEntitySchema('node');
74 $this->installEntitySchema('comment');
75 $this->installEntitySchema('file');
77 $this->installSchema('comment', ['comment_entity_statistics']);
78 $this->installSchema('node', ['node_access']);
80 $this->vocabulary = Vocabulary::create([
81 'name' => $this->randomMachineName(),
82 'vid' => mb_strtolower($this->randomMachineName()),
83 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
85 $this->vocabulary->save();
87 $this->term = Term::create([
88 'name' => $this->randomMachineName(),
89 'vid' => $this->vocabulary->id(),
90 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
95 'type' => $this->randomMachineName(),
98 'id' => $this->randomMachineName(),
99 'target_entity_type_id' => 'node',
102 $this->entityStringId = EntityTestStringId::create([
103 'id' => $this->randomMachineName(),
105 $this->entityStringId->save();
107 // Use the util to create an instance.
108 $this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_taxonomy_term', 'Test content entity reference', 'taxonomy_term');
109 $this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_entity_test_string_id', 'Test content entity reference with string ID', 'entity_test_string_id');
110 $this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_taxonomy_vocabulary', 'Test config entity reference', 'taxonomy_vocabulary');
111 $this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_node', 'Test node entity reference', 'node', 'default', [], FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
112 $this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_user', 'Test user entity reference', 'user');
113 $this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_comment', 'Test comment entity reference', 'comment');
114 $this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_file', 'Test file entity reference', 'file');
115 $this->createEntityReferenceField('entity_test_string_id', 'entity_test_string_id', 'field_test_entity_test', 'Test content entity reference with string ID', 'entity_test');
119 * Tests the entity reference field type for referencing content entities.
121 public function testContentEntityReferenceItem() {
122 $tid = $this->term->id();
124 // Just being able to create the entity like this verifies a lot of code.
125 $entity = EntityTest::create();
126 $entity->field_test_taxonomy_term->target_id = $tid;
127 $entity->name->value = $this->randomMachineName();
130 $entity = EntityTest::load($entity->id());
131 $this->assertTrue($entity->field_test_taxonomy_term instanceof FieldItemListInterface, 'Field implements interface.');
132 $this->assertTrue($entity->field_test_taxonomy_term[0] instanceof FieldItemInterface, 'Field item implements interface.');
133 $this->assertEqual($entity->field_test_taxonomy_term->target_id, $tid);
134 $this->assertEqual($entity->field_test_taxonomy_term->entity->getName(), $this->term->getName());
135 $this->assertEqual($entity->field_test_taxonomy_term->entity->id(), $tid);
136 $this->assertEqual($entity->field_test_taxonomy_term->entity->uuid(), $this->term->uuid());
137 // Verify that the label for the target ID property definition is correct.
138 $label = $entity->field_test_taxonomy_term->getFieldDefinition()->getFieldStorageDefinition()->getPropertyDefinition('target_id')->getLabel();
139 $this->assertTrue($label instanceof TranslatableMarkup);
140 $this->assertEqual($label->render(), 'Taxonomy term ID');
142 // Change the name of the term via the reference.
143 $new_name = $this->randomMachineName();
144 $entity->field_test_taxonomy_term->entity->setName($new_name);
145 $entity->field_test_taxonomy_term->entity->save();
146 // Verify it is the correct name.
147 $term = Term::load($tid);
148 $this->assertEqual($term->getName(), $new_name);
150 // Make sure the computed term reflects updates to the term id.
151 $term2 = Term::create([
152 'name' => $this->randomMachineName(),
153 'vid' => $this->term->bundle(),
154 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
158 // Test all the possible ways of assigning a value.
159 $entity->field_test_taxonomy_term->target_id = $term->id();
160 $this->assertEqual($entity->field_test_taxonomy_term->entity->id(), $term->id());
161 $this->assertEqual($entity->field_test_taxonomy_term->entity->getName(), $term->getName());
163 $entity->field_test_taxonomy_term = [['target_id' => $term2->id()]];
164 $this->assertEqual($entity->field_test_taxonomy_term->entity->id(), $term2->id());
165 $this->assertEqual($entity->field_test_taxonomy_term->entity->getName(), $term2->getName());
167 // Test value assignment via the computed 'entity' property.
168 $entity->field_test_taxonomy_term->entity = $term;
169 $this->assertEqual($entity->field_test_taxonomy_term->target_id, $term->id());
170 $this->assertEqual($entity->field_test_taxonomy_term->entity->getName(), $term->getName());
172 $entity->field_test_taxonomy_term = [['entity' => $term2]];
173 $this->assertEqual($entity->field_test_taxonomy_term->target_id, $term2->id());
174 $this->assertEqual($entity->field_test_taxonomy_term->entity->getName(), $term2->getName());
176 // Test assigning an invalid item throws an exception.
178 $entity->field_test_taxonomy_term = ['target_id' => 'invalid', 'entity' => $term2];
179 $this->fail('Assigning an invalid item throws an exception.');
181 catch (\InvalidArgumentException $e) {
182 $this->pass('Assigning an invalid item throws an exception.');
185 // Delete terms so we have nothing to reference and try again
188 $entity = EntityTest::create(['name' => $this->randomMachineName()]);
191 // Test the generateSampleValue() method.
192 $entity = EntityTest::create();
193 $entity->field_test_taxonomy_term->generateSampleItems();
194 $entity->field_test_taxonomy_vocabulary->generateSampleItems();
195 $this->entityValidateAndSave($entity);
197 // Tests that setting an integer target ID together with an entity object
198 // succeeds and does not cause any exceptions. There is no assertion here,
199 // as the assignment should not throw any exceptions and if it does the
201 // @see \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem::setValue().
202 $user = User::create(['name' => $this->randomString()]);
204 $entity = EntityTest::create(['user_id' => ['target_id' => (int) $user->id(), 'entity' => $user]]);
208 * Tests the ::generateSampleValue() method.
210 public function testGenerateSampleValue() {
211 $entity = EntityTest::create();
213 // Test while a term exists.
214 $entity->field_test_taxonomy_term->generateSampleItems();
215 $this->assertInstanceOf(TermInterface::class, $entity->field_test_taxonomy_term->entity);
216 $this->entityValidateAndSave($entity);
218 // Delete the term and test again.
219 $this->term->delete();
220 $entity->field_test_taxonomy_term->generateSampleItems();
221 $this->assertInstanceOf(TermInterface::class, $entity->field_test_taxonomy_term->entity);
222 $this->entityValidateAndSave($entity);
226 * Tests the ::generateSampleValue() method when it has a circular reference.
228 public function testGenerateSampleValueCircularReference() {
229 // Delete the existing entity.
230 $this->entityStringId->delete();
232 $entity_storage = \Drupal::entityTypeManager()->getStorage('entity_test');
233 $entity = $entity_storage->createWithSampleValues('entity_test');
234 $this->assertInstanceOf(EntityTestStringId::class, $entity->field_test_entity_test_string_id->entity);
235 $this->assertInstanceOf(EntityTest::class, $entity->field_test_entity_test_string_id->entity->field_test_entity_test->entity);
239 * Tests referencing content entities with string IDs.
241 public function testContentEntityReferenceItemWithStringId() {
242 $entity = EntityTest::create();
243 $entity->field_test_entity_test_string_id->target_id = $this->entityStringId->id();
245 $storage = \Drupal::entityManager()->getStorage('entity_test');
246 $storage->resetCache();
247 $this->assertEqual($this->entityStringId->id(), $storage->load($entity->id())->field_test_entity_test_string_id->target_id);
248 // Verify that the label for the target ID property definition is correct.
249 $label = $entity->field_test_taxonomy_term->getFieldDefinition()->getFieldStorageDefinition()->getPropertyDefinition('target_id')->getLabel();
250 $this->assertTrue($label instanceof TranslatableMarkup);
251 $this->assertEqual($label->render(), 'Taxonomy term ID');
255 * Tests the entity reference field type for referencing config entities.
257 public function testConfigEntityReferenceItem() {
258 $referenced_entity_id = $this->vocabulary->id();
260 // Just being able to create the entity like this verifies a lot of code.
261 $entity = EntityTest::create();
262 $entity->field_test_taxonomy_vocabulary->target_id = $referenced_entity_id;
263 $entity->name->value = $this->randomMachineName();
266 $entity = EntityTest::load($entity->id());
267 $this->assertTrue($entity->field_test_taxonomy_vocabulary instanceof FieldItemListInterface, 'Field implements interface.');
268 $this->assertTrue($entity->field_test_taxonomy_vocabulary[0] instanceof FieldItemInterface, 'Field item implements interface.');
269 $this->assertEqual($entity->field_test_taxonomy_vocabulary->target_id, $referenced_entity_id);
270 $this->assertEqual($entity->field_test_taxonomy_vocabulary->entity->label(), $this->vocabulary->label());
271 $this->assertEqual($entity->field_test_taxonomy_vocabulary->entity->id(), $referenced_entity_id);
272 $this->assertEqual($entity->field_test_taxonomy_vocabulary->entity->uuid(), $this->vocabulary->uuid());
274 // Change the name of the term via the reference.
275 $new_name = $this->randomMachineName();
276 $entity->field_test_taxonomy_vocabulary->entity->set('name', $new_name);
277 $entity->field_test_taxonomy_vocabulary->entity->save();
278 // Verify it is the correct name.
279 $vocabulary = Vocabulary::load($referenced_entity_id);
280 $this->assertEqual($vocabulary->label(), $new_name);
282 // Make sure the computed term reflects updates to the term id.
283 $vocabulary2 = $vocabulary = Vocabulary::create([
284 'name' => $this->randomMachineName(),
285 'vid' => mb_strtolower($this->randomMachineName()),
286 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
288 $vocabulary2->save();
290 $entity->field_test_taxonomy_vocabulary->target_id = $vocabulary2->id();
291 $this->assertEqual($entity->field_test_taxonomy_vocabulary->entity->id(), $vocabulary2->id());
292 $this->assertEqual($entity->field_test_taxonomy_vocabulary->entity->label(), $vocabulary2->label());
294 // Delete terms so we have nothing to reference and try again
295 $this->vocabulary->delete();
296 $vocabulary2->delete();
297 $entity = EntityTest::create(['name' => $this->randomMachineName()]);
302 * Tests entity auto create.
304 public function testEntityAutoCreate() {
305 // The term entity is unsaved here.
306 $term = Term::create([
307 'name' => $this->randomMachineName(),
308 'vid' => $this->term->bundle(),
309 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
311 $entity = EntityTest::create();
312 // Now assign the unsaved term to the field.
313 $entity->field_test_taxonomy_term->entity = $term;
314 $entity->name->value = $this->randomMachineName();
315 // This is equal to storing an entity to tempstore or cache and retrieving
316 // it back. An example for this is node preview.
317 $entity = serialize($entity);
318 $entity = unserialize($entity);
319 // And then the entity.
321 $term = \Drupal::entityManager()->loadEntityByUuid($term->getEntityTypeId(), $term->uuid());
322 $this->assertEqual($entity->field_test_taxonomy_term->entity->id(), $term->id());
326 * Test saving order sequence doesn't matter.
328 public function testEntitySaveOrder() {
329 // The term entity is unsaved here.
330 $term = Term::create([
331 'name' => $this->randomMachineName(),
332 'vid' => $this->term->bundle(),
333 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
335 $entity = EntityTest::create();
336 // Now assign the unsaved term to the field.
337 $entity->field_test_taxonomy_term->entity = $term;
338 $entity->name->value = $this->randomMachineName();
339 // Now get the field value.
340 $value = $entity->get('field_test_taxonomy_term');
341 $this->assertTrue(empty($value['target_id']));
342 $this->assertNull($entity->field_test_taxonomy_term->target_id);
344 $entity->field_test_taxonomy_term = $value;
345 // Now save the term.
347 // And then the entity.
349 $this->assertEqual($entity->field_test_taxonomy_term->entity->id(), $term->id());
353 * Tests that the 'handler' field setting stores the proper plugin ID.
355 public function testSelectionHandlerSettings() {
356 $field_name = mb_strtolower($this->randomMachineName());
357 $field_storage = FieldStorageConfig::create([
358 'field_name' => $field_name,
359 'entity_type' => 'entity_test',
360 'type' => 'entity_reference',
362 'target_type' => 'entity_test',
365 $field_storage->save();
367 // Do not specify any value for the 'handler' setting in order to verify
368 // that the default handler with the correct derivative is used.
369 $field = FieldConfig::create([
370 'field_storage' => $field_storage,
371 'bundle' => 'entity_test',
374 $field = FieldConfig::load($field->id());
375 $this->assertEqual($field->getSetting('handler'), 'default:entity_test');
377 // Change the target_type in the field storage, and check that the handler
378 // was correctly reassigned in the field.
379 $field_storage->setSetting('target_type', 'entity_test_rev');
380 $field_storage->save();
381 $field = FieldConfig::load($field->id());
382 $this->assertEqual($field->getSetting('handler'), 'default:entity_test_rev');
384 // Change the handler to another, non-derivative plugin.
385 $field->setSetting('handler', 'views');
387 $field = FieldConfig::load($field->id());
388 $this->assertEqual($field->getSetting('handler'), 'views');
390 // Change the target_type in the field storage again, and check that the
391 // non-derivative handler was unchanged.
392 $field_storage->setSetting('target_type', 'entity_test_rev');
393 $field_storage->save();
394 $field = FieldConfig::load($field->id());
395 $this->assertEqual($field->getSetting('handler'), 'views');
399 * Tests ValidReferenceConstraint with newly created and unsaved entities.
401 public function testAutocreateValidation() {
402 // The term entity is unsaved here.
403 $term = Term::create([
404 'name' => $this->randomMachineName(),
405 'vid' => $this->term->bundle(),
406 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
408 $entity = EntityTest::create([
409 'field_test_taxonomy_term' => [
414 $errors = $entity->validate();
415 // Using target_id of NULL is valid with an unsaved entity.
416 $this->assertEqual(0, count($errors));
417 // Using target_id of NULL is not valid with a saved entity.
419 $entity = EntityTest::create([
420 'field_test_taxonomy_term' => [
425 $errors = $entity->validate();
426 $this->assertEqual(1, count($errors));
427 $this->assertEqual($errors[0]->getMessage(), 'This value should not be null.');
428 $this->assertEqual($errors[0]->getPropertyPath(), 'field_test_taxonomy_term.0');
429 // This should rectify the issue, favoring the entity over the target_id.
431 $errors = $entity->validate();
432 $this->assertEqual(0, count($errors));
434 // Test with an unpublished and unsaved node.
435 $title = $this->randomString();
436 $node = Node::create([
439 'status' => NodeInterface::NOT_PUBLISHED,
442 $entity = EntityTest::create([
443 'field_test_node' => [
448 $errors = $entity->validate();
449 $this->assertEqual(1, count($errors));
450 $this->assertEqual($errors[0]->getMessage(), new FormattableMarkup('This entity (%type: %label) cannot be referenced.', ['%type' => 'node', '%label' => $title]));
451 $this->assertEqual($errors[0]->getPropertyPath(), 'field_test_node.0.entity');
453 // Publish the node and try again.
454 $node->setPublished();
455 $errors = $entity->validate();
456 $this->assertEqual(0, count($errors));
458 // Test with a mix of valid and invalid nodes.
459 $unsaved_unpublished_node_title = $this->randomString();
460 $unsaved_unpublished_node = Node::create([
461 'title' => $unsaved_unpublished_node_title,
463 'status' => NodeInterface::NOT_PUBLISHED,
466 $saved_unpublished_node_title = $this->randomString();
467 $saved_unpublished_node = Node::create([
468 'title' => $saved_unpublished_node_title,
470 'status' => NodeInterface::NOT_PUBLISHED,
472 $saved_unpublished_node->save();
474 $saved_published_node_title = $this->randomString();
475 $saved_published_node = Node::create([
476 'title' => $saved_published_node_title,
478 'status' => NodeInterface::PUBLISHED,
480 $saved_published_node->save();
482 $entity = EntityTest::create([
483 'field_test_node' => [
485 'entity' => $unsaved_unpublished_node,
488 'target_id' => $saved_unpublished_node->id(),
491 'target_id' => $saved_published_node->id(),
496 $errors = $entity->validate();
497 $this->assertEqual(2, count($errors));
498 $this->assertEqual($errors[0]->getMessage(), new FormattableMarkup('This entity (%type: %label) cannot be referenced.', ['%type' => 'node', '%label' => $unsaved_unpublished_node_title]));
499 $this->assertEqual($errors[0]->getPropertyPath(), 'field_test_node.0.entity');
500 $this->assertEqual($errors[1]->getMessage(), new FormattableMarkup('This entity (%type: %label) cannot be referenced.', ['%type' => 'node', '%label' => $saved_unpublished_node->id()]));
501 $this->assertEqual($errors[1]->getPropertyPath(), 'field_test_node.1.target_id');
503 // Publish one of the nodes and try again.
504 $saved_unpublished_node->setPublished();
505 $saved_unpublished_node->save();
506 $errors = $entity->validate();
507 $this->assertEqual(1, count($errors));
508 $this->assertEqual($errors[0]->getMessage(), new FormattableMarkup('This entity (%type: %label) cannot be referenced.', ['%type' => 'node', '%label' => $unsaved_unpublished_node_title]));
509 $this->assertEqual($errors[0]->getPropertyPath(), 'field_test_node.0.entity');
511 // Publish the last invalid node and try again.
512 $unsaved_unpublished_node->setPublished();
513 $errors = $entity->validate();
514 $this->assertEqual(0, count($errors));
516 // Test with an unpublished and unsaved comment.
517 $title = $this->randomString();
518 $comment = Comment::create([
520 'comment_type' => 'comment',
524 $entity = EntityTest::create([
525 'field_test_comment' => [
526 'entity' => $comment,
530 $errors = $entity->validate();
531 $this->assertEqual(1, count($errors));
532 $this->assertEqual($errors[0]->getMessage(), new FormattableMarkup('This entity (%type: %label) cannot be referenced.', ['%type' => 'comment', '%label' => $title]));
533 $this->assertEqual($errors[0]->getPropertyPath(), 'field_test_comment.0.entity');
535 // Publish the comment and try again.
536 $comment->setPublished();
537 $errors = $entity->validate();
538 $this->assertEqual(0, count($errors));
540 // Test with an inactive and unsaved user.
541 $name = $this->randomString();
542 $user = User::create([
547 $entity = EntityTest::create([
548 'field_test_user' => [
553 $errors = $entity->validate();
554 $this->assertEqual(1, count($errors));
555 $this->assertEqual($errors[0]->getMessage(), new FormattableMarkup('This entity (%type: %label) cannot be referenced.', ['%type' => 'user', '%label' => $name]));
556 $this->assertEqual($errors[0]->getPropertyPath(), 'field_test_user.0.entity');
558 // Activate the user and try again.
560 $errors = $entity->validate();
561 $this->assertEqual(0, count($errors));
563 // Test with a temporary and unsaved file.
564 $filename = $this->randomMachineName() . '.txt';
565 $file = File::create([
566 'filename' => $filename,
570 $entity = EntityTest::create([
571 'field_test_file' => [
576 $errors = $entity->validate();
577 $this->assertEqual(1, count($errors));
578 $this->assertEqual($errors[0]->getMessage(), new FormattableMarkup('This entity (%type: %label) cannot be referenced.', ['%type' => 'file', '%label' => $filename]));
579 $this->assertEqual($errors[0]->getPropertyPath(), 'field_test_file.0.entity');
581 // Set the file as permanent and try again.
582 $file->setPermanent();
583 $errors = $entity->validate();
584 $this->assertEqual(0, count($errors));