3 namespace Drupal\Tests\field\Kernel\EntityReference;
5 use Drupal\comment\Entity\Comment;
6 use Drupal\Component\Render\FormattableMarkup;
7 use Drupal\Component\Utility\Unicode;
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\field\Tests\EntityReference\EntityReferenceTestTrait;
18 use Drupal\node\NodeInterface;
19 use Drupal\Tests\field\Kernel\FieldKernelTestBase;
20 use Drupal\file\Entity\File;
21 use Drupal\node\Entity\Node;
22 use Drupal\taxonomy\Entity\Term;
23 use Drupal\taxonomy\Entity\Vocabulary;
24 use Drupal\user\Entity\User;
27 * Tests the new entity API for the entity reference field type.
29 * @group entity_reference
31 class EntityReferenceItemTest extends FieldKernelTestBase {
33 use EntityReferenceTestTrait;
40 public static $modules = ['node', 'comment', 'file', 'taxonomy', 'text', 'filter', 'views', 'field'];
43 * The taxonomy vocabulary to test with.
45 * @var \Drupal\taxonomy\VocabularyInterface
47 protected $vocabulary;
50 * The taxonomy term to test with.
52 * @var \Drupal\taxonomy\TermInterface
57 * The test entity with a string ID.
59 * @var \Drupal\entity_test\Entity\EntityTestStringId
61 protected $entityStringId;
66 protected function setUp() {
69 $this->installEntitySchema('entity_test_string_id');
70 $this->installEntitySchema('taxonomy_term');
71 $this->installEntitySchema('node');
72 $this->installEntitySchema('comment');
73 $this->installEntitySchema('file');
75 $this->installSchema('comment', ['comment_entity_statistics']);
76 $this->installSchema('node', ['node_access']);
78 $this->vocabulary = Vocabulary::create([
79 'name' => $this->randomMachineName(),
80 'vid' => Unicode::strtolower($this->randomMachineName()),
81 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
83 $this->vocabulary->save();
85 $this->term = Term::create([
86 'name' => $this->randomMachineName(),
87 'vid' => $this->vocabulary->id(),
88 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
92 $this->entityStringId = EntityTestStringId::create([
93 'id' => $this->randomMachineName(),
95 $this->entityStringId->save();
97 // Use the util to create an instance.
98 $this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_taxonomy_term', 'Test content entity reference', 'taxonomy_term');
99 $this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_entity_test_string_id', 'Test content entity reference with string ID', 'entity_test_string_id');
100 $this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_taxonomy_vocabulary', 'Test config entity reference', 'taxonomy_vocabulary');
101 $this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_node', 'Test node entity reference', 'node', 'default', [], FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
102 $this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_user', 'Test user entity reference', 'user');
103 $this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_comment', 'Test comment entity reference', 'comment');
104 $this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_file', 'Test file entity reference', 'file');
108 * Tests the entity reference field type for referencing content entities.
110 public function testContentEntityReferenceItem() {
111 $tid = $this->term->id();
113 // Just being able to create the entity like this verifies a lot of code.
114 $entity = EntityTest::create();
115 $entity->field_test_taxonomy_term->target_id = $tid;
116 $entity->name->value = $this->randomMachineName();
119 $entity = EntityTest::load($entity->id());
120 $this->assertTrue($entity->field_test_taxonomy_term instanceof FieldItemListInterface, 'Field implements interface.');
121 $this->assertTrue($entity->field_test_taxonomy_term[0] instanceof FieldItemInterface, 'Field item implements interface.');
122 $this->assertEqual($entity->field_test_taxonomy_term->target_id, $tid);
123 $this->assertEqual($entity->field_test_taxonomy_term->entity->getName(), $this->term->getName());
124 $this->assertEqual($entity->field_test_taxonomy_term->entity->id(), $tid);
125 $this->assertEqual($entity->field_test_taxonomy_term->entity->uuid(), $this->term->uuid());
126 // Verify that the label for the target ID property definition is correct.
127 $label = $entity->field_test_taxonomy_term->getFieldDefinition()->getFieldStorageDefinition()->getPropertyDefinition('target_id')->getLabel();
128 $this->assertTrue($label instanceof TranslatableMarkup);
129 $this->assertEqual($label->render(), 'Taxonomy term ID');
131 // Change the name of the term via the reference.
132 $new_name = $this->randomMachineName();
133 $entity->field_test_taxonomy_term->entity->setName($new_name);
134 $entity->field_test_taxonomy_term->entity->save();
135 // Verify it is the correct name.
136 $term = Term::load($tid);
137 $this->assertEqual($term->getName(), $new_name);
139 // Make sure the computed term reflects updates to the term id.
140 $term2 = Term::create([
141 'name' => $this->randomMachineName(),
142 'vid' => $this->term->bundle(),
143 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
147 // Test all the possible ways of assigning a value.
148 $entity->field_test_taxonomy_term->target_id = $term->id();
149 $this->assertEqual($entity->field_test_taxonomy_term->entity->id(), $term->id());
150 $this->assertEqual($entity->field_test_taxonomy_term->entity->getName(), $term->getName());
152 $entity->field_test_taxonomy_term = [['target_id' => $term2->id()]];
153 $this->assertEqual($entity->field_test_taxonomy_term->entity->id(), $term2->id());
154 $this->assertEqual($entity->field_test_taxonomy_term->entity->getName(), $term2->getName());
156 // Test value assignment via the computed 'entity' property.
157 $entity->field_test_taxonomy_term->entity = $term;
158 $this->assertEqual($entity->field_test_taxonomy_term->target_id, $term->id());
159 $this->assertEqual($entity->field_test_taxonomy_term->entity->getName(), $term->getName());
161 $entity->field_test_taxonomy_term = [['entity' => $term2]];
162 $this->assertEqual($entity->field_test_taxonomy_term->target_id, $term2->id());
163 $this->assertEqual($entity->field_test_taxonomy_term->entity->getName(), $term2->getName());
165 // Test assigning an invalid item throws an exception.
167 $entity->field_test_taxonomy_term = ['target_id' => 'invalid', 'entity' => $term2];
168 $this->fail('Assigning an invalid item throws an exception.');
170 catch (\InvalidArgumentException $e) {
171 $this->pass('Assigning an invalid item throws an exception.');
174 // Delete terms so we have nothing to reference and try again
177 $entity = EntityTest::create(['name' => $this->randomMachineName()]);
180 // Test the generateSampleValue() method.
181 $entity = EntityTest::create();
182 $entity->field_test_taxonomy_term->generateSampleItems();
183 $entity->field_test_taxonomy_vocabulary->generateSampleItems();
184 $this->entityValidateAndSave($entity);
186 // Tests that setting an integer target ID together with an entity object
187 // succeeds and does not cause any exceptions. There is no assertion here,
188 // as the assignment should not throw any exceptions and if it does the
190 // @see \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem::setValue().
191 $user = User::create(['name' => $this->randomString()]);
193 $entity = EntityTest::create(['user_id' => ['target_id' => (int) $user->id(), 'entity' => $user]]);
197 * Tests referencing content entities with string IDs.
199 public function testContentEntityReferenceItemWithStringId() {
200 $entity = EntityTest::create();
201 $entity->field_test_entity_test_string_id->target_id = $this->entityStringId->id();
203 $storage = \Drupal::entityManager()->getStorage('entity_test');
204 $storage->resetCache();
205 $this->assertEqual($this->entityStringId->id(), $storage->load($entity->id())->field_test_entity_test_string_id->target_id);
206 // Verify that the label for the target ID property definition is correct.
207 $label = $entity->field_test_taxonomy_term->getFieldDefinition()->getFieldStorageDefinition()->getPropertyDefinition('target_id')->getLabel();
208 $this->assertTrue($label instanceof TranslatableMarkup);
209 $this->assertEqual($label->render(), 'Taxonomy term ID');
213 * Tests the entity reference field type for referencing config entities.
215 public function testConfigEntityReferenceItem() {
216 $referenced_entity_id = $this->vocabulary->id();
218 // Just being able to create the entity like this verifies a lot of code.
219 $entity = EntityTest::create();
220 $entity->field_test_taxonomy_vocabulary->target_id = $referenced_entity_id;
221 $entity->name->value = $this->randomMachineName();
224 $entity = EntityTest::load($entity->id());
225 $this->assertTrue($entity->field_test_taxonomy_vocabulary instanceof FieldItemListInterface, 'Field implements interface.');
226 $this->assertTrue($entity->field_test_taxonomy_vocabulary[0] instanceof FieldItemInterface, 'Field item implements interface.');
227 $this->assertEqual($entity->field_test_taxonomy_vocabulary->target_id, $referenced_entity_id);
228 $this->assertEqual($entity->field_test_taxonomy_vocabulary->entity->label(), $this->vocabulary->label());
229 $this->assertEqual($entity->field_test_taxonomy_vocabulary->entity->id(), $referenced_entity_id);
230 $this->assertEqual($entity->field_test_taxonomy_vocabulary->entity->uuid(), $this->vocabulary->uuid());
232 // Change the name of the term via the reference.
233 $new_name = $this->randomMachineName();
234 $entity->field_test_taxonomy_vocabulary->entity->set('name', $new_name);
235 $entity->field_test_taxonomy_vocabulary->entity->save();
236 // Verify it is the correct name.
237 $vocabulary = Vocabulary::load($referenced_entity_id);
238 $this->assertEqual($vocabulary->label(), $new_name);
240 // Make sure the computed term reflects updates to the term id.
241 $vocabulary2 = $vocabulary = Vocabulary::create([
242 'name' => $this->randomMachineName(),
243 'vid' => Unicode::strtolower($this->randomMachineName()),
244 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
246 $vocabulary2->save();
248 $entity->field_test_taxonomy_vocabulary->target_id = $vocabulary2->id();
249 $this->assertEqual($entity->field_test_taxonomy_vocabulary->entity->id(), $vocabulary2->id());
250 $this->assertEqual($entity->field_test_taxonomy_vocabulary->entity->label(), $vocabulary2->label());
252 // Delete terms so we have nothing to reference and try again
253 $this->vocabulary->delete();
254 $vocabulary2->delete();
255 $entity = EntityTest::create(['name' => $this->randomMachineName()]);
260 * Tests entity auto create.
262 public function testEntityAutoCreate() {
263 // The term entity is unsaved here.
264 $term = Term::create([
265 'name' => $this->randomMachineName(),
266 'vid' => $this->term->bundle(),
267 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
269 $entity = EntityTest::create();
270 // Now assign the unsaved term to the field.
271 $entity->field_test_taxonomy_term->entity = $term;
272 $entity->name->value = $this->randomMachineName();
273 // This is equal to storing an entity to tempstore or cache and retrieving
274 // it back. An example for this is node preview.
275 $entity = serialize($entity);
276 $entity = unserialize($entity);
277 // And then the entity.
279 $term = \Drupal::entityManager()->loadEntityByUuid($term->getEntityTypeId(), $term->uuid());
280 $this->assertEqual($entity->field_test_taxonomy_term->entity->id(), $term->id());
284 * Test saving order sequence doesn't matter.
286 public function testEntitySaveOrder() {
287 // The term entity is unsaved here.
288 $term = Term::create([
289 'name' => $this->randomMachineName(),
290 'vid' => $this->term->bundle(),
291 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
293 $entity = EntityTest::create();
294 // Now assign the unsaved term to the field.
295 $entity->field_test_taxonomy_term->entity = $term;
296 $entity->name->value = $this->randomMachineName();
297 // Now get the field value.
298 $value = $entity->get('field_test_taxonomy_term');
299 $this->assertTrue(empty($value['target_id']));
300 $this->assertNull($entity->field_test_taxonomy_term->target_id);
302 $entity->field_test_taxonomy_term = $value;
303 // Now save the term.
305 // And then the entity.
307 $this->assertEqual($entity->field_test_taxonomy_term->entity->id(), $term->id());
311 * Tests that the 'handler' field setting stores the proper plugin ID.
313 public function testSelectionHandlerSettings() {
314 $field_name = Unicode::strtolower($this->randomMachineName());
315 $field_storage = FieldStorageConfig::create([
316 'field_name' => $field_name,
317 'entity_type' => 'entity_test',
318 'type' => 'entity_reference',
320 'target_type' => 'entity_test'
323 $field_storage->save();
325 // Do not specify any value for the 'handler' setting in order to verify
326 // that the default handler with the correct derivative is used.
327 $field = FieldConfig::create([
328 'field_storage' => $field_storage,
329 'bundle' => 'entity_test',
332 $field = FieldConfig::load($field->id());
333 $this->assertEqual($field->getSetting('handler'), 'default:entity_test');
335 // Change the target_type in the field storage, and check that the handler
336 // was correctly reassigned in the field.
337 $field_storage->setSetting('target_type', 'entity_test_rev');
338 $field_storage->save();
339 $field = FieldConfig::load($field->id());
340 $this->assertEqual($field->getSetting('handler'), 'default:entity_test_rev');
342 // Change the handler to another, non-derivative plugin.
343 $field->setSetting('handler', 'views');
345 $field = FieldConfig::load($field->id());
346 $this->assertEqual($field->getSetting('handler'), 'views');
348 // Change the target_type in the field storage again, and check that the
349 // non-derivative handler was unchanged.
350 $field_storage->setSetting('target_type', 'entity_test_rev');
351 $field_storage->save();
352 $field = FieldConfig::load($field->id());
353 $this->assertEqual($field->getSetting('handler'), 'views');
357 * Tests ValidReferenceConstraint with newly created and unsaved entities.
359 public function testAutocreateValidation() {
360 // The term entity is unsaved here.
361 $term = Term::create([
362 'name' => $this->randomMachineName(),
363 'vid' => $this->term->bundle(),
364 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
366 $entity = EntityTest::create([
367 'field_test_taxonomy_term' => [
372 $errors = $entity->validate();
373 // Using target_id of NULL is valid with an unsaved entity.
374 $this->assertEqual(0, count($errors));
375 // Using target_id of NULL is not valid with a saved entity.
377 $entity = EntityTest::create([
378 'field_test_taxonomy_term' => [
383 $errors = $entity->validate();
384 $this->assertEqual(1, count($errors));
385 $this->assertEqual($errors[0]->getMessage(), 'This value should not be null.');
386 $this->assertEqual($errors[0]->getPropertyPath(), 'field_test_taxonomy_term.0');
387 // This should rectify the issue, favoring the entity over the target_id.
389 $errors = $entity->validate();
390 $this->assertEqual(0, count($errors));
392 // Test with an unpublished and unsaved node.
393 $title = $this->randomString();
394 $node = Node::create([
397 'status' => NodeInterface::NOT_PUBLISHED,
400 $entity = EntityTest::create([
401 'field_test_node' => [
406 $errors = $entity->validate();
407 $this->assertEqual(1, count($errors));
408 $this->assertEqual($errors[0]->getMessage(), new FormattableMarkup('This entity (%type: %label) cannot be referenced.', ['%type' => 'node', '%label' => $title]));
409 $this->assertEqual($errors[0]->getPropertyPath(), 'field_test_node.0.entity');
411 // Publish the node and try again.
412 $node->setPublished(TRUE);
413 $errors = $entity->validate();
414 $this->assertEqual(0, count($errors));
416 // Test with a mix of valid and invalid nodes.
417 $unsaved_unpublished_node_title = $this->randomString();
418 $unsaved_unpublished_node = Node::create([
419 'title' => $unsaved_unpublished_node_title,
421 'status' => NodeInterface::NOT_PUBLISHED,
424 $saved_unpublished_node_title = $this->randomString();
425 $saved_unpublished_node = Node::create([
426 'title' => $saved_unpublished_node_title,
428 'status' => NodeInterface::NOT_PUBLISHED,
430 $saved_unpublished_node->save();
432 $saved_published_node_title = $this->randomString();
433 $saved_published_node = Node::create([
434 'title' => $saved_published_node_title,
436 'status' => NodeInterface::PUBLISHED,
438 $saved_published_node->save();
440 $entity = EntityTest::create([
441 'field_test_node' => [
443 'entity' => $unsaved_unpublished_node,
446 'target_id' => $saved_unpublished_node->id(),
449 'target_id' => $saved_published_node->id(),
454 $errors = $entity->validate();
455 $this->assertEqual(2, count($errors));
456 $this->assertEqual($errors[0]->getMessage(), new FormattableMarkup('This entity (%type: %label) cannot be referenced.', ['%type' => 'node', '%label' => $unsaved_unpublished_node_title]));
457 $this->assertEqual($errors[0]->getPropertyPath(), 'field_test_node.0.entity');
458 $this->assertEqual($errors[1]->getMessage(), new FormattableMarkup('This entity (%type: %label) cannot be referenced.', ['%type' => 'node', '%label' => $saved_unpublished_node->id()]));
459 $this->assertEqual($errors[1]->getPropertyPath(), 'field_test_node.1.target_id');
461 // Publish one of the nodes and try again.
462 $saved_unpublished_node->setPublished(TRUE);
463 $saved_unpublished_node->save();
464 $errors = $entity->validate();
465 $this->assertEqual(1, count($errors));
466 $this->assertEqual($errors[0]->getMessage(), new FormattableMarkup('This entity (%type: %label) cannot be referenced.', ['%type' => 'node', '%label' => $unsaved_unpublished_node_title]));
467 $this->assertEqual($errors[0]->getPropertyPath(), 'field_test_node.0.entity');
469 // Publish the last invalid node and try again.
470 $unsaved_unpublished_node->setPublished(TRUE);
471 $errors = $entity->validate();
472 $this->assertEqual(0, count($errors));
474 // Test with an unpublished and unsaved comment.
475 $title = $this->randomString();
476 $comment = Comment::create([
478 'comment_type' => 'comment',
482 $entity = EntityTest::create([
483 'field_test_comment' => [
484 'entity' => $comment,
488 $errors = $entity->validate();
489 $this->assertEqual(1, count($errors));
490 $this->assertEqual($errors[0]->getMessage(), new FormattableMarkup('This entity (%type: %label) cannot be referenced.', ['%type' => 'comment', '%label' => $title]));
491 $this->assertEqual($errors[0]->getPropertyPath(), 'field_test_comment.0.entity');
493 // Publish the comment and try again.
494 $comment->setPublished(TRUE);
495 $errors = $entity->validate();
496 $this->assertEqual(0, count($errors));
498 // Test with an inactive and unsaved user.
499 $name = $this->randomString();
500 $user = User::create([
505 $entity = EntityTest::create([
506 'field_test_user' => [
511 $errors = $entity->validate();
512 $this->assertEqual(1, count($errors));
513 $this->assertEqual($errors[0]->getMessage(), new FormattableMarkup('This entity (%type: %label) cannot be referenced.', ['%type' => 'user', '%label' => $name]));
514 $this->assertEqual($errors[0]->getPropertyPath(), 'field_test_user.0.entity');
516 // Activate the user and try again.
518 $errors = $entity->validate();
519 $this->assertEqual(0, count($errors));
521 // Test with a temporary and unsaved file.
522 $filename = $this->randomMachineName() . '.txt';
523 $file = File::create([
524 'filename' => $filename,
528 $entity = EntityTest::create([
529 'field_test_file' => [
534 $errors = $entity->validate();
535 $this->assertEqual(1, count($errors));
536 $this->assertEqual($errors[0]->getMessage(), new FormattableMarkup('This entity (%type: %label) cannot be referenced.', ['%type' => 'file', '%label' => $filename]));
537 $this->assertEqual($errors[0]->getPropertyPath(), 'field_test_file.0.entity');
539 // Set the file as permanent and try again.
540 $file->setPermanent();
541 $errors = $entity->validate();
542 $this->assertEqual(0, count($errors));