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\EntityInterface;
12 use Drupal\Core\Entity\EntityStorageInterface;
13 use Drupal\Core\Entity\Query\QueryFactoryInterface;
14 use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
15 use Drupal\Core\Language\Language;
16 use Drupal\Tests\UnitTestCase;
17 use Symfony\Component\DependencyInjection\ContainerBuilder;
20 * @coversDefaultClass \Drupal\Core\Entity\Sql\SqlContentEntityStorage
23 class SqlContentEntityStorageTest extends UnitTestCase {
26 * The content entity database storage used in this test.
28 * @var \Drupal\Core\Entity\Sql\SqlContentEntityStorage|\PHPUnit_Framework_MockObject_MockObject
30 protected $entityStorage;
33 * The mocked entity type used in this test.
35 * @var \Drupal\Core\Entity\ContentEntityTypeInterface|\PHPUnit_Framework_MockObject_MockObject
37 protected $entityType;
40 * An array of field definitions used for this test, keyed by field name.
42 * @var \Drupal\Core\Field\BaseFieldDefinition[]|\PHPUnit_Framework_MockObject_MockObject[]
44 protected $fieldDefinitions = [];
47 * The mocked entity manager used in this test.
49 * @var \Drupal\Core\Entity\EntityManagerInterface|\PHPUnit_Framework_MockObject_MockObject
51 protected $entityManager;
58 protected $entityTypeId = 'entity_test';
61 * The dependency injection container.
63 * @var \Symfony\Component\DependencyInjection\ContainerBuilder
70 * @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
72 protected $moduleHandler;
75 * The cache backend to use.
77 * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject
82 * The language manager.
84 * @var \Drupal\Core\Language\LanguageManagerInterface|\PHPUnit_Framework_MockObject_MockObject
86 protected $languageManager;
89 * The database connection to use.
91 * @var \Drupal\Core\Database\Connection|\PHPUnit_Framework_MockObject_MockObject
93 protected $connection;
98 protected function setUp() {
99 $this->entityType = $this->getMock('Drupal\Core\Entity\ContentEntityTypeInterface');
100 $this->entityType->expects($this->any())
102 ->will($this->returnValue($this->entityTypeId));
104 $this->container = new ContainerBuilder();
105 \Drupal::setContainer($this->container);
107 $this->entityManager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface');
108 $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
109 $this->cache = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
110 $this->languageManager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface');
111 $this->languageManager->expects($this->any())
112 ->method('getDefaultLanguage')
113 ->will($this->returnValue(new Language(['langcode' => 'en'])));
114 $this->connection = $this->getMockBuilder('Drupal\Core\Database\Connection')
115 ->disableOriginalConstructor()
120 * Tests SqlContentEntityStorage::getBaseTable().
122 * @param string $base_table
123 * The base table to be returned by the mocked entity type.
124 * @param string $expected
125 * The expected return value of
126 * SqlContentEntityStorage::getBaseTable().
128 * @covers ::__construct
129 * @covers ::getBaseTable
131 * @dataProvider providerTestGetBaseTable
133 public function testGetBaseTable($base_table, $expected) {
134 $this->entityType->expects($this->once())
135 ->method('getBaseTable')
136 ->willReturn($base_table);
138 $this->setUpEntityStorage();
140 $this->assertSame($expected, $this->entityStorage->getBaseTable());
144 * Provides test data for testGetBaseTable().
147 * An nested array where each inner array has the base table to be returned
148 * by the mocked entity type as the first value and the expected return
149 * value of SqlContentEntityStorage::getBaseTable() as the second
152 public function providerTestGetBaseTable() {
154 // Test that the entity type's base table is used, if provided.
155 ['entity_test', 'entity_test'],
156 // Test that the storage falls back to the entity type ID.
157 [NULL, 'entity_test'],
162 * Tests SqlContentEntityStorage::getRevisionTable().
164 * @param string $revision_table
165 * The revision table to be returned by the mocked entity type.
166 * @param string $expected
167 * The expected return value of
168 * SqlContentEntityStorage::getRevisionTable().
170 * @covers ::__construct
171 * @covers ::getRevisionTable
173 * @dataProvider providerTestGetRevisionTable
175 public function testGetRevisionTable($revision_table, $expected) {
176 $this->entityType->expects($this->once())
177 ->method('isRevisionable')
178 ->will($this->returnValue(TRUE));
179 $this->entityType->expects($this->once())
180 ->method('getRevisionTable')
181 ->will($this->returnValue($revision_table));
183 $this->setUpEntityStorage();
185 $this->assertSame($expected, $this->entityStorage->getRevisionTable());
189 * Provides test data for testGetRevisionTable().
192 * An nested array where each inner array has the revision table to be
193 * returned by the mocked entity type as the first value and the expected
194 * return value of SqlContentEntityStorage::getRevisionTable() as the
197 public function providerTestGetRevisionTable() {
199 // Test that the entity type's revision table is used, if provided.
200 ['entity_test_revision', 'entity_test_revision'],
201 // Test that the storage falls back to the entity type ID with a
202 // '_revision' suffix.
203 [NULL, 'entity_test_revision'],
208 * Tests SqlContentEntityStorage::getDataTable().
210 * @covers ::__construct
211 * @covers ::getDataTable
213 public function testGetDataTable() {
214 $this->entityType->expects($this->once())
215 ->method('isTranslatable')
216 ->will($this->returnValue(TRUE));
217 $this->entityType->expects($this->exactly(1))
218 ->method('getDataTable')
219 ->will($this->returnValue('entity_test_field_data'));
221 $this->setUpEntityStorage();
223 $this->assertSame('entity_test_field_data', $this->entityStorage->getDataTable());
227 * Tests SqlContentEntityStorage::getRevisionDataTable().
229 * @param string $revision_data_table
230 * The revision data table to be returned by the mocked entity type.
231 * @param string $expected
232 * The expected return value of
233 * SqlContentEntityStorage::getRevisionDataTable().
235 * @covers ::__construct
236 * @covers ::getRevisionDataTable
238 * @dataProvider providerTestGetRevisionDataTable
240 public function testGetRevisionDataTable($revision_data_table, $expected) {
241 $this->entityType->expects($this->once())
242 ->method('isRevisionable')
243 ->will($this->returnValue(TRUE));
244 $this->entityType->expects($this->once())
245 ->method('isTranslatable')
246 ->will($this->returnValue(TRUE));
247 $this->entityType->expects($this->exactly(1))
248 ->method('getDataTable')
249 ->will($this->returnValue('entity_test_field_data'));
250 $this->entityType->expects($this->once())
251 ->method('getRevisionDataTable')
252 ->will($this->returnValue($revision_data_table));
254 $this->setUpEntityStorage();
256 $actual = $this->entityStorage->getRevisionDataTable();
257 $this->assertSame($expected, $actual);
261 * Provides test data for testGetRevisionDataTable().
264 * An nested array where each inner array has the revision data table to be
265 * returned by the mocked entity type as the first value and the expected
266 * return value of SqlContentEntityStorage::getRevisionDataTable() as
269 public function providerTestGetRevisionDataTable() {
271 // Test that the entity type's revision data table is used, if provided.
272 ['entity_test_field_revision', 'entity_test_field_revision'],
273 // Test that the storage falls back to the entity type ID with a
274 // '_field_revision' suffix.
275 [NULL, 'entity_test_field_revision'],
280 * Tests ContentEntityDatabaseStorage::onEntityTypeCreate().
282 * @covers ::__construct
283 * @covers ::onEntityTypeCreate
284 * @covers ::getTableMapping
286 public function testOnEntityTypeCreate() {
293 $this->fieldDefinitions = $this->mockFieldDefinitions(['id']);
294 $this->fieldDefinitions['id']->expects($this->any())
295 ->method('getColumns')
296 ->will($this->returnValue($columns));
297 $this->fieldDefinitions['id']->expects($this->once())
298 ->method('getSchema')
299 ->will($this->returnValue(['columns' => $columns]));
301 $this->entityType->expects($this->once())
303 ->will($this->returnValue(['id' => 'id']));
304 $this->entityType->expects($this->any())
306 ->will($this->returnValueMap([
307 // EntityStorageBase::__construct()
309 // ContentEntityStorageBase::__construct()
312 // SqlContentEntityStorageSchema::initializeBaseTable()
314 // SqlContentEntityStorageSchema::processBaseTable()
318 $this->setUpEntityStorage();
321 'description' => 'The base table for entity_test entities.',
328 'primary key' => ['id'],
331 'foreign keys' => [],
334 $schema_handler = $this->getMockBuilder('Drupal\Core\Database\Schema')
335 ->disableOriginalConstructor()
337 $schema_handler->expects($this->any())
338 ->method('createTable')
339 ->with($this->equalTo('entity_test'), $this->equalTo($expected));
341 $this->connection->expects($this->once())
343 ->will($this->returnValue($schema_handler));
345 $storage = $this->getMockBuilder('Drupal\Core\Entity\Sql\SqlContentEntityStorage')
346 ->setConstructorArgs([$this->entityType, $this->connection, $this->entityManager, $this->cache, $this->languageManager])
347 ->setMethods(['getStorageSchema'])
350 $key_value = $this->getMock('Drupal\Core\KeyValueStore\KeyValueStoreInterface');
351 $schema_handler = $this->getMockBuilder('Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema')
352 ->setConstructorArgs([$this->entityManager, $this->entityType, $storage, $this->connection])
353 ->setMethods(['installedStorageSchema', 'createSharedTableSchema'])
356 ->expects($this->any())
357 ->method('installedStorageSchema')
358 ->will($this->returnValue($key_value));
361 ->expects($this->any())
362 ->method('getStorageSchema')
363 ->will($this->returnValue($schema_handler));
365 $storage->onEntityTypeCreate($this->entityType);
369 * Tests getTableMapping() with an empty entity type.
371 * @covers ::__construct
372 * @covers ::getTableMapping
374 public function testGetTableMappingEmpty() {
375 $this->setUpEntityStorage();
377 $mapping = $this->entityStorage->getTableMapping();
378 $this->assertSame(['entity_test'], $mapping->getTableNames());
379 $this->assertSame([], $mapping->getFieldNames('entity_test'));
380 $this->assertSame([], $mapping->getExtraColumns('entity_test'));
384 * Tests getTableMapping() with a simple entity type.
386 * @param string[] $entity_keys
387 * A map of entity keys to use for the mocked entity type.
389 * @covers ::__construct
390 * @covers ::getTableMapping
392 * @dataProvider providerTestGetTableMappingSimple()
394 public function testGetTableMappingSimple(array $entity_keys) {
395 $this->entityType->expects($this->any())
397 ->will($this->returnValueMap([
398 ['id', $entity_keys['id']],
399 ['uuid', $entity_keys['uuid']],
400 ['bundle', $entity_keys['bundle']],
403 $this->setUpEntityStorage();
405 $mapping = $this->entityStorage->getTableMapping();
407 $this->assertEquals(['entity_test'], $mapping->getTableNames());
409 $expected = array_values(array_filter($entity_keys));
410 $this->assertEquals($expected, $mapping->getFieldNames('entity_test'));
412 $this->assertEquals([], $mapping->getExtraColumns('entity_test'));
416 * Tests getTableMapping() with a simple entity type with some base fields.
418 * @param string[] $entity_keys
419 * A map of entity keys to use for the mocked entity type.
421 * @covers ::__construct
422 * @covers ::getTableMapping
424 * @dataProvider providerTestGetTableMappingSimple()
426 public function testGetTableMappingSimpleWithFields(array $entity_keys) {
427 $base_field_names = ['title', 'description', 'owner'];
428 $field_names = array_merge(array_values(array_filter($entity_keys)), $base_field_names);
429 $this->fieldDefinitions = $this->mockFieldDefinitions($field_names);
430 $this->setUpEntityStorage();
432 $mapping = $this->entityStorage->getTableMapping();
433 $this->assertEquals(['entity_test'], $mapping->getTableNames());
434 $this->assertEquals($field_names, $mapping->getFieldNames('entity_test'));
435 $this->assertEquals([], $mapping->getExtraColumns('entity_test'));
439 * Provides test data for testGetTableMappingSimple().
442 * A nested array, where each inner array has a single value being a map of
443 * entity keys to use for the mocked entity type.
445 public function providerTestGetTableMappingSimple() {
447 [['id' => 'test_id', 'bundle' => NULL, 'uuid' => NULL]],
448 [['id' => 'test_id', 'bundle' => 'test_bundle', 'uuid' => NULL]],
449 [['id' => 'test_id', 'bundle' => NULL, 'uuid' => 'test_uuid']],
450 [['id' => 'test_id', 'bundle' => 'test_bundle', 'uuid' => 'test_uuid']],
455 * Tests getTableMapping() with a base field that requires a dedicated table.
457 * @covers ::__construct
458 * @covers ::getTableMapping
460 public function testGetTableMappingSimpleWithDedicatedStorageFields() {
461 $base_field_names = ['multi_valued_base_field'];
463 // Set up one entity key in order to have a base table.
464 $this->fieldDefinitions = $this->mockFieldDefinitions(['test_id']);
466 // Set up the multi-valued base field.
467 $this->fieldDefinitions += $this->mockFieldDefinitions($base_field_names, [
468 'hasCustomStorage' => FALSE,
469 'isMultiple' => TRUE,
470 'getTargetEntityTypeId' => 'entity_test',
473 $this->setUpEntityStorage();
475 $mapping = $this->entityStorage->getTableMapping();
476 $this->assertEquals(['entity_test', 'entity_test__multi_valued_base_field'], $mapping->getTableNames());
477 $this->assertEquals($base_field_names, $mapping->getFieldNames('entity_test__multi_valued_base_field'));
487 $this->assertEquals($extra_columns, $mapping->getExtraColumns('entity_test__multi_valued_base_field'));
491 * Tests getTableMapping() with a revisionable, non-translatable entity type.
493 * @param string[] $entity_keys
494 * A map of entity keys to use for the mocked entity type.
496 * @covers ::__construct
497 * @covers ::getTableMapping
499 * @dataProvider providerTestGetTableMappingSimple()
501 public function testGetTableMappingRevisionable(array $entity_keys) {
502 // This allows to re-use the data provider.
504 'id' => $entity_keys['id'],
505 'revision' => 'test_revision',
506 'bundle' => $entity_keys['bundle'],
507 'uuid' => $entity_keys['uuid'],
510 $this->entityType->expects($this->exactly(2))
511 ->method('isRevisionable')
512 ->will($this->returnValue(TRUE));
513 $this->entityType->expects($this->any())
515 ->will($this->returnValueMap([
516 ['id', $entity_keys['id']],
517 ['uuid', $entity_keys['uuid']],
518 ['bundle', $entity_keys['bundle']],
519 ['revision', $entity_keys['revision']],
521 $this->entityType->expects($this->any())
522 ->method('getRevisionMetadataKeys')
523 ->will($this->returnValue([]));
525 $this->setUpEntityStorage();
527 $mapping = $this->entityStorage->getTableMapping();
529 $expected = ['entity_test', 'entity_test_revision'];
530 $this->assertEquals($expected, $mapping->getTableNames());
532 $expected = array_values(array_filter($entity_keys));
533 $this->assertEquals($expected, $mapping->getFieldNames('entity_test'));
534 $expected = [$entity_keys['id'], $entity_keys['revision']];
535 $this->assertEquals($expected, $mapping->getFieldNames('entity_test_revision'));
537 $this->assertEquals([], $mapping->getExtraColumns('entity_test'));
538 $this->assertEquals([], $mapping->getExtraColumns('entity_test_revision'));
542 * Tests getTableMapping() with a revisionable entity type with fields.
544 * @param string[] $entity_keys
545 * A map of entity keys to use for the mocked entity type.
547 * @covers ::__construct
548 * @covers ::getTableMapping
550 * @dataProvider providerTestGetTableMappingSimple()
552 public function testGetTableMappingRevisionableWithFields(array $entity_keys) {
553 // This allows to re-use the data provider.
555 'id' => $entity_keys['id'],
556 'revision' => 'test_revision',
557 'bundle' => $entity_keys['bundle'],
558 'uuid' => $entity_keys['uuid'],
561 // PHPUnit does not allow for multiple data providers.
564 ['revision_created' => 'revision_timestamp'],
565 ['revision_user' => 'revision_uid'],
566 ['revision_log_message' => 'revision_log'],
567 ['revision_created' => 'revision_timestamp', 'revision_user' => 'revision_uid'],
568 ['revision_created' => 'revision_timestamp', 'revision_log_message' => 'revision_log'],
569 ['revision_user' => 'revision_uid', 'revision_log_message' => 'revision_log'],
570 ['revision_created' => 'revision_timestamp', 'revision_user' => 'revision_uid', 'revision_log_message' => 'revision_log'],
572 foreach ($test_cases as $revision_metadata_field_names) {
575 $base_field_names = ['title'];
576 $field_names = array_merge(array_values(array_filter($entity_keys)), $base_field_names);
577 $this->fieldDefinitions = $this->mockFieldDefinitions($field_names);
579 $revisionable_field_names = ['description', 'owner'];
580 $field_names = array_merge($field_names, $revisionable_field_names);
581 $this->fieldDefinitions += $this->mockFieldDefinitions(array_merge($revisionable_field_names, array_values($revision_metadata_field_names)), ['isRevisionable' => TRUE]);
583 $this->entityType->expects($this->exactly(2))
584 ->method('isRevisionable')
585 ->will($this->returnValue(TRUE));
586 $this->entityType->expects($this->any())
588 ->will($this->returnValueMap([
589 ['id', $entity_keys['id']],
590 ['uuid', $entity_keys['uuid']],
591 ['bundle', $entity_keys['bundle']],
592 ['revision', $entity_keys['revision']],
595 $this->entityType->expects($this->any())
596 ->method('getRevisionMetadataKeys')
597 ->will($this->returnValue($revision_metadata_field_names));
599 $this->setUpEntityStorage();
601 $mapping = $this->entityStorage->getTableMapping();
603 $expected = ['entity_test', 'entity_test_revision'];
604 $this->assertEquals($expected, $mapping->getTableNames());
606 $this->assertEquals($field_names, $mapping->getFieldNames('entity_test'));
607 $expected = array_merge(
608 [$entity_keys['id'], $entity_keys['revision']],
609 $revisionable_field_names,
610 array_values($revision_metadata_field_names)
612 $this->assertEquals($expected, $mapping->getFieldNames('entity_test_revision'));
614 $this->assertEquals([], $mapping->getExtraColumns('entity_test'));
615 $this->assertEquals([], $mapping->getExtraColumns('entity_test_revision'));
620 * Tests getTableMapping() with a non-revisionable, translatable entity type.
622 * @param string[] $entity_keys
623 * A map of entity keys to use for the mocked entity type.
625 * @covers ::__construct
626 * @covers ::getTableMapping
628 * @dataProvider providerTestGetTableMappingSimple()
630 public function testGetTableMappingTranslatable(array $entity_keys) {
631 // This allows to re-use the data provider.
632 $entity_keys['langcode'] = 'langcode';
634 $this->entityType->expects($this->atLeastOnce())
635 ->method('isTranslatable')
636 ->will($this->returnValue(TRUE));
637 $this->entityType->expects($this->atLeastOnce())
638 ->method('getDataTable')
639 ->will($this->returnValue('entity_test_field_data'));
640 $this->entityType->expects($this->any())
642 ->will($this->returnValueMap([
643 ['id', $entity_keys['id']],
644 ['uuid', $entity_keys['uuid']],
645 ['bundle', $entity_keys['bundle']],
646 ['langcode', $entity_keys['langcode']],
649 $this->setUpEntityStorage();
651 $mapping = $this->entityStorage->getTableMapping();
653 $expected = ['entity_test', 'entity_test_field_data'];
654 $this->assertEquals($expected, $mapping->getTableNames());
656 $expected = array_values(array_filter($entity_keys));
657 $actual = $mapping->getFieldNames('entity_test');
658 $this->assertEquals($expected, $actual);
659 // The UUID is not stored on the data table.
660 $expected = array_values(array_filter([
662 $entity_keys['bundle'],
663 $entity_keys['langcode'],
665 $actual = $mapping->getFieldNames('entity_test_field_data');
666 $this->assertEquals($expected, $actual);
669 $actual = $mapping->getExtraColumns('entity_test');
670 $this->assertEquals($expected, $actual);
671 $actual = $mapping->getExtraColumns('entity_test_field_data');
672 $this->assertEquals($expected, $actual);
676 * Tests getTableMapping() with a translatable entity type with fields.
678 * @param string[] $entity_keys
679 * A map of entity keys to use for the mocked entity type.
681 * @covers ::__construct
682 * @covers ::getTableMapping
684 * @dataProvider providerTestGetTableMappingSimple()
686 public function testGetTableMappingTranslatableWithFields(array $entity_keys) {
687 // This allows to re-use the data provider.
688 $entity_keys['langcode'] = 'langcode';
690 $base_field_names = ['title', 'description', 'owner'];
691 $field_names = array_merge(array_values(array_filter($entity_keys)), $base_field_names);
692 $this->fieldDefinitions = $this->mockFieldDefinitions($field_names);
694 $this->entityType->expects($this->atLeastOnce())
695 ->method('isTranslatable')
696 ->will($this->returnValue(TRUE));
697 $this->entityType->expects($this->atLeastOnce())
698 ->method('getDataTable')
699 ->will($this->returnValue('entity_test_field_data'));
700 $this->entityType->expects($this->any())
702 ->will($this->returnValueMap([
703 ['id', $entity_keys['id']],
704 ['uuid', $entity_keys['uuid']],
705 ['bundle', $entity_keys['bundle']],
706 ['langcode', $entity_keys['langcode']],
709 $this->setUpEntityStorage();
711 $mapping = $this->entityStorage->getTableMapping();
713 $expected = ['entity_test', 'entity_test_field_data'];
714 $this->assertEquals($expected, $mapping->getTableNames());
716 $expected = array_values(array_filter($entity_keys));
717 $actual = $mapping->getFieldNames('entity_test');
718 $this->assertEquals($expected, $actual);
719 // The UUID is not stored on the data table.
720 $expected = array_merge(array_filter([
722 $entity_keys['bundle'],
723 $entity_keys['langcode'],
724 ]), $base_field_names);
725 $actual = $mapping->getFieldNames('entity_test_field_data');
726 $this->assertEquals($expected, $actual);
729 $actual = $mapping->getExtraColumns('entity_test');
730 $this->assertEquals($expected, $actual);
731 $actual = $mapping->getExtraColumns('entity_test_field_data');
732 $this->assertEquals($expected, $actual);
736 * Tests getTableMapping() with a revisionable, translatable entity type.
738 * @param string[] $entity_keys
739 * A map of entity keys to use for the mocked entity type.
741 * @covers ::__construct
742 * @covers ::getTableMapping
744 * @dataProvider providerTestGetTableMappingSimple()
746 public function testGetTableMappingRevisionableTranslatable(array $entity_keys) {
747 // This allows to re-use the data provider.
749 'id' => $entity_keys['id'],
750 'revision' => 'test_revision',
751 'bundle' => $entity_keys['bundle'],
752 'uuid' => $entity_keys['uuid'],
753 'langcode' => 'langcode',
755 $revision_metadata_keys = [
756 'revision_created' => 'revision_timestamp',
757 'revision_user' => 'revision_uid',
758 'revision_log_message' => 'revision_log'
761 $this->entityType->expects($this->atLeastOnce())
762 ->method('isRevisionable')
763 ->will($this->returnValue(TRUE));
764 $this->entityType->expects($this->atLeastOnce())
765 ->method('isTranslatable')
766 ->will($this->returnValue(TRUE));
767 $this->entityType->expects($this->atLeastOnce())
768 ->method('getDataTable')
769 ->will($this->returnValue('entity_test_field_data'));
770 $this->entityType->expects($this->any())
772 ->will($this->returnValueMap([
773 ['id', $entity_keys['id']],
774 ['uuid', $entity_keys['uuid']],
775 ['bundle', $entity_keys['bundle']],
776 ['revision', $entity_keys['revision']],
777 ['langcode', $entity_keys['langcode']],
779 $this->entityType->expects($this->any())
780 ->method('getRevisionMetadataKeys')
781 ->will($this->returnValue($revision_metadata_keys));
783 $this->setUpEntityStorage();
785 $mapping = $this->entityStorage->getTableMapping();
789 'entity_test_field_data',
790 'entity_test_revision',
791 'entity_test_field_revision',
793 $this->assertEquals($expected, $mapping->getTableNames());
795 // The default language code is stored on the base table.
796 $expected = array_values(array_filter([
798 $entity_keys['revision'],
799 $entity_keys['bundle'],
800 $entity_keys['uuid'],
801 $entity_keys['langcode'],
803 $actual = $mapping->getFieldNames('entity_test');
804 $this->assertEquals($expected, $actual);
805 // The revision table on the other hand does not store the bundle and the
807 $expected = array_values(array_filter([
809 $entity_keys['revision'],
810 $entity_keys['langcode'],
812 $expected = array_merge($expected, array_values($revision_metadata_keys));
813 $actual = $mapping->getFieldNames('entity_test_revision');
814 $this->assertEquals($expected, $actual);
815 // The UUID is not stored on the data table.
816 $expected = array_values(array_filter([
818 $entity_keys['revision'],
819 $entity_keys['bundle'],
820 $entity_keys['langcode'],
822 $actual = $mapping->getFieldNames('entity_test_field_data');
823 $this->assertEquals($expected, $actual);
824 // The data revision also does not store the bundle.
825 $expected = array_values(array_filter([
827 $entity_keys['revision'],
828 $entity_keys['langcode'],
830 $actual = $mapping->getFieldNames('entity_test_field_revision');
831 $this->assertEquals($expected, $actual);
834 $actual = $mapping->getExtraColumns('entity_test');
835 $this->assertEquals($expected, $actual);
836 $actual = $mapping->getExtraColumns('entity_test_revision');
837 $this->assertEquals($expected, $actual);
838 $actual = $mapping->getExtraColumns('entity_test_field_data');
839 $this->assertEquals($expected, $actual);
840 $actual = $mapping->getExtraColumns('entity_test_field_revision');
841 $this->assertEquals($expected, $actual);
845 * Tests getTableMapping() with a complex entity type with fields.
847 * @param string[] $entity_keys
848 * A map of entity keys to use for the mocked entity type.
850 * @covers ::__construct
851 * @covers ::getTableMapping
853 * @dataProvider providerTestGetTableMappingSimple()
855 public function testGetTableMappingRevisionableTranslatableWithFields(array $entity_keys) {
856 // This allows to re-use the data provider.
858 'id' => $entity_keys['id'],
859 'revision' => 'test_revision',
860 'bundle' => $entity_keys['bundle'],
861 'uuid' => $entity_keys['uuid'],
862 'langcode' => 'langcode',
865 // PHPUnit does not allow for multiple data providers.
868 ['revision_created' => 'revision_timestamp'],
869 ['revision_user' => 'revision_uid'],
870 ['revision_log_message' => 'revision_log'],
871 ['revision_created' => 'revision_timestamp', 'revision_user' => 'revision_uid'],
872 ['revision_created' => 'revision_timestamp', 'revision_log_message' => 'revision_log'],
873 ['revision_user' => 'revision_uid', 'revision_log_message' => 'revision_log'],
874 ['revision_created' => 'revision_timestamp', 'revision_user' => 'revision_uid', 'revision_log_message' => 'revision_log'],
876 foreach ($test_cases as $revision_metadata_field_names) {
879 $base_field_names = ['title'];
880 $field_names = array_merge(array_values(array_filter($entity_keys)), $base_field_names);
881 $this->fieldDefinitions = $this->mockFieldDefinitions($field_names);
883 $revisionable_field_names = ['description', 'owner'];
884 $this->fieldDefinitions += $this->mockFieldDefinitions(array_merge($revisionable_field_names, array_values($revision_metadata_field_names)), ['isRevisionable' => TRUE]);
886 $this->entityType->expects($this->atLeastOnce())
887 ->method('isRevisionable')
888 ->will($this->returnValue(TRUE));
889 $this->entityType->expects($this->atLeastOnce())
890 ->method('isTranslatable')
891 ->will($this->returnValue(TRUE));
892 $this->entityType->expects($this->atLeastOnce())
893 ->method('getDataTable')
894 ->will($this->returnValue('entity_test_field_data'));
895 $this->entityType->expects($this->any())
897 ->will($this->returnValueMap([
898 ['id', $entity_keys['id']],
899 ['uuid', $entity_keys['uuid']],
900 ['bundle', $entity_keys['bundle']],
901 ['revision', $entity_keys['revision']],
902 ['langcode', $entity_keys['langcode']],
904 $this->entityType->expects($this->any())
905 ->method('getRevisionMetadataKeys')
906 ->will($this->returnValue($revision_metadata_field_names));
908 $this->setUpEntityStorage();
910 $mapping = $this->entityStorage->getTableMapping();
914 'entity_test_field_data',
915 'entity_test_revision',
916 'entity_test_field_revision',
918 $this->assertEquals($expected, $mapping->getTableNames());
922 'entity_test_field_data',
923 'entity_test_revision',
924 'entity_test_field_revision',
926 $this->assertEquals($expected, $mapping->getTableNames());
928 // The default language code is not stored on the base table.
929 $expected = array_values(array_filter([
931 $entity_keys['revision'],
932 $entity_keys['bundle'],
933 $entity_keys['uuid'],
934 $entity_keys['langcode'],
936 $actual = $mapping->getFieldNames('entity_test');
937 $this->assertEquals($expected, $actual);
938 // The revision table on the other hand does not store the bundle and the
940 $expected = array_merge(array_filter([
942 $entity_keys['revision'],
943 $entity_keys['langcode'],
944 ]), array_values($revision_metadata_field_names));
945 $actual = $mapping->getFieldNames('entity_test_revision');
946 $this->assertEquals($expected, $actual);
947 // The UUID is not stored on the data table.
948 $expected = array_merge(array_filter([
950 $entity_keys['revision'],
951 $entity_keys['bundle'],
952 $entity_keys['langcode'],
953 ]), $base_field_names, $revisionable_field_names);
954 $actual = $mapping->getFieldNames('entity_test_field_data');
955 $this->assertEquals($expected, $actual);
956 // The data revision also does not store the bundle.
957 $expected = array_merge(array_filter([
959 $entity_keys['revision'],
960 $entity_keys['langcode'],
961 ]), $revisionable_field_names);
962 $actual = $mapping->getFieldNames('entity_test_field_revision');
963 $this->assertEquals($expected, $actual);
966 $actual = $mapping->getExtraColumns('entity_test');
967 $this->assertEquals($expected, $actual);
968 $actual = $mapping->getExtraColumns('entity_test_revision');
969 $this->assertEquals($expected, $actual);
970 $actual = $mapping->getExtraColumns('entity_test_field_data');
971 $this->assertEquals($expected, $actual);
972 $actual = $mapping->getExtraColumns('entity_test_field_revision');
973 $this->assertEquals($expected, $actual);
980 public function testCreate() {
981 $language_manager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface');
983 $language = new Language(['id' => 'en']);
984 $language_manager->expects($this->any())
985 ->method('getCurrentLanguage')
986 ->will($this->returnValue($language));
988 $this->container->set('language_manager', $language_manager);
989 $this->container->set('entity.manager', $this->entityManager);
990 $this->container->set('module_handler', $this->moduleHandler);
992 $entity = $this->getMockBuilder('Drupal\Core\Entity\ContentEntityBase')
993 ->disableOriginalConstructor()
995 ->getMockForAbstractClass();
997 $this->entityType->expects($this->atLeastOnce())
999 ->will($this->returnValue($this->entityTypeId));
1000 $this->entityType->expects($this->atLeastOnce())
1001 ->method('getClass')
1002 ->will($this->returnValue(get_class($entity)));
1003 $this->entityType->expects($this->atLeastOnce())
1005 ->will($this->returnValue(['id' => 'id']));
1007 // ContentEntityStorageBase iterates over the entity which calls this method
1008 // internally in ContentEntityBase::getProperties().
1009 $this->entityManager->expects($this->once())
1010 ->method('getFieldDefinitions')
1011 ->will($this->returnValue([]));
1013 $this->entityType->expects($this->atLeastOnce())
1014 ->method('isRevisionable')
1015 ->will($this->returnValue(FALSE));
1016 $this->entityManager->expects($this->atLeastOnce())
1017 ->method('getDefinition')
1018 ->with($this->entityType->id())
1019 ->will($this->returnValue($this->entityType));
1021 $this->setUpEntityStorage();
1023 $entity = $this->entityStorage->create();
1024 $entity->expects($this->atLeastOnce())
1026 ->will($this->returnValue('foo'));
1028 $this->assertInstanceOf('Drupal\Core\Entity\EntityInterface', $entity);
1029 $this->assertSame('foo', $entity->id());
1030 $this->assertTrue($entity->isNew());
1034 * Returns a set of mock field definitions for the given names.
1036 * @param array $field_names
1037 * An array of field names.
1038 * @param array $methods
1039 * (optional) An associative array of mock method return values keyed by
1042 * @return \Drupal\Tests\Core\Field\TestBaseFieldDefinitionInterface[]|\PHPUnit_Framework_MockObject_MockObject[]
1043 * An array of mock base field definitions.
1045 protected function mockFieldDefinitions(array $field_names, $methods = []) {
1046 $field_definitions = [];
1047 $definition = $this->getMock('Drupal\Tests\Core\Field\TestBaseFieldDefinitionInterface');
1049 // Assign common method return values.
1051 'isBaseField' => TRUE,
1053 foreach ($methods as $method => $result) {
1055 ->expects($this->any())
1057 ->will($this->returnValue($result));
1060 // Assign field names to mock definitions.
1061 foreach ($field_names as $field_name) {
1062 $field_definitions[$field_name] = clone $definition;
1063 $field_definitions[$field_name]
1064 ->expects($this->any())
1066 ->will($this->returnValue($field_name));
1069 return $field_definitions;
1073 * Sets up the content entity database storage.
1075 protected function setUpEntityStorage() {
1076 $this->connection = $this->getMockBuilder('Drupal\Core\Database\Connection')
1077 ->disableOriginalConstructor()
1080 $this->entityManager->expects($this->any())
1081 ->method('getDefinition')
1082 ->will($this->returnValue($this->entityType));
1084 $this->entityManager->expects($this->any())
1085 ->method('getFieldStorageDefinitions')
1086 ->will($this->returnValue($this->fieldDefinitions));
1088 $this->entityManager->expects($this->any())
1089 ->method('getBaseFieldDefinitions')
1090 ->will($this->returnValue($this->fieldDefinitions));
1092 $this->entityStorage = new SqlContentEntityStorage($this->entityType, $this->connection, $this->entityManager, $this->cache, $this->languageManager);
1096 * @covers ::doLoadMultiple
1097 * @covers ::buildCacheId
1098 * @covers ::getFromPersistentCache
1100 public function testLoadMultiplePersistentCached() {
1101 $this->setUpModuleHandlerNoImplementations();
1103 $key = 'values:' . $this->entityTypeId . ':1';
1105 $entity = $this->getMockBuilder('\Drupal\Tests\Core\Entity\Sql\SqlContentEntityStorageTestEntityInterface')
1106 ->getMockForAbstractClass();
1107 $entity->expects($this->any())
1109 ->will($this->returnValue($id));
1111 $this->entityType->expects($this->atLeastOnce())
1112 ->method('isPersistentlyCacheable')
1113 ->will($this->returnValue(TRUE));
1114 $this->entityType->expects($this->atLeastOnce())
1116 ->will($this->returnValue($this->entityTypeId));
1117 $this->entityType->expects($this->atLeastOnce())
1118 ->method('getClass')
1119 ->will($this->returnValue(get_class($entity)));
1121 $this->cache->expects($this->once())
1122 ->method('getMultiple')
1124 ->will($this->returnValue([$key => (object) ['data' => $entity]]));
1125 $this->cache->expects($this->never())
1128 $this->setUpEntityStorage();
1129 $entities = $this->entityStorage->loadMultiple([$id]);
1130 $this->assertEquals($entity, $entities[$id]);
1134 * @covers ::doLoadMultiple
1135 * @covers ::buildCacheId
1136 * @covers ::getFromPersistentCache
1137 * @covers ::setPersistentCache
1139 public function testLoadMultipleNoPersistentCache() {
1140 $this->setUpModuleHandlerNoImplementations();
1143 $entity = $this->getMockBuilder('\Drupal\Tests\Core\Entity\Sql\SqlContentEntityStorageTestEntityInterface')
1144 ->getMockForAbstractClass();
1145 $entity->expects($this->any())
1147 ->will($this->returnValue($id));
1149 $this->entityType->expects($this->any())
1150 ->method('isPersistentlyCacheable')
1151 ->will($this->returnValue(FALSE));
1152 $this->entityType->expects($this->atLeastOnce())
1154 ->will($this->returnValue($this->entityTypeId));
1155 $this->entityType->expects($this->atLeastOnce())
1156 ->method('getClass')
1157 ->will($this->returnValue(get_class($entity)));
1159 // There should be no calls to the cache backend for an entity type without
1160 // persistent caching.
1161 $this->cache->expects($this->never())
1162 ->method('getMultiple');
1163 $this->cache->expects($this->never())
1166 $entity_storage = $this->getMockBuilder('Drupal\Core\Entity\Sql\SqlContentEntityStorage')
1167 ->setConstructorArgs([$this->entityType, $this->connection, $this->entityManager, $this->cache, $this->languageManager])
1168 ->setMethods(['getFromStorage', 'invokeStorageLoadHook'])
1170 $entity_storage->method('invokeStorageLoadHook')
1172 $entity_storage->expects($this->once())
1173 ->method('getFromStorage')
1175 ->will($this->returnValue([$id => $entity]));
1177 $entities = $entity_storage->loadMultiple([$id]);
1178 $this->assertEquals($entity, $entities[$id]);
1182 * @covers ::doLoadMultiple
1183 * @covers ::buildCacheId
1184 * @covers ::getFromPersistentCache
1185 * @covers ::setPersistentCache
1187 public function testLoadMultiplePersistentCacheMiss() {
1188 $this->setUpModuleHandlerNoImplementations();
1191 $entity = $this->getMockBuilder('\Drupal\Tests\Core\Entity\Sql\SqlContentEntityStorageTestEntityInterface')
1192 ->getMockForAbstractClass();
1193 $entity->expects($this->any())
1195 ->will($this->returnValue($id));
1197 $this->entityType->expects($this->any())
1198 ->method('isPersistentlyCacheable')
1199 ->will($this->returnValue(TRUE));
1200 $this->entityType->expects($this->atLeastOnce())
1202 ->will($this->returnValue($this->entityTypeId));
1203 $this->entityType->expects($this->atLeastOnce())
1204 ->method('getClass')
1205 ->will($this->returnValue(get_class($entity)));
1207 // In case of a cache miss, the entity is loaded from the storage and then
1208 // set in the cache.
1209 $key = 'values:' . $this->entityTypeId . ':1';
1210 $this->cache->expects($this->once())
1211 ->method('getMultiple')
1213 ->will($this->returnValue([]));
1214 $this->cache->expects($this->once())
1216 ->with($key, $entity, CacheBackendInterface::CACHE_PERMANENT, [$this->entityTypeId . '_values', 'entity_field_info']);
1218 $entity_storage = $this->getMockBuilder('Drupal\Core\Entity\Sql\SqlContentEntityStorage')
1219 ->setConstructorArgs([$this->entityType, $this->connection, $this->entityManager, $this->cache, $this->languageManager])
1220 ->setMethods(['getFromStorage', 'invokeStorageLoadHook'])
1222 $entity_storage->method('invokeStorageLoadHook')
1224 $entity_storage->expects($this->once())
1225 ->method('getFromStorage')
1227 ->will($this->returnValue([$id => $entity]));
1229 $entities = $entity_storage->loadMultiple([$id]);
1230 $this->assertEquals($entity, $entities[$id]);
1236 public function testHasData() {
1237 $query = $this->getMock('Drupal\Core\Entity\Query\QueryInterface');
1238 $query->expects(($this->once()))
1239 ->method('accessCheck')
1241 ->willReturn($query);
1242 $query->expects(($this->once()))
1245 ->willReturn($query);
1246 $query->expects(($this->once()))
1250 $factory = $this->getMock(QueryFactoryInterface::class);
1251 $factory->expects($this->once())
1253 ->with($this->entityType, 'AND')
1254 ->willReturn($query);
1256 $this->container->set('entity.query.sql', $factory);
1258 $database = $this->getMockBuilder('Drupal\Core\Database\Connection')
1259 ->disableOriginalConstructor()
1262 $this->entityManager->expects($this->any())
1263 ->method('getDefinition')
1264 ->will($this->returnValue($this->entityType));
1266 $this->entityManager->expects($this->any())
1267 ->method('getFieldStorageDefinitions')
1268 ->will($this->returnValue($this->fieldDefinitions));
1270 $this->entityManager->expects($this->any())
1271 ->method('getBaseFieldDefinitions')
1272 ->will($this->returnValue($this->fieldDefinitions));
1274 $this->entityStorage = new SqlContentEntityStorage($this->entityType, $database, $this->entityManager, $this->cache, $this->languageManager);
1276 $result = $this->entityStorage->hasData();
1278 $this->assertTrue($result, 'hasData returned TRUE');
1282 * Tests entity ID sanitization.
1284 public function testCleanIds() {
1315 $this->fieldDefinitions = $this->mockFieldDefinitions(['id']);
1316 $this->fieldDefinitions['id']->expects($this->any())
1318 ->will($this->returnValue('integer'));
1320 $this->setUpEntityStorage();
1322 $this->entityType->expects($this->any())
1324 ->will($this->returnValueMap(
1328 $method = new \ReflectionMethod($this->entityStorage, 'cleanIds');
1329 $method->setAccessible(TRUE);
1330 $this->assertEquals($valid_ids, $method->invoke($this->entityStorage, $valid_ids));
1346 $this->assertEquals([], $method->invoke($this->entityStorage, $invalid_ids));
1351 * Sets up the module handler with no implementations.
1353 protected function setUpModuleHandlerNoImplementations() {
1354 $this->moduleHandler->expects($this->any())
1355 ->method('getImplementations')
1356 ->will($this->returnValueMap([
1357 ['entity_load', []],
1358 [$this->entityTypeId . '_load', []]
1361 $this->container->set('module_handler', $this->moduleHandler);
1367 * Provides an entity with dummy implementations of static methods, because
1368 * those cannot be mocked.
1370 abstract class SqlContentEntityStorageTestEntityInterface implements EntityInterface {
1375 public static function postLoad(EntityStorageInterface $storage, array &$entities) {