5 * Contains \Drupal\Tests\Core\Entity\Sql\SqlContentEntityStorageTest.
8 namespace Drupal\Tests\Core\Entity\Sql;
10 use Drupal\Core\Cache\CacheBackendInterface;
11 use Drupal\Core\Entity\EntityFieldManagerInterface;
12 use Drupal\Core\Entity\EntityInterface;
13 use Drupal\Core\Entity\EntityManager;
14 use Drupal\Core\Entity\EntityStorageInterface;
15 use Drupal\Core\Entity\EntityTypeManagerInterface;
16 use Drupal\Core\Entity\Query\QueryFactoryInterface;
17 use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
18 use Drupal\Core\Language\Language;
19 use Drupal\Tests\UnitTestCase;
20 use Symfony\Component\DependencyInjection\ContainerBuilder;
23 * @coversDefaultClass \Drupal\Core\Entity\Sql\SqlContentEntityStorage
26 class SqlContentEntityStorageTest extends UnitTestCase {
29 * The content entity database storage used in this test.
31 * @var \Drupal\Core\Entity\Sql\SqlContentEntityStorage|\PHPUnit_Framework_MockObject_MockObject
33 protected $entityStorage;
36 * The mocked entity type used in this test.
38 * @var \Drupal\Core\Entity\ContentEntityTypeInterface|\PHPUnit_Framework_MockObject_MockObject
40 protected $entityType;
43 * An array of field definitions used for this test, keyed by field name.
45 * @var \Drupal\Core\Field\BaseFieldDefinition[]|\PHPUnit_Framework_MockObject_MockObject[]
47 protected $fieldDefinitions = [];
50 * The mocked entity manager used in this test.
52 * @var \Drupal\Core\Entity\EntityManagerInterface|\PHPUnit_Framework_MockObject_MockObject
54 protected $entityManager;
57 * The mocked entity type manager used in this test.
59 * @var \Drupal\Core\Entity\EntityTypeManagerInterface|\PHPUnit_Framework_MockObject_MockObject
61 protected $entityTypeManager;
64 * The mocked entity field manager used in this test.
66 * @var \Drupal\Core\Entity\EntityFieldManagerInterface|\PHPUnit_Framework_MockObject_MockObject
68 protected $entityFieldManager;
75 protected $entityTypeId = 'entity_test';
78 * The dependency injection container.
80 * @var \Symfony\Component\DependencyInjection\ContainerBuilder
87 * @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
89 protected $moduleHandler;
92 * The cache backend to use.
94 * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject
99 * The language manager.
101 * @var \Drupal\Core\Language\LanguageManagerInterface|\PHPUnit_Framework_MockObject_MockObject
103 protected $languageManager;
106 * The database connection to use.
108 * @var \Drupal\Core\Database\Connection|\PHPUnit_Framework_MockObject_MockObject
110 protected $connection;
115 protected function setUp() {
116 $this->entityType = $this->getMock('Drupal\Core\Entity\ContentEntityTypeInterface');
117 $this->entityType->expects($this->any())
119 ->will($this->returnValue($this->entityTypeId));
121 $this->container = new ContainerBuilder();
122 \Drupal::setContainer($this->container);
124 $this->entityManager = new EntityManager();
125 // Inject the container into entity.manager so it can defer to
126 // entity_type.manager and other services.
127 $this->entityManager->setContainer($this->container);
128 $this->entityTypeManager = $this->getMock(EntityTypeManagerInterface::class);
129 $this->entityFieldManager = $this->getMock(EntityFieldManagerInterface::class);
130 $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
131 $this->cache = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
132 $this->languageManager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface');
133 $this->languageManager->expects($this->any())
134 ->method('getDefaultLanguage')
135 ->will($this->returnValue(new Language(['langcode' => 'en'])));
136 $this->connection = $this->getMockBuilder('Drupal\Core\Database\Connection')
137 ->disableOriginalConstructor()
140 $this->container->set('entity.manager', $this->entityManager);
141 $this->container->set('entity_type.manager', $this->entityTypeManager);
142 $this->container->set('entity_field.manager', $this->entityFieldManager);
146 * Tests SqlContentEntityStorage::getBaseTable().
148 * @param string $base_table
149 * The base table to be returned by the mocked entity type.
150 * @param string $expected
151 * The expected return value of
152 * SqlContentEntityStorage::getBaseTable().
154 * @covers ::__construct
155 * @covers ::getBaseTable
157 * @dataProvider providerTestGetBaseTable
159 public function testGetBaseTable($base_table, $expected) {
160 $this->entityType->expects($this->once())
161 ->method('getBaseTable')
162 ->willReturn($base_table);
164 $this->setUpEntityStorage();
166 $this->assertSame($expected, $this->entityStorage->getBaseTable());
170 * Provides test data for testGetBaseTable().
173 * An nested array where each inner array has the base table to be returned
174 * by the mocked entity type as the first value and the expected return
175 * value of SqlContentEntityStorage::getBaseTable() as the second
178 public function providerTestGetBaseTable() {
180 // Test that the entity type's base table is used, if provided.
181 ['entity_test', 'entity_test'],
182 // Test that the storage falls back to the entity type ID.
183 [NULL, 'entity_test'],
188 * Tests SqlContentEntityStorage::getRevisionTable().
190 * @param string $revision_table
191 * The revision table to be returned by the mocked entity type.
192 * @param string $expected
193 * The expected return value of
194 * SqlContentEntityStorage::getRevisionTable().
196 * @covers ::__construct
197 * @covers ::getRevisionTable
199 * @dataProvider providerTestGetRevisionTable
201 public function testGetRevisionTable($revision_table, $expected) {
202 $this->entityType->expects($this->once())
203 ->method('isRevisionable')
204 ->will($this->returnValue(TRUE));
205 $this->entityType->expects($this->once())
206 ->method('getRevisionTable')
207 ->will($this->returnValue($revision_table));
209 $this->setUpEntityStorage();
211 $this->assertSame($expected, $this->entityStorage->getRevisionTable());
215 * Provides test data for testGetRevisionTable().
218 * An nested array where each inner array has the revision table to be
219 * returned by the mocked entity type as the first value and the expected
220 * return value of SqlContentEntityStorage::getRevisionTable() as the
223 public function providerTestGetRevisionTable() {
225 // Test that the entity type's revision table is used, if provided.
226 ['entity_test_revision', 'entity_test_revision'],
227 // Test that the storage falls back to the entity type ID with a
228 // '_revision' suffix.
229 [NULL, 'entity_test_revision'],
234 * Tests SqlContentEntityStorage::getDataTable().
236 * @covers ::__construct
237 * @covers ::getDataTable
239 public function testGetDataTable() {
240 $this->entityType->expects($this->once())
241 ->method('isTranslatable')
242 ->will($this->returnValue(TRUE));
243 $this->entityType->expects($this->exactly(1))
244 ->method('getDataTable')
245 ->will($this->returnValue('entity_test_field_data'));
247 $this->setUpEntityStorage();
249 $this->assertSame('entity_test_field_data', $this->entityStorage->getDataTable());
253 * Tests SqlContentEntityStorage::getRevisionDataTable().
255 * @param string $revision_data_table
256 * The revision data table to be returned by the mocked entity type.
257 * @param string $expected
258 * The expected return value of
259 * SqlContentEntityStorage::getRevisionDataTable().
261 * @covers ::__construct
262 * @covers ::getRevisionDataTable
264 * @dataProvider providerTestGetRevisionDataTable
266 public function testGetRevisionDataTable($revision_data_table, $expected) {
267 $this->entityType->expects($this->once())
268 ->method('isRevisionable')
269 ->will($this->returnValue(TRUE));
270 $this->entityType->expects($this->once())
271 ->method('isTranslatable')
272 ->will($this->returnValue(TRUE));
273 $this->entityType->expects($this->exactly(1))
274 ->method('getDataTable')
275 ->will($this->returnValue('entity_test_field_data'));
276 $this->entityType->expects($this->once())
277 ->method('getRevisionDataTable')
278 ->will($this->returnValue($revision_data_table));
280 $this->setUpEntityStorage();
282 $actual = $this->entityStorage->getRevisionDataTable();
283 $this->assertSame($expected, $actual);
287 * Provides test data for testGetRevisionDataTable().
290 * An nested array where each inner array has the revision data table to be
291 * returned by the mocked entity type as the first value and the expected
292 * return value of SqlContentEntityStorage::getRevisionDataTable() as
295 public function providerTestGetRevisionDataTable() {
297 // Test that the entity type's revision data table is used, if provided.
298 ['entity_test_field_revision', 'entity_test_field_revision'],
299 // Test that the storage falls back to the entity type ID with a
300 // '_field_revision' suffix.
301 [NULL, 'entity_test_field_revision'],
306 * Tests ContentEntityDatabaseStorage::onEntityTypeCreate().
308 * @covers ::__construct
309 * @covers ::onEntityTypeCreate
310 * @covers ::getTableMapping
312 public function testOnEntityTypeCreate() {
319 $this->fieldDefinitions = $this->mockFieldDefinitions(['id']);
320 $this->fieldDefinitions['id']->expects($this->any())
321 ->method('getColumns')
322 ->will($this->returnValue($columns));
323 $this->fieldDefinitions['id']->expects($this->once())
324 ->method('getSchema')
325 ->will($this->returnValue(['columns' => $columns]));
327 $this->entityType->expects($this->once())
329 ->will($this->returnValue(['id' => 'id']));
330 $this->entityType->expects($this->any())
332 ->will($this->returnValueMap([
333 // EntityStorageBase::__construct()
335 // ContentEntityStorageBase::__construct()
338 // SqlContentEntityStorageSchema::initializeBaseTable()
340 // SqlContentEntityStorageSchema::processBaseTable()
344 $this->setUpEntityStorage();
347 'description' => 'The base table for entity_test entities.',
354 'primary key' => ['id'],
357 'foreign keys' => [],
360 $schema_handler = $this->getMockBuilder('Drupal\Core\Database\Schema')
361 ->disableOriginalConstructor()
363 $schema_handler->expects($this->any())
364 ->method('createTable')
365 ->with($this->equalTo('entity_test'), $this->equalTo($expected));
367 $this->connection->expects($this->once())
369 ->will($this->returnValue($schema_handler));
371 $storage = $this->getMockBuilder('Drupal\Core\Entity\Sql\SqlContentEntityStorage')
372 ->setConstructorArgs([$this->entityType, $this->connection, $this->entityManager, $this->cache, $this->languageManager])
373 ->setMethods(['getStorageSchema'])
376 $key_value = $this->getMock('Drupal\Core\KeyValueStore\KeyValueStoreInterface');
377 $schema_handler = $this->getMockBuilder('Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema')
378 ->setConstructorArgs([$this->entityManager, $this->entityType, $storage, $this->connection])
379 ->setMethods(['installedStorageSchema', 'createSharedTableSchema'])
382 ->expects($this->any())
383 ->method('installedStorageSchema')
384 ->will($this->returnValue($key_value));
387 ->expects($this->any())
388 ->method('getStorageSchema')
389 ->will($this->returnValue($schema_handler));
391 $storage->onEntityTypeCreate($this->entityType);
395 * Tests getTableMapping() with an empty entity type.
397 * @covers ::__construct
398 * @covers ::getTableMapping
400 public function testGetTableMappingEmpty() {
401 $this->setUpEntityStorage();
403 $mapping = $this->entityStorage->getTableMapping();
404 $this->assertSame(['entity_test'], $mapping->getTableNames());
405 $this->assertSame([], $mapping->getFieldNames('entity_test'));
406 $this->assertSame([], $mapping->getExtraColumns('entity_test'));
410 * Tests getTableMapping() with a simple entity type.
412 * @param string[] $entity_keys
413 * A map of entity keys to use for the mocked entity type.
415 * @covers ::__construct
416 * @covers ::getTableMapping
418 * @dataProvider providerTestGetTableMappingSimple()
420 public function testGetTableMappingSimple(array $entity_keys) {
421 $this->entityType->expects($this->any())
423 ->will($this->returnValueMap([
424 ['id', $entity_keys['id']],
425 ['uuid', $entity_keys['uuid']],
426 ['bundle', $entity_keys['bundle']],
429 $this->setUpEntityStorage();
431 $mapping = $this->entityStorage->getTableMapping();
433 $this->assertEquals(['entity_test'], $mapping->getTableNames());
435 $expected = array_values(array_filter($entity_keys));
436 $this->assertEquals($expected, $mapping->getFieldNames('entity_test'));
438 $this->assertEquals([], $mapping->getExtraColumns('entity_test'));
442 * Tests getTableMapping() with a simple entity type with some base fields.
444 * @param string[] $entity_keys
445 * A map of entity keys to use for the mocked entity type.
447 * @covers ::__construct
448 * @covers ::getTableMapping
450 * @dataProvider providerTestGetTableMappingSimple()
452 public function testGetTableMappingSimpleWithFields(array $entity_keys) {
453 $base_field_names = ['title', 'description', 'owner'];
454 $field_names = array_merge(array_values(array_filter($entity_keys)), $base_field_names);
455 $this->fieldDefinitions = $this->mockFieldDefinitions($field_names);
456 $this->setUpEntityStorage();
458 $mapping = $this->entityStorage->getTableMapping();
459 $this->assertEquals(['entity_test'], $mapping->getTableNames());
460 $this->assertEquals($field_names, $mapping->getFieldNames('entity_test'));
461 $this->assertEquals([], $mapping->getExtraColumns('entity_test'));
465 * Provides test data for testGetTableMappingSimple().
468 * A nested array, where each inner array has a single value being a map of
469 * entity keys to use for the mocked entity type.
471 public function providerTestGetTableMappingSimple() {
473 [['id' => 'test_id', 'bundle' => NULL, 'uuid' => NULL]],
474 [['id' => 'test_id', 'bundle' => 'test_bundle', 'uuid' => NULL]],
475 [['id' => 'test_id', 'bundle' => NULL, 'uuid' => 'test_uuid']],
476 [['id' => 'test_id', 'bundle' => 'test_bundle', 'uuid' => 'test_uuid']],
481 * Tests getTableMapping() with a base field that requires a dedicated table.
483 * @covers ::__construct
484 * @covers ::getTableMapping
486 public function testGetTableMappingSimpleWithDedicatedStorageFields() {
487 $base_field_names = ['multi_valued_base_field'];
489 // Set up one entity key in order to have a base table.
490 $this->fieldDefinitions = $this->mockFieldDefinitions(['test_id']);
492 // Set up the multi-valued base field.
493 $this->fieldDefinitions += $this->mockFieldDefinitions($base_field_names, [
494 'hasCustomStorage' => FALSE,
495 'isMultiple' => TRUE,
496 'getTargetEntityTypeId' => 'entity_test',
499 $this->setUpEntityStorage();
501 $mapping = $this->entityStorage->getTableMapping();
502 $this->assertEquals(['entity_test', 'entity_test__multi_valued_base_field'], $mapping->getTableNames());
503 $this->assertEquals($base_field_names, $mapping->getFieldNames('entity_test__multi_valued_base_field'));
513 $this->assertEquals($extra_columns, $mapping->getExtraColumns('entity_test__multi_valued_base_field'));
517 * Tests getTableMapping() with a revisionable, non-translatable entity type.
519 * @param string[] $entity_keys
520 * A map of entity keys to use for the mocked entity type.
522 * @covers ::__construct
523 * @covers ::getTableMapping
525 * @dataProvider providerTestGetTableMappingSimple()
527 public function testGetTableMappingRevisionable(array $entity_keys) {
528 // This allows to re-use the data provider.
530 'id' => $entity_keys['id'],
531 'revision' => 'test_revision',
532 'bundle' => $entity_keys['bundle'],
533 'uuid' => $entity_keys['uuid'],
536 $this->entityType->expects($this->exactly(2))
537 ->method('isRevisionable')
538 ->will($this->returnValue(TRUE));
539 $this->entityType->expects($this->any())
541 ->will($this->returnValueMap([
542 ['id', $entity_keys['id']],
543 ['uuid', $entity_keys['uuid']],
544 ['bundle', $entity_keys['bundle']],
545 ['revision', $entity_keys['revision']],
547 $this->entityType->expects($this->any())
548 ->method('getRevisionMetadataKeys')
549 ->will($this->returnValue([]));
551 $this->setUpEntityStorage();
553 $mapping = $this->entityStorage->getTableMapping();
555 $expected = ['entity_test', 'entity_test_revision'];
556 $this->assertEquals($expected, $mapping->getTableNames());
558 $expected = array_values(array_filter($entity_keys));
559 $this->assertEquals($expected, $mapping->getFieldNames('entity_test'));
560 $expected = [$entity_keys['id'], $entity_keys['revision']];
561 $this->assertEquals($expected, $mapping->getFieldNames('entity_test_revision'));
563 $this->assertEquals([], $mapping->getExtraColumns('entity_test'));
564 $this->assertEquals([], $mapping->getExtraColumns('entity_test_revision'));
568 * Tests getTableMapping() with a revisionable entity type with fields.
570 * @param string[] $entity_keys
571 * A map of entity keys to use for the mocked entity type.
573 * @covers ::__construct
574 * @covers ::getTableMapping
576 * @dataProvider providerTestGetTableMappingSimple()
578 public function testGetTableMappingRevisionableWithFields(array $entity_keys) {
579 // This allows to re-use the data provider.
581 'id' => $entity_keys['id'],
582 'revision' => 'test_revision',
583 'bundle' => $entity_keys['bundle'],
584 'uuid' => $entity_keys['uuid'],
587 // PHPUnit does not allow for multiple data providers.
590 ['revision_created' => 'revision_timestamp'],
591 ['revision_user' => 'revision_uid'],
592 ['revision_log_message' => 'revision_log'],
593 ['revision_created' => 'revision_timestamp', 'revision_user' => 'revision_uid'],
594 ['revision_created' => 'revision_timestamp', 'revision_log_message' => 'revision_log'],
595 ['revision_user' => 'revision_uid', 'revision_log_message' => 'revision_log'],
596 ['revision_created' => 'revision_timestamp', 'revision_user' => 'revision_uid', 'revision_log_message' => 'revision_log'],
598 foreach ($test_cases as $revision_metadata_field_names) {
601 $base_field_names = ['title'];
602 $field_names = array_merge(array_values(array_filter($entity_keys)), $base_field_names);
603 $this->fieldDefinitions = $this->mockFieldDefinitions($field_names);
605 $revisionable_field_names = ['description', 'owner'];
606 $field_names = array_merge($field_names, $revisionable_field_names);
607 $this->fieldDefinitions += $this->mockFieldDefinitions(array_merge($revisionable_field_names, array_values($revision_metadata_field_names)), ['isRevisionable' => TRUE]);
609 $this->entityType->expects($this->exactly(2))
610 ->method('isRevisionable')
611 ->will($this->returnValue(TRUE));
612 $this->entityType->expects($this->any())
614 ->will($this->returnValueMap([
615 ['id', $entity_keys['id']],
616 ['uuid', $entity_keys['uuid']],
617 ['bundle', $entity_keys['bundle']],
618 ['revision', $entity_keys['revision']],
621 $this->entityType->expects($this->any())
622 ->method('getRevisionMetadataKeys')
623 ->will($this->returnValue($revision_metadata_field_names));
625 $this->setUpEntityStorage();
627 $mapping = $this->entityStorage->getTableMapping();
629 $expected = ['entity_test', 'entity_test_revision'];
630 $this->assertEquals($expected, $mapping->getTableNames());
632 $this->assertEquals($field_names, $mapping->getFieldNames('entity_test'));
633 $expected = array_merge(
634 [$entity_keys['id'], $entity_keys['revision']],
635 $revisionable_field_names,
636 array_values($revision_metadata_field_names)
638 $this->assertEquals($expected, $mapping->getFieldNames('entity_test_revision'));
640 $this->assertEquals([], $mapping->getExtraColumns('entity_test'));
641 $this->assertEquals([], $mapping->getExtraColumns('entity_test_revision'));
646 * Tests getTableMapping() with a non-revisionable, translatable entity type.
648 * @param string[] $entity_keys
649 * A map of entity keys to use for the mocked entity type.
651 * @covers ::__construct
652 * @covers ::getTableMapping
654 * @dataProvider providerTestGetTableMappingSimple()
656 public function testGetTableMappingTranslatable(array $entity_keys) {
657 // This allows to re-use the data provider.
658 $entity_keys['langcode'] = 'langcode';
660 $this->entityType->expects($this->atLeastOnce())
661 ->method('isTranslatable')
662 ->will($this->returnValue(TRUE));
663 $this->entityType->expects($this->atLeastOnce())
664 ->method('getDataTable')
665 ->will($this->returnValue('entity_test_field_data'));
666 $this->entityType->expects($this->any())
668 ->will($this->returnValueMap([
669 ['id', $entity_keys['id']],
670 ['uuid', $entity_keys['uuid']],
671 ['bundle', $entity_keys['bundle']],
672 ['langcode', $entity_keys['langcode']],
675 $this->setUpEntityStorage();
677 $mapping = $this->entityStorage->getTableMapping();
679 $expected = ['entity_test', 'entity_test_field_data'];
680 $this->assertEquals($expected, $mapping->getTableNames());
682 $expected = array_values(array_filter($entity_keys));
683 $actual = $mapping->getFieldNames('entity_test');
684 $this->assertEquals($expected, $actual);
685 // The UUID is not stored on the data table.
686 $expected = array_values(array_filter([
688 $entity_keys['bundle'],
689 $entity_keys['langcode'],
691 $actual = $mapping->getFieldNames('entity_test_field_data');
692 $this->assertEquals($expected, $actual);
695 $actual = $mapping->getExtraColumns('entity_test');
696 $this->assertEquals($expected, $actual);
697 $actual = $mapping->getExtraColumns('entity_test_field_data');
698 $this->assertEquals($expected, $actual);
702 * Tests getTableMapping() with a translatable entity type with fields.
704 * @param string[] $entity_keys
705 * A map of entity keys to use for the mocked entity type.
707 * @covers ::__construct
708 * @covers ::getTableMapping
710 * @dataProvider providerTestGetTableMappingSimple()
712 public function testGetTableMappingTranslatableWithFields(array $entity_keys) {
713 // This allows to re-use the data provider.
714 $entity_keys['langcode'] = 'langcode';
716 $base_field_names = ['title', 'description', 'owner'];
717 $field_names = array_merge(array_values(array_filter($entity_keys)), $base_field_names);
718 $this->fieldDefinitions = $this->mockFieldDefinitions($field_names);
720 $this->entityType->expects($this->atLeastOnce())
721 ->method('isTranslatable')
722 ->will($this->returnValue(TRUE));
723 $this->entityType->expects($this->atLeastOnce())
724 ->method('getDataTable')
725 ->will($this->returnValue('entity_test_field_data'));
726 $this->entityType->expects($this->any())
728 ->will($this->returnValueMap([
729 ['id', $entity_keys['id']],
730 ['uuid', $entity_keys['uuid']],
731 ['bundle', $entity_keys['bundle']],
732 ['langcode', $entity_keys['langcode']],
735 $this->setUpEntityStorage();
737 $mapping = $this->entityStorage->getTableMapping();
739 $expected = ['entity_test', 'entity_test_field_data'];
740 $this->assertEquals($expected, $mapping->getTableNames());
742 $expected = array_values(array_filter($entity_keys));
743 $actual = $mapping->getFieldNames('entity_test');
744 $this->assertEquals($expected, $actual);
745 // The UUID is not stored on the data table.
746 $expected = array_merge(array_filter([
748 $entity_keys['bundle'],
749 $entity_keys['langcode'],
750 ]), $base_field_names);
751 $actual = $mapping->getFieldNames('entity_test_field_data');
752 $this->assertEquals($expected, $actual);
755 $actual = $mapping->getExtraColumns('entity_test');
756 $this->assertEquals($expected, $actual);
757 $actual = $mapping->getExtraColumns('entity_test_field_data');
758 $this->assertEquals($expected, $actual);
762 * Tests getTableMapping() with a revisionable, translatable entity type.
764 * @param string[] $entity_keys
765 * A map of entity keys to use for the mocked entity type.
767 * @covers ::__construct
768 * @covers ::getTableMapping
770 * @dataProvider providerTestGetTableMappingSimple()
772 public function testGetTableMappingRevisionableTranslatable(array $entity_keys) {
773 // This allows to re-use the data provider.
775 'id' => $entity_keys['id'],
776 'revision' => 'test_revision',
777 'bundle' => $entity_keys['bundle'],
778 'uuid' => $entity_keys['uuid'],
779 'langcode' => 'langcode',
781 $revision_metadata_keys = [
782 'revision_created' => 'revision_timestamp',
783 'revision_user' => 'revision_uid',
784 'revision_log_message' => 'revision_log'
787 $this->entityType->expects($this->atLeastOnce())
788 ->method('isRevisionable')
789 ->will($this->returnValue(TRUE));
790 $this->entityType->expects($this->atLeastOnce())
791 ->method('isTranslatable')
792 ->will($this->returnValue(TRUE));
793 $this->entityType->expects($this->atLeastOnce())
794 ->method('getDataTable')
795 ->will($this->returnValue('entity_test_field_data'));
796 $this->entityType->expects($this->any())
798 ->will($this->returnValueMap([
799 ['id', $entity_keys['id']],
800 ['uuid', $entity_keys['uuid']],
801 ['bundle', $entity_keys['bundle']],
802 ['revision', $entity_keys['revision']],
803 ['langcode', $entity_keys['langcode']],
805 $this->entityType->expects($this->any())
806 ->method('getRevisionMetadataKeys')
807 ->will($this->returnValue($revision_metadata_keys));
809 $this->setUpEntityStorage();
811 $mapping = $this->entityStorage->getTableMapping();
815 'entity_test_field_data',
816 'entity_test_revision',
817 'entity_test_field_revision',
819 $this->assertEquals($expected, $mapping->getTableNames());
821 // The default language code is stored on the base table.
822 $expected = array_values(array_filter([
824 $entity_keys['revision'],
825 $entity_keys['bundle'],
826 $entity_keys['uuid'],
827 $entity_keys['langcode'],
829 $actual = $mapping->getFieldNames('entity_test');
830 $this->assertEquals($expected, $actual);
831 // The revision table on the other hand does not store the bundle and the
833 $expected = array_values(array_filter([
835 $entity_keys['revision'],
836 $entity_keys['langcode'],
838 $expected = array_merge($expected, array_values($revision_metadata_keys));
839 $actual = $mapping->getFieldNames('entity_test_revision');
840 $this->assertEquals($expected, $actual);
841 // The UUID is not stored on the data table.
842 $expected = array_values(array_filter([
844 $entity_keys['revision'],
845 $entity_keys['bundle'],
846 $entity_keys['langcode'],
848 $actual = $mapping->getFieldNames('entity_test_field_data');
849 $this->assertEquals($expected, $actual);
850 // The data revision also does not store the bundle.
851 $expected = array_values(array_filter([
853 $entity_keys['revision'],
854 $entity_keys['langcode'],
856 $actual = $mapping->getFieldNames('entity_test_field_revision');
857 $this->assertEquals($expected, $actual);
860 $actual = $mapping->getExtraColumns('entity_test');
861 $this->assertEquals($expected, $actual);
862 $actual = $mapping->getExtraColumns('entity_test_revision');
863 $this->assertEquals($expected, $actual);
864 $actual = $mapping->getExtraColumns('entity_test_field_data');
865 $this->assertEquals($expected, $actual);
866 $actual = $mapping->getExtraColumns('entity_test_field_revision');
867 $this->assertEquals($expected, $actual);
871 * Tests getTableMapping() with a complex entity type with fields.
873 * @param string[] $entity_keys
874 * A map of entity keys to use for the mocked entity type.
876 * @covers ::__construct
877 * @covers ::getTableMapping
879 * @dataProvider providerTestGetTableMappingSimple()
881 public function testGetTableMappingRevisionableTranslatableWithFields(array $entity_keys) {
882 // This allows to re-use the data provider.
884 'id' => $entity_keys['id'],
885 'revision' => 'test_revision',
886 'bundle' => $entity_keys['bundle'],
887 'uuid' => $entity_keys['uuid'],
888 'langcode' => 'langcode',
891 // PHPUnit does not allow for multiple data providers.
894 ['revision_created' => 'revision_timestamp'],
895 ['revision_user' => 'revision_uid'],
896 ['revision_log_message' => 'revision_log'],
897 ['revision_created' => 'revision_timestamp', 'revision_user' => 'revision_uid'],
898 ['revision_created' => 'revision_timestamp', 'revision_log_message' => 'revision_log'],
899 ['revision_user' => 'revision_uid', 'revision_log_message' => 'revision_log'],
900 ['revision_created' => 'revision_timestamp', 'revision_user' => 'revision_uid', 'revision_log_message' => 'revision_log'],
902 foreach ($test_cases as $revision_metadata_field_names) {
905 $base_field_names = ['title'];
906 $field_names = array_merge(array_values(array_filter($entity_keys)), $base_field_names);
907 $this->fieldDefinitions = $this->mockFieldDefinitions($field_names);
909 $revisionable_field_names = ['description', 'owner'];
910 $this->fieldDefinitions += $this->mockFieldDefinitions(array_merge($revisionable_field_names, array_values($revision_metadata_field_names)), ['isRevisionable' => TRUE]);
912 $this->entityType->expects($this->atLeastOnce())
913 ->method('isRevisionable')
914 ->will($this->returnValue(TRUE));
915 $this->entityType->expects($this->atLeastOnce())
916 ->method('isTranslatable')
917 ->will($this->returnValue(TRUE));
918 $this->entityType->expects($this->atLeastOnce())
919 ->method('getDataTable')
920 ->will($this->returnValue('entity_test_field_data'));
921 $this->entityType->expects($this->any())
923 ->will($this->returnValueMap([
924 ['id', $entity_keys['id']],
925 ['uuid', $entity_keys['uuid']],
926 ['bundle', $entity_keys['bundle']],
927 ['revision', $entity_keys['revision']],
928 ['langcode', $entity_keys['langcode']],
930 $this->entityType->expects($this->any())
931 ->method('getRevisionMetadataKeys')
932 ->will($this->returnValue($revision_metadata_field_names));
934 $this->setUpEntityStorage();
936 $mapping = $this->entityStorage->getTableMapping();
940 'entity_test_field_data',
941 'entity_test_revision',
942 'entity_test_field_revision',
944 $this->assertEquals($expected, $mapping->getTableNames());
948 'entity_test_field_data',
949 'entity_test_revision',
950 'entity_test_field_revision',
952 $this->assertEquals($expected, $mapping->getTableNames());
954 // The default language code is not stored on the base table.
955 $expected = array_values(array_filter([
957 $entity_keys['revision'],
958 $entity_keys['bundle'],
959 $entity_keys['uuid'],
960 $entity_keys['langcode'],
962 $actual = $mapping->getFieldNames('entity_test');
963 $this->assertEquals($expected, $actual);
964 // The revision table on the other hand does not store the bundle and the
966 $expected = array_merge(array_filter([
968 $entity_keys['revision'],
969 $entity_keys['langcode'],
970 ]), array_values($revision_metadata_field_names));
971 $actual = $mapping->getFieldNames('entity_test_revision');
972 $this->assertEquals($expected, $actual);
973 // The UUID is not stored on the data table.
974 $expected = array_merge(array_filter([
976 $entity_keys['revision'],
977 $entity_keys['bundle'],
978 $entity_keys['langcode'],
979 ]), $base_field_names, $revisionable_field_names);
980 $actual = $mapping->getFieldNames('entity_test_field_data');
981 $this->assertEquals($expected, $actual);
982 // The data revision also does not store the bundle.
983 $expected = array_merge(array_filter([
985 $entity_keys['revision'],
986 $entity_keys['langcode'],
987 ]), $revisionable_field_names);
988 $actual = $mapping->getFieldNames('entity_test_field_revision');
989 $this->assertEquals($expected, $actual);
992 $actual = $mapping->getExtraColumns('entity_test');
993 $this->assertEquals($expected, $actual);
994 $actual = $mapping->getExtraColumns('entity_test_revision');
995 $this->assertEquals($expected, $actual);
996 $actual = $mapping->getExtraColumns('entity_test_field_data');
997 $this->assertEquals($expected, $actual);
998 $actual = $mapping->getExtraColumns('entity_test_field_revision');
999 $this->assertEquals($expected, $actual);
1006 public function testCreate() {
1007 $language_manager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface');
1009 $language = new Language(['id' => 'en']);
1010 $language_manager->expects($this->any())
1011 ->method('getCurrentLanguage')
1012 ->will($this->returnValue($language));
1014 $this->container->set('language_manager', $language_manager);
1015 $this->container->set('module_handler', $this->moduleHandler);
1017 $entity = $this->getMockBuilder('Drupal\Core\Entity\ContentEntityBase')
1018 ->disableOriginalConstructor()
1019 ->setMethods(['id'])
1020 ->getMockForAbstractClass();
1022 $this->entityType->expects($this->atLeastOnce())
1024 ->will($this->returnValue($this->entityTypeId));
1025 $this->entityType->expects($this->atLeastOnce())
1026 ->method('getClass')
1027 ->will($this->returnValue(get_class($entity)));
1028 $this->entityType->expects($this->atLeastOnce())
1030 ->will($this->returnValue(['id' => 'id']));
1032 // ContentEntityStorageBase iterates over the entity which calls this method
1033 // internally in ContentEntityBase::getProperties().
1034 $this->entityFieldManager->expects($this->once())
1035 ->method('getFieldDefinitions')
1036 ->will($this->returnValue([]));
1038 $this->entityType->expects($this->atLeastOnce())
1039 ->method('isRevisionable')
1040 ->will($this->returnValue(FALSE));
1041 $this->entityTypeManager->expects($this->atLeastOnce())
1042 ->method('getDefinition')
1043 ->with($this->entityType->id())
1044 ->will($this->returnValue($this->entityType));
1046 $this->setUpEntityStorage();
1048 $entity = $this->entityStorage->create();
1049 $entity->expects($this->atLeastOnce())
1051 ->will($this->returnValue('foo'));
1053 $this->assertInstanceOf('Drupal\Core\Entity\EntityInterface', $entity);
1054 $this->assertSame('foo', $entity->id());
1055 $this->assertTrue($entity->isNew());
1059 * Returns a set of mock field definitions for the given names.
1061 * @param array $field_names
1062 * An array of field names.
1063 * @param array $methods
1064 * (optional) An associative array of mock method return values keyed by
1067 * @return \Drupal\Tests\Core\Field\TestBaseFieldDefinitionInterface[]|\PHPUnit_Framework_MockObject_MockObject[]
1068 * An array of mock base field definitions.
1070 protected function mockFieldDefinitions(array $field_names, $methods = []) {
1071 $field_definitions = [];
1072 $definition = $this->getMock('Drupal\Tests\Core\Field\TestBaseFieldDefinitionInterface');
1074 // Assign common method return values.
1076 'isBaseField' => TRUE,
1078 foreach ($methods as $method => $result) {
1080 ->expects($this->any())
1082 ->will($this->returnValue($result));
1085 // Assign field names to mock definitions.
1086 foreach ($field_names as $field_name) {
1087 $field_definitions[$field_name] = clone $definition;
1088 $field_definitions[$field_name]
1089 ->expects($this->any())
1091 ->will($this->returnValue($field_name));
1094 return $field_definitions;
1098 * Sets up the content entity database storage.
1100 protected function setUpEntityStorage() {
1101 $this->connection = $this->getMockBuilder('Drupal\Core\Database\Connection')
1102 ->disableOriginalConstructor()
1105 $this->entityTypeManager->expects($this->any())
1106 ->method('getDefinition')
1107 ->will($this->returnValue($this->entityType));
1109 $this->entityFieldManager->expects($this->any())
1110 ->method('getFieldStorageDefinitions')
1111 ->will($this->returnValue($this->fieldDefinitions));
1113 $this->entityFieldManager->expects($this->any())
1114 ->method('getBaseFieldDefinitions')
1115 ->will($this->returnValue($this->fieldDefinitions));
1117 $this->entityStorage = new SqlContentEntityStorage($this->entityType, $this->connection, $this->entityManager, $this->cache, $this->languageManager);
1121 * @covers ::doLoadMultiple
1122 * @covers ::buildCacheId
1123 * @covers ::getFromPersistentCache
1125 public function testLoadMultiplePersistentCached() {
1126 $this->setUpModuleHandlerNoImplementations();
1128 $key = 'values:' . $this->entityTypeId . ':1';
1130 $entity = $this->getMockBuilder('\Drupal\Tests\Core\Entity\Sql\SqlContentEntityStorageTestEntityInterface')
1131 ->getMockForAbstractClass();
1132 $entity->expects($this->any())
1134 ->will($this->returnValue($id));
1136 $this->entityType->expects($this->atLeastOnce())
1137 ->method('isPersistentlyCacheable')
1138 ->will($this->returnValue(TRUE));
1139 $this->entityType->expects($this->atLeastOnce())
1141 ->will($this->returnValue($this->entityTypeId));
1142 $this->entityType->expects($this->atLeastOnce())
1143 ->method('getClass')
1144 ->will($this->returnValue(get_class($entity)));
1146 $this->cache->expects($this->once())
1147 ->method('getMultiple')
1149 ->will($this->returnValue([$key => (object) ['data' => $entity]]));
1150 $this->cache->expects($this->never())
1153 $this->setUpEntityStorage();
1154 $entities = $this->entityStorage->loadMultiple([$id]);
1155 $this->assertEquals($entity, $entities[$id]);
1159 * @covers ::doLoadMultiple
1160 * @covers ::buildCacheId
1161 * @covers ::getFromPersistentCache
1162 * @covers ::setPersistentCache
1164 public function testLoadMultipleNoPersistentCache() {
1165 $this->setUpModuleHandlerNoImplementations();
1168 $entity = $this->getMockBuilder('\Drupal\Tests\Core\Entity\Sql\SqlContentEntityStorageTestEntityInterface')
1169 ->getMockForAbstractClass();
1170 $entity->expects($this->any())
1172 ->will($this->returnValue($id));
1174 $this->entityType->expects($this->any())
1175 ->method('isPersistentlyCacheable')
1176 ->will($this->returnValue(FALSE));
1177 $this->entityType->expects($this->atLeastOnce())
1179 ->will($this->returnValue($this->entityTypeId));
1180 $this->entityType->expects($this->atLeastOnce())
1181 ->method('getClass')
1182 ->will($this->returnValue(get_class($entity)));
1184 // There should be no calls to the cache backend for an entity type without
1185 // persistent caching.
1186 $this->cache->expects($this->never())
1187 ->method('getMultiple');
1188 $this->cache->expects($this->never())
1191 $entity_storage = $this->getMockBuilder('Drupal\Core\Entity\Sql\SqlContentEntityStorage')
1192 ->setConstructorArgs([$this->entityType, $this->connection, $this->entityManager, $this->cache, $this->languageManager])
1193 ->setMethods(['getFromStorage', 'invokeStorageLoadHook'])
1195 $entity_storage->method('invokeStorageLoadHook')
1197 $entity_storage->expects($this->once())
1198 ->method('getFromStorage')
1200 ->will($this->returnValue([$id => $entity]));
1202 $entities = $entity_storage->loadMultiple([$id]);
1203 $this->assertEquals($entity, $entities[$id]);
1207 * @covers ::doLoadMultiple
1208 * @covers ::buildCacheId
1209 * @covers ::getFromPersistentCache
1210 * @covers ::setPersistentCache
1212 public function testLoadMultiplePersistentCacheMiss() {
1213 $this->setUpModuleHandlerNoImplementations();
1216 $entity = $this->getMockBuilder('\Drupal\Tests\Core\Entity\Sql\SqlContentEntityStorageTestEntityInterface')
1217 ->getMockForAbstractClass();
1218 $entity->expects($this->any())
1220 ->will($this->returnValue($id));
1222 $this->entityType->expects($this->any())
1223 ->method('isPersistentlyCacheable')
1224 ->will($this->returnValue(TRUE));
1225 $this->entityType->expects($this->atLeastOnce())
1227 ->will($this->returnValue($this->entityTypeId));
1228 $this->entityType->expects($this->atLeastOnce())
1229 ->method('getClass')
1230 ->will($this->returnValue(get_class($entity)));
1232 // In case of a cache miss, the entity is loaded from the storage and then
1233 // set in the cache.
1234 $key = 'values:' . $this->entityTypeId . ':1';
1235 $this->cache->expects($this->once())
1236 ->method('getMultiple')
1238 ->will($this->returnValue([]));
1239 $this->cache->expects($this->once())
1241 ->with($key, $entity, CacheBackendInterface::CACHE_PERMANENT, [$this->entityTypeId . '_values', 'entity_field_info']);
1243 $entity_storage = $this->getMockBuilder('Drupal\Core\Entity\Sql\SqlContentEntityStorage')
1244 ->setConstructorArgs([$this->entityType, $this->connection, $this->entityManager, $this->cache, $this->languageManager])
1245 ->setMethods(['getFromStorage', 'invokeStorageLoadHook'])
1247 $entity_storage->method('invokeStorageLoadHook')
1249 $entity_storage->expects($this->once())
1250 ->method('getFromStorage')
1252 ->will($this->returnValue([$id => $entity]));
1254 $entities = $entity_storage->loadMultiple([$id]);
1255 $this->assertEquals($entity, $entities[$id]);
1261 public function testHasData() {
1262 $query = $this->getMock('Drupal\Core\Entity\Query\QueryInterface');
1263 $query->expects(($this->once()))
1264 ->method('accessCheck')
1266 ->willReturn($query);
1267 $query->expects(($this->once()))
1270 ->willReturn($query);
1271 $query->expects(($this->once()))
1275 $factory = $this->getMock(QueryFactoryInterface::class);
1276 $factory->expects($this->once())
1278 ->with($this->entityType, 'AND')
1279 ->willReturn($query);
1281 $this->container->set('entity.query.sql', $factory);
1283 $database = $this->getMockBuilder('Drupal\Core\Database\Connection')
1284 ->disableOriginalConstructor()
1287 $this->entityTypeManager->expects($this->any())
1288 ->method('getDefinition')
1289 ->will($this->returnValue($this->entityType));
1291 $this->entityFieldManager->expects($this->any())
1292 ->method('getFieldStorageDefinitions')
1293 ->will($this->returnValue($this->fieldDefinitions));
1295 $this->entityFieldManager->expects($this->any())
1296 ->method('getBaseFieldDefinitions')
1297 ->will($this->returnValue($this->fieldDefinitions));
1299 $this->entityStorage = new SqlContentEntityStorage($this->entityType, $database, $this->entityManager, $this->cache, $this->languageManager);
1301 $result = $this->entityStorage->hasData();
1303 $this->assertTrue($result, 'hasData returned TRUE');
1307 * Tests entity ID sanitization.
1309 public function testCleanIds() {
1340 $this->fieldDefinitions = $this->mockFieldDefinitions(['id']);
1341 $this->fieldDefinitions['id']->expects($this->any())
1343 ->will($this->returnValue('integer'));
1345 $this->setUpEntityStorage();
1347 $this->entityType->expects($this->any())
1349 ->will($this->returnValueMap(
1353 $method = new \ReflectionMethod($this->entityStorage, 'cleanIds');
1354 $method->setAccessible(TRUE);
1355 $this->assertEquals($valid_ids, $method->invoke($this->entityStorage, $valid_ids));
1371 $this->assertEquals([], $method->invoke($this->entityStorage, $invalid_ids));
1376 * Sets up the module handler with no implementations.
1378 protected function setUpModuleHandlerNoImplementations() {
1379 $this->moduleHandler->expects($this->any())
1380 ->method('getImplementations')
1381 ->will($this->returnValueMap([
1382 ['entity_load', []],
1383 [$this->entityTypeId . '_load', []]
1386 $this->container->set('module_handler', $this->moduleHandler);
1392 * Provides an entity with dummy implementations of static methods, because
1393 * those cannot be mocked.
1395 abstract class SqlContentEntityStorageTestEntityInterface implements EntityInterface {
1400 public static function postLoad(EntityStorageInterface $storage, array &$entities) {