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\Cache\MemoryCache\MemoryCache;
12 use Drupal\Core\Entity\EntityFieldManagerInterface;
13 use Drupal\Core\Entity\EntityInterface;
14 use Drupal\Core\Entity\EntityManager;
15 use Drupal\Core\Entity\EntityStorageInterface;
16 use Drupal\Core\Entity\EntityTypeManagerInterface;
17 use Drupal\Core\Entity\Query\QueryFactoryInterface;
18 use Drupal\Core\Entity\Sql\DefaultTableMapping;
19 use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
20 use Drupal\Core\Language\Language;
21 use Drupal\Tests\UnitTestCase;
22 use Symfony\Component\DependencyInjection\ContainerBuilder;
25 * @coversDefaultClass \Drupal\Core\Entity\Sql\SqlContentEntityStorage
28 class SqlContentEntityStorageTest extends UnitTestCase {
31 * The content entity database storage used in this test.
33 * @var \Drupal\Core\Entity\Sql\SqlContentEntityStorage|\PHPUnit_Framework_MockObject_MockObject
35 protected $entityStorage;
38 * The mocked entity type used in this test.
40 * @var \Drupal\Core\Entity\ContentEntityTypeInterface|\PHPUnit_Framework_MockObject_MockObject
42 protected $entityType;
45 * An array of field definitions used for this test, keyed by field name.
47 * @var \Drupal\Core\Field\BaseFieldDefinition[]|\PHPUnit_Framework_MockObject_MockObject[]
49 protected $fieldDefinitions = [];
52 * The mocked entity manager used in this test.
54 * @var \Drupal\Core\Entity\EntityManagerInterface|\PHPUnit_Framework_MockObject_MockObject
56 protected $entityManager;
59 * The mocked entity type manager used in this test.
61 * @var \Drupal\Core\Entity\EntityTypeManagerInterface|\PHPUnit_Framework_MockObject_MockObject
63 protected $entityTypeManager;
66 * The mocked entity field manager used in this test.
68 * @var \Drupal\Core\Entity\EntityFieldManagerInterface|\PHPUnit_Framework_MockObject_MockObject
70 protected $entityFieldManager;
77 protected $entityTypeId = 'entity_test';
80 * The dependency injection container.
82 * @var \Symfony\Component\DependencyInjection\ContainerBuilder
89 * @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
91 protected $moduleHandler;
94 * The cache backend to use.
96 * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject
101 * The language manager.
103 * @var \Drupal\Core\Language\LanguageManagerInterface|\PHPUnit_Framework_MockObject_MockObject
105 protected $languageManager;
108 * The database connection to use.
110 * @var \Drupal\Core\Database\Connection|\PHPUnit_Framework_MockObject_MockObject
112 protected $connection;
117 protected function setUp() {
118 $this->entityType = $this->getMock('Drupal\Core\Entity\ContentEntityTypeInterface');
119 $this->entityType->expects($this->any())
121 ->will($this->returnValue($this->entityTypeId));
123 $this->container = new ContainerBuilder();
124 \Drupal::setContainer($this->container);
126 $this->entityManager = new EntityManager();
127 // Inject the container into entity.manager so it can defer to
128 // entity_type.manager and other services.
129 $this->entityManager->setContainer($this->container);
130 $this->entityTypeManager = $this->getMock(EntityTypeManagerInterface::class);
131 $this->entityFieldManager = $this->getMock(EntityFieldManagerInterface::class);
132 $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
133 $this->cache = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
134 $this->languageManager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface');
135 $this->languageManager->expects($this->any())
136 ->method('getDefaultLanguage')
137 ->will($this->returnValue(new Language(['langcode' => 'en'])));
138 $this->connection = $this->getMockBuilder('Drupal\Core\Database\Connection')
139 ->disableOriginalConstructor()
142 $this->container->set('entity.manager', $this->entityManager);
143 $this->container->set('entity_type.manager', $this->entityTypeManager);
144 $this->container->set('entity_field.manager', $this->entityFieldManager);
148 * Tests SqlContentEntityStorage::getBaseTable().
150 * @param string $base_table
151 * The base table to be returned by the mocked entity type.
152 * @param string $expected
153 * The expected return value of
154 * SqlContentEntityStorage::getBaseTable().
156 * @covers ::__construct
157 * @covers ::getBaseTable
159 * @dataProvider providerTestGetBaseTable
161 public function testGetBaseTable($base_table, $expected) {
162 $this->entityType->expects($this->once())
163 ->method('getBaseTable')
164 ->willReturn($base_table);
166 $this->setUpEntityStorage();
168 $this->assertSame($expected, $this->entityStorage->getBaseTable());
172 * Provides test data for testGetBaseTable().
175 * An nested array where each inner array has the base table to be returned
176 * by the mocked entity type as the first value and the expected return
177 * value of SqlContentEntityStorage::getBaseTable() as the second
180 public function providerTestGetBaseTable() {
182 // Test that the entity type's base table is used, if provided.
183 ['entity_test', 'entity_test'],
184 // Test that the storage falls back to the entity type ID.
185 [NULL, 'entity_test'],
190 * Tests SqlContentEntityStorage::getRevisionTable().
192 * @param string $revision_table
193 * The revision table to be returned by the mocked entity type.
194 * @param string $expected
195 * The expected return value of
196 * SqlContentEntityStorage::getRevisionTable().
198 * @covers ::__construct
199 * @covers ::getRevisionTable
201 * @dataProvider providerTestGetRevisionTable
203 public function testGetRevisionTable($revision_table, $expected) {
204 $this->entityType->expects($this->any())
205 ->method('isRevisionable')
206 ->will($this->returnValue(TRUE));
207 $this->entityType->expects($this->once())
208 ->method('getRevisionTable')
209 ->will($this->returnValue($revision_table));
210 $this->entityType->expects($this->any())
211 ->method('getRevisionMetadataKeys')
214 $this->setUpEntityStorage();
216 $this->assertSame($expected, $this->entityStorage->getRevisionTable());
220 * Provides test data for testGetRevisionTable().
223 * An nested array where each inner array has the revision table to be
224 * returned by the mocked entity type as the first value and the expected
225 * return value of SqlContentEntityStorage::getRevisionTable() as the
228 public function providerTestGetRevisionTable() {
230 // Test that the entity type's revision table is used, if provided.
231 ['entity_test_revision', 'entity_test_revision'],
232 // Test that the storage falls back to the entity type ID with a
233 // '_revision' suffix.
234 [NULL, 'entity_test_revision'],
239 * Tests SqlContentEntityStorage::getDataTable().
241 * @covers ::__construct
242 * @covers ::getDataTable
244 public function testGetDataTable() {
245 $this->entityType->expects($this->any())
246 ->method('isTranslatable')
247 ->will($this->returnValue(TRUE));
248 $this->entityType->expects($this->exactly(1))
249 ->method('getDataTable')
250 ->will($this->returnValue('entity_test_field_data'));
251 $this->entityType->expects($this->any())
252 ->method('getRevisionMetadataKeys')
255 $this->setUpEntityStorage();
257 $this->assertSame('entity_test_field_data', $this->entityStorage->getDataTable());
261 * Tests SqlContentEntityStorage::getRevisionDataTable().
263 * @param string $revision_data_table
264 * The revision data table to be returned by the mocked entity type.
265 * @param string $expected
266 * The expected return value of
267 * SqlContentEntityStorage::getRevisionDataTable().
269 * @covers ::__construct
270 * @covers ::getRevisionDataTable
272 * @dataProvider providerTestGetRevisionDataTable
274 public function testGetRevisionDataTable($revision_data_table, $expected) {
275 $this->entityType->expects($this->any())
276 ->method('isRevisionable')
277 ->will($this->returnValue(TRUE));
278 $this->entityType->expects($this->any())
279 ->method('isTranslatable')
280 ->will($this->returnValue(TRUE));
281 $this->entityType->expects($this->exactly(1))
282 ->method('getDataTable')
283 ->will($this->returnValue('entity_test_field_data'));
284 $this->entityType->expects($this->once())
285 ->method('getRevisionDataTable')
286 ->will($this->returnValue($revision_data_table));
287 $this->entityType->expects($this->any())
288 ->method('getRevisionMetadataKeys')
291 $this->setUpEntityStorage();
293 $actual = $this->entityStorage->getRevisionDataTable();
294 $this->assertSame($expected, $actual);
298 * Provides test data for testGetRevisionDataTable().
301 * An nested array where each inner array has the revision data table to be
302 * returned by the mocked entity type as the first value and the expected
303 * return value of SqlContentEntityStorage::getRevisionDataTable() as
306 public function providerTestGetRevisionDataTable() {
308 // Test that the entity type's revision data table is used, if provided.
309 ['entity_test_field_revision', 'entity_test_field_revision'],
310 // Test that the storage falls back to the entity type ID with a
311 // '_field_revision' suffix.
312 [NULL, 'entity_test_field_revision'],
317 * Tests that setting a new table mapping also updates the table names.
319 * @covers ::setTableMapping
321 public function testSetTableMapping() {
322 $this->entityType->expects($this->any())
323 ->method('isRevisionable')
324 ->will($this->returnValue(FALSE));
325 $this->entityType->expects($this->any())
326 ->method('isTranslatable')
327 ->will($this->returnValue(FALSE));
328 $this->entityType->expects($this->any())
329 ->method('getRevisionMetadataKeys')
332 $this->setUpEntityStorage();
334 $this->assertSame('entity_test', $this->entityStorage->getBaseTable());
335 $this->assertNull($this->entityStorage->getRevisionTable());
336 $this->assertNull($this->entityStorage->getDataTable());
337 $this->assertNull($this->entityStorage->getRevisionDataTable());
339 // Change the entity type definition and instantiate a new table mapping
341 $updated_entity_type = $this->createMock('Drupal\Core\Entity\ContentEntityTypeInterface');
342 $updated_entity_type->expects($this->any())
344 ->will($this->returnValue($this->entityTypeId));
345 $updated_entity_type->expects($this->any())
346 ->method('isRevisionable')
347 ->will($this->returnValue(TRUE));
348 $updated_entity_type->expects($this->any())
349 ->method('isTranslatable')
350 ->will($this->returnValue(TRUE));
352 $table_mapping = new DefaultTableMapping($updated_entity_type, []);
353 $this->entityStorage->setTableMapping($table_mapping);
355 $this->assertSame('entity_test', $this->entityStorage->getBaseTable());
356 $this->assertSame('entity_test_revision', $this->entityStorage->getRevisionTable());
357 $this->assertSame('entity_test_field_data', $this->entityStorage->getDataTable());
358 $this->assertSame('entity_test_field_revision', $this->entityStorage->getRevisionDataTable());
362 * Tests ContentEntityDatabaseStorage::onEntityTypeCreate().
364 * @covers ::__construct
365 * @covers ::onEntityTypeCreate
366 * @covers ::getTableMapping
368 public function testOnEntityTypeCreate() {
375 $this->fieldDefinitions = $this->mockFieldDefinitions(['id']);
376 $this->fieldDefinitions['id']->expects($this->any())
377 ->method('getColumns')
378 ->will($this->returnValue($columns));
379 $this->fieldDefinitions['id']->expects($this->once())
380 ->method('getSchema')
381 ->will($this->returnValue(['columns' => $columns]));
383 $this->entityType->expects($this->once())
385 ->will($this->returnValue(['id' => 'id']));
386 $this->entityType->expects($this->any())
388 ->will($this->returnValueMap([
389 // SqlContentEntityStorageSchema::initializeBaseTable()
391 // SqlContentEntityStorageSchema::processBaseTable()
394 $this->entityType->expects($this->any())
396 ->will($this->returnValueMap([
397 // EntityStorageBase::__construct()
399 // ContentEntityStorageBase::__construct()
402 // SqlContentEntityStorageSchema::initializeBaseTable()
404 // SqlContentEntityStorageSchema::processBaseTable()
408 $this->setUpEntityStorage();
411 'description' => 'The base table for entity_test entities.',
418 'primary key' => ['id'],
421 'foreign keys' => [],
424 $schema_handler = $this->getMockBuilder('Drupal\Core\Database\Schema')
425 ->disableOriginalConstructor()
427 $schema_handler->expects($this->any())
428 ->method('createTable')
429 ->with($this->equalTo('entity_test'), $this->equalTo($expected));
431 $this->connection->expects($this->once())
433 ->will($this->returnValue($schema_handler));
435 $storage = $this->getMockBuilder('Drupal\Core\Entity\Sql\SqlContentEntityStorage')
436 ->setConstructorArgs([$this->entityType, $this->connection, $this->entityManager, $this->cache, $this->languageManager, new MemoryCache()])
437 ->setMethods(['getStorageSchema'])
440 $key_value = $this->getMock('Drupal\Core\KeyValueStore\KeyValueStoreInterface');
441 $schema_handler = $this->getMockBuilder('Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema')
442 ->setConstructorArgs([$this->entityManager, $this->entityType, $storage, $this->connection])
443 ->setMethods(['installedStorageSchema', 'createSharedTableSchema'])
446 ->expects($this->any())
447 ->method('installedStorageSchema')
448 ->will($this->returnValue($key_value));
451 ->expects($this->any())
452 ->method('getStorageSchema')
453 ->will($this->returnValue($schema_handler));
455 $storage->onEntityTypeCreate($this->entityType);
459 * Tests getTableMapping() with an empty entity type.
461 * @covers ::__construct
462 * @covers ::getTableMapping
464 public function testGetTableMappingEmpty() {
465 $this->setUpEntityStorage();
467 $mapping = $this->entityStorage->getTableMapping();
468 $this->assertSame(['entity_test'], $mapping->getTableNames());
469 $this->assertSame([], $mapping->getFieldNames('entity_test'));
470 $this->assertSame([], $mapping->getExtraColumns('entity_test'));
474 * Tests getTableMapping() with a simple entity type.
476 * @param string[] $entity_keys
477 * A map of entity keys to use for the mocked entity type.
479 * @covers ::__construct
480 * @covers ::getTableMapping
482 * @dataProvider providerTestGetTableMappingSimple()
484 public function testGetTableMappingSimple(array $entity_keys) {
485 $this->entityType->expects($this->any())
487 ->will($this->returnValueMap([
488 ['id', $entity_keys['id']],
489 ['uuid', $entity_keys['uuid']],
490 ['bundle', $entity_keys['bundle']],
493 $this->setUpEntityStorage();
495 $mapping = $this->entityStorage->getTableMapping();
497 $this->assertEquals(['entity_test'], $mapping->getTableNames());
499 $expected = array_values(array_filter($entity_keys));
500 $this->assertEquals($expected, $mapping->getFieldNames('entity_test'));
502 $this->assertEquals([], $mapping->getExtraColumns('entity_test'));
506 * Tests getTableMapping() with a simple entity type with some base fields.
508 * @param string[] $entity_keys
509 * A map of entity keys to use for the mocked entity type.
511 * @covers ::__construct
512 * @covers ::getTableMapping
514 * @dataProvider providerTestGetTableMappingSimple()
516 public function testGetTableMappingSimpleWithFields(array $entity_keys) {
517 $base_field_names = ['title', 'description', 'owner'];
518 $field_names = array_merge(array_values(array_filter($entity_keys)), $base_field_names);
519 $this->fieldDefinitions = $this->mockFieldDefinitions($field_names);
520 $this->setUpEntityStorage();
522 $mapping = $this->entityStorage->getTableMapping();
523 $this->assertEquals(['entity_test'], $mapping->getTableNames());
524 $this->assertEquals($field_names, $mapping->getFieldNames('entity_test'));
525 $this->assertEquals([], $mapping->getExtraColumns('entity_test'));
529 * Provides test data for testGetTableMappingSimple().
532 * A nested array, where each inner array has a single value being a map of
533 * entity keys to use for the mocked entity type.
535 public function providerTestGetTableMappingSimple() {
537 [['id' => 'test_id', 'bundle' => NULL, 'uuid' => NULL]],
538 [['id' => 'test_id', 'bundle' => 'test_bundle', 'uuid' => NULL]],
539 [['id' => 'test_id', 'bundle' => NULL, 'uuid' => 'test_uuid']],
540 [['id' => 'test_id', 'bundle' => 'test_bundle', 'uuid' => 'test_uuid']],
545 * Tests getTableMapping() with a base field that requires a dedicated table.
547 * @covers ::__construct
548 * @covers ::getTableMapping
550 public function testGetTableMappingSimpleWithDedicatedStorageFields() {
551 $base_field_names = ['multi_valued_base_field'];
553 // Set up one entity key in order to have a base table.
554 $this->fieldDefinitions = $this->mockFieldDefinitions(['test_id']);
556 // Set up the multi-valued base field.
557 $this->fieldDefinitions += $this->mockFieldDefinitions($base_field_names, [
558 'hasCustomStorage' => FALSE,
559 'isMultiple' => TRUE,
560 'getTargetEntityTypeId' => 'entity_test',
563 $this->setUpEntityStorage();
565 $mapping = $this->entityStorage->getTableMapping();
566 $this->assertEquals(['entity_test', 'entity_test__multi_valued_base_field'], $mapping->getTableNames());
567 $this->assertEquals($base_field_names, $mapping->getFieldNames('entity_test__multi_valued_base_field'));
577 $this->assertEquals($extra_columns, $mapping->getExtraColumns('entity_test__multi_valued_base_field'));
581 * Tests getTableMapping() with a revisionable, non-translatable entity type.
583 * @param string[] $entity_keys
584 * A map of entity keys to use for the mocked entity type.
586 * @covers ::__construct
587 * @covers ::getTableMapping
589 * @dataProvider providerTestGetTableMappingSimple()
591 public function testGetTableMappingRevisionable(array $entity_keys) {
592 // This allows to re-use the data provider.
594 'id' => $entity_keys['id'],
595 'revision' => 'test_revision',
596 'bundle' => $entity_keys['bundle'],
597 'uuid' => $entity_keys['uuid'],
600 $this->entityType->expects($this->exactly(4))
601 ->method('isRevisionable')
602 ->will($this->returnValue(TRUE));
603 $this->entityType->expects($this->any())
605 ->will($this->returnValueMap([
606 ['id', $entity_keys['id']],
607 ['uuid', $entity_keys['uuid']],
608 ['bundle', $entity_keys['bundle']],
609 ['revision', $entity_keys['revision']],
611 $this->entityType->expects($this->any())
612 ->method('getRevisionMetadataKeys')
613 ->will($this->returnValue([]));
615 $this->setUpEntityStorage();
617 $mapping = $this->entityStorage->getTableMapping();
619 $expected = ['entity_test', 'entity_test_revision'];
620 $this->assertEquals($expected, $mapping->getTableNames());
622 $expected = array_values(array_filter($entity_keys));
623 $this->assertEquals($expected, $mapping->getFieldNames('entity_test'));
624 $expected = [$entity_keys['id'], $entity_keys['revision']];
625 $this->assertEquals($expected, $mapping->getFieldNames('entity_test_revision'));
627 $this->assertEquals([], $mapping->getExtraColumns('entity_test'));
628 $this->assertEquals([], $mapping->getExtraColumns('entity_test_revision'));
632 * Tests getTableMapping() with a revisionable entity type with fields.
634 * @param string[] $entity_keys
635 * A map of entity keys to use for the mocked entity type.
637 * @covers ::__construct
638 * @covers ::getTableMapping
640 * @dataProvider providerTestGetTableMappingSimple()
642 public function testGetTableMappingRevisionableWithFields(array $entity_keys) {
643 // This allows to re-use the data provider.
645 'id' => $entity_keys['id'],
646 'revision' => 'test_revision',
647 'bundle' => $entity_keys['bundle'],
648 'uuid' => $entity_keys['uuid'],
651 // PHPUnit does not allow for multiple data providers.
654 ['revision_created' => 'revision_timestamp'],
655 ['revision_user' => 'revision_uid'],
656 ['revision_log_message' => 'revision_log'],
657 ['revision_created' => 'revision_timestamp', 'revision_user' => 'revision_uid'],
658 ['revision_created' => 'revision_timestamp', 'revision_log_message' => 'revision_log'],
659 ['revision_user' => 'revision_uid', 'revision_log_message' => 'revision_log'],
660 ['revision_created' => 'revision_timestamp', 'revision_user' => 'revision_uid', 'revision_log_message' => 'revision_log'],
662 foreach ($test_cases as $revision_metadata_field_names) {
665 $base_field_names = ['title'];
666 $field_names = array_merge(array_values(array_filter($entity_keys)), $base_field_names);
667 $this->fieldDefinitions = $this->mockFieldDefinitions($field_names);
669 $revisionable_field_names = ['description', 'owner'];
670 $field_names = array_merge($field_names, $revisionable_field_names);
671 $this->fieldDefinitions += $this->mockFieldDefinitions(array_merge($revisionable_field_names, array_values($revision_metadata_field_names)), ['isRevisionable' => TRUE]);
673 $this->entityType->expects($this->exactly(4))
674 ->method('isRevisionable')
675 ->will($this->returnValue(TRUE));
676 $this->entityType->expects($this->any())
678 ->will($this->returnValueMap([
679 ['id', $entity_keys['id']],
680 ['uuid', $entity_keys['uuid']],
681 ['bundle', $entity_keys['bundle']],
682 ['revision', $entity_keys['revision']],
685 $this->entityType->expects($this->any())
686 ->method('getRevisionMetadataKeys')
687 ->will($this->returnValue($revision_metadata_field_names));
689 $this->setUpEntityStorage();
691 $mapping = $this->entityStorage->getTableMapping();
693 $expected = ['entity_test', 'entity_test_revision'];
694 $this->assertEquals($expected, $mapping->getTableNames());
696 $this->assertEquals($field_names, $mapping->getFieldNames('entity_test'));
697 $expected = array_merge(
698 [$entity_keys['id'], $entity_keys['revision']],
699 $revisionable_field_names,
700 array_values($revision_metadata_field_names)
702 $this->assertEquals($expected, $mapping->getFieldNames('entity_test_revision'));
704 $this->assertEquals([], $mapping->getExtraColumns('entity_test'));
705 $this->assertEquals([], $mapping->getExtraColumns('entity_test_revision'));
710 * Tests getTableMapping() with a non-revisionable, translatable entity type.
712 * @param string[] $entity_keys
713 * A map of entity keys to use for the mocked entity type.
715 * @covers ::__construct
716 * @covers ::getTableMapping
718 * @dataProvider providerTestGetTableMappingSimple()
720 public function testGetTableMappingTranslatable(array $entity_keys) {
721 // This allows to re-use the data provider.
722 $entity_keys['langcode'] = 'langcode';
724 $this->entityType->expects($this->atLeastOnce())
725 ->method('isTranslatable')
726 ->will($this->returnValue(TRUE));
727 $this->entityType->expects($this->atLeastOnce())
728 ->method('getDataTable')
729 ->will($this->returnValue('entity_test_field_data'));
730 $this->entityType->expects($this->any())
732 ->will($this->returnValueMap([
733 ['id', $entity_keys['id']],
734 ['uuid', $entity_keys['uuid']],
735 ['bundle', $entity_keys['bundle']],
736 ['langcode', $entity_keys['langcode']],
739 $this->setUpEntityStorage();
741 $mapping = $this->entityStorage->getTableMapping();
743 $expected = ['entity_test', 'entity_test_field_data'];
744 $this->assertEquals($expected, $mapping->getTableNames());
746 $expected = array_values(array_filter($entity_keys));
747 $actual = $mapping->getFieldNames('entity_test');
748 $this->assertEquals($expected, $actual);
749 // The UUID is not stored on the data table.
750 $expected = array_values(array_filter([
752 $entity_keys['bundle'],
753 $entity_keys['langcode'],
755 $actual = $mapping->getFieldNames('entity_test_field_data');
756 $this->assertEquals($expected, $actual);
759 $actual = $mapping->getExtraColumns('entity_test');
760 $this->assertEquals($expected, $actual);
761 $actual = $mapping->getExtraColumns('entity_test_field_data');
762 $this->assertEquals($expected, $actual);
766 * Tests getTableMapping() with a translatable entity type with fields.
768 * @param string[] $entity_keys
769 * A map of entity keys to use for the mocked entity type.
771 * @covers ::__construct
772 * @covers ::getTableMapping
774 * @dataProvider providerTestGetTableMappingSimple()
776 public function testGetTableMappingTranslatableWithFields(array $entity_keys) {
777 // This allows to re-use the data provider.
778 $entity_keys['langcode'] = 'langcode';
780 $base_field_names = ['title', 'description', 'owner'];
781 $field_names = array_merge(array_values(array_filter($entity_keys)), $base_field_names);
782 $this->fieldDefinitions = $this->mockFieldDefinitions($field_names);
784 $this->entityType->expects($this->atLeastOnce())
785 ->method('isTranslatable')
786 ->will($this->returnValue(TRUE));
787 $this->entityType->expects($this->atLeastOnce())
788 ->method('getDataTable')
789 ->will($this->returnValue('entity_test_field_data'));
790 $this->entityType->expects($this->any())
792 ->will($this->returnValueMap([
793 ['id', $entity_keys['id']],
794 ['uuid', $entity_keys['uuid']],
795 ['bundle', $entity_keys['bundle']],
796 ['langcode', $entity_keys['langcode']],
799 $this->setUpEntityStorage();
801 $mapping = $this->entityStorage->getTableMapping();
803 $expected = ['entity_test', 'entity_test_field_data'];
804 $this->assertEquals($expected, $mapping->getTableNames());
806 $expected = array_values(array_filter($entity_keys));
807 $actual = $mapping->getFieldNames('entity_test');
808 $this->assertEquals($expected, $actual);
809 // The UUID is not stored on the data table.
810 $expected = array_merge(array_filter([
812 $entity_keys['bundle'],
813 $entity_keys['langcode'],
814 ]), $base_field_names);
815 $actual = $mapping->getFieldNames('entity_test_field_data');
816 $this->assertEquals($expected, $actual);
819 $actual = $mapping->getExtraColumns('entity_test');
820 $this->assertEquals($expected, $actual);
821 $actual = $mapping->getExtraColumns('entity_test_field_data');
822 $this->assertEquals($expected, $actual);
826 * Tests getTableMapping() with a revisionable, translatable entity type.
828 * @param string[] $entity_keys
829 * A map of entity keys to use for the mocked entity type.
831 * @covers ::__construct
832 * @covers ::getTableMapping
834 * @dataProvider providerTestGetTableMappingSimple()
836 public function testGetTableMappingRevisionableTranslatable(array $entity_keys) {
837 // This allows to re-use the data provider.
839 'id' => $entity_keys['id'],
840 'revision' => 'test_revision',
841 'bundle' => $entity_keys['bundle'],
842 'uuid' => $entity_keys['uuid'],
843 'langcode' => 'langcode',
845 $revision_metadata_keys = [
846 'revision_created' => 'revision_timestamp',
847 'revision_user' => 'revision_uid',
848 'revision_log_message' => 'revision_log',
851 $this->entityType->expects($this->atLeastOnce())
852 ->method('isRevisionable')
853 ->will($this->returnValue(TRUE));
854 $this->entityType->expects($this->atLeastOnce())
855 ->method('isTranslatable')
856 ->will($this->returnValue(TRUE));
857 $this->entityType->expects($this->atLeastOnce())
858 ->method('getDataTable')
859 ->will($this->returnValue('entity_test_field_data'));
860 $this->entityType->expects($this->any())
862 ->will($this->returnValueMap([
863 ['id', $entity_keys['id']],
864 ['uuid', $entity_keys['uuid']],
865 ['bundle', $entity_keys['bundle']],
866 ['revision', $entity_keys['revision']],
867 ['langcode', $entity_keys['langcode']],
869 $this->entityType->expects($this->any())
870 ->method('getRevisionMetadataKeys')
871 ->will($this->returnValue($revision_metadata_keys));
873 $this->setUpEntityStorage();
875 $mapping = $this->entityStorage->getTableMapping();
879 'entity_test_field_data',
880 'entity_test_revision',
881 'entity_test_field_revision',
883 $this->assertEquals($expected, $mapping->getTableNames());
885 // The default language code is stored on the base table.
886 $expected = array_values(array_filter([
888 $entity_keys['revision'],
889 $entity_keys['bundle'],
890 $entity_keys['uuid'],
891 $entity_keys['langcode'],
893 $actual = $mapping->getFieldNames('entity_test');
894 $this->assertEquals($expected, $actual);
895 // The revision table on the other hand does not store the bundle and the
897 $expected = array_values(array_filter([
899 $entity_keys['revision'],
900 $entity_keys['langcode'],
902 $expected = array_merge($expected, array_values($revision_metadata_keys));
903 $actual = $mapping->getFieldNames('entity_test_revision');
904 $this->assertEquals($expected, $actual);
905 // The UUID is not stored on the data table.
906 $expected = array_values(array_filter([
908 $entity_keys['revision'],
909 $entity_keys['bundle'],
910 $entity_keys['langcode'],
912 $actual = $mapping->getFieldNames('entity_test_field_data');
913 $this->assertEquals($expected, $actual);
914 // The data revision also does not store the bundle.
915 $expected = array_values(array_filter([
917 $entity_keys['revision'],
918 $entity_keys['langcode'],
920 $actual = $mapping->getFieldNames('entity_test_field_revision');
921 $this->assertEquals($expected, $actual);
924 $actual = $mapping->getExtraColumns('entity_test');
925 $this->assertEquals($expected, $actual);
926 $actual = $mapping->getExtraColumns('entity_test_revision');
927 $this->assertEquals($expected, $actual);
928 $actual = $mapping->getExtraColumns('entity_test_field_data');
929 $this->assertEquals($expected, $actual);
930 $actual = $mapping->getExtraColumns('entity_test_field_revision');
931 $this->assertEquals($expected, $actual);
935 * Tests getTableMapping() with a complex entity type with fields.
937 * @param string[] $entity_keys
938 * A map of entity keys to use for the mocked entity type.
940 * @covers ::__construct
941 * @covers ::getTableMapping
943 * @dataProvider providerTestGetTableMappingSimple()
945 public function testGetTableMappingRevisionableTranslatableWithFields(array $entity_keys) {
946 // This allows to re-use the data provider.
948 'id' => $entity_keys['id'],
949 'revision' => 'test_revision',
950 'bundle' => $entity_keys['bundle'],
951 'uuid' => $entity_keys['uuid'],
952 'langcode' => 'langcode',
955 // PHPUnit does not allow for multiple data providers.
958 ['revision_created' => 'revision_timestamp'],
959 ['revision_user' => 'revision_uid'],
960 ['revision_log_message' => 'revision_log'],
961 ['revision_created' => 'revision_timestamp', 'revision_user' => 'revision_uid'],
962 ['revision_created' => 'revision_timestamp', 'revision_log_message' => 'revision_log'],
963 ['revision_user' => 'revision_uid', 'revision_log_message' => 'revision_log'],
964 ['revision_created' => 'revision_timestamp', 'revision_user' => 'revision_uid', 'revision_log_message' => 'revision_log'],
966 foreach ($test_cases as $revision_metadata_field_names) {
969 $base_field_names = ['title'];
970 $field_names = array_merge(array_values(array_filter($entity_keys)), $base_field_names);
971 $this->fieldDefinitions = $this->mockFieldDefinitions($field_names);
973 $revisionable_field_names = ['description', 'owner'];
974 $this->fieldDefinitions += $this->mockFieldDefinitions(array_merge($revisionable_field_names, array_values($revision_metadata_field_names)), ['isRevisionable' => TRUE]);
976 $this->entityType->expects($this->atLeastOnce())
977 ->method('isRevisionable')
978 ->will($this->returnValue(TRUE));
979 $this->entityType->expects($this->atLeastOnce())
980 ->method('isTranslatable')
981 ->will($this->returnValue(TRUE));
982 $this->entityType->expects($this->atLeastOnce())
983 ->method('getDataTable')
984 ->will($this->returnValue('entity_test_field_data'));
985 $this->entityType->expects($this->any())
987 ->will($this->returnValueMap([
988 ['id', $entity_keys['id']],
989 ['uuid', $entity_keys['uuid']],
990 ['bundle', $entity_keys['bundle']],
991 ['revision', $entity_keys['revision']],
992 ['langcode', $entity_keys['langcode']],
994 $this->entityType->expects($this->any())
995 ->method('getRevisionMetadataKeys')
996 ->will($this->returnValue($revision_metadata_field_names));
998 $this->setUpEntityStorage();
1000 $mapping = $this->entityStorage->getTableMapping();
1004 'entity_test_field_data',
1005 'entity_test_revision',
1006 'entity_test_field_revision',
1008 $this->assertEquals($expected, $mapping->getTableNames());
1012 'entity_test_field_data',
1013 'entity_test_revision',
1014 'entity_test_field_revision',
1016 $this->assertEquals($expected, $mapping->getTableNames());
1018 // The default language code is not stored on the base table.
1019 $expected = array_values(array_filter([
1021 $entity_keys['revision'],
1022 $entity_keys['bundle'],
1023 $entity_keys['uuid'],
1024 $entity_keys['langcode'],
1026 $actual = $mapping->getFieldNames('entity_test');
1027 $this->assertEquals($expected, $actual);
1028 // The revision table on the other hand does not store the bundle and the
1030 $expected = array_merge(array_filter([
1032 $entity_keys['revision'],
1033 $entity_keys['langcode'],
1034 ]), array_values($revision_metadata_field_names));
1035 $actual = $mapping->getFieldNames('entity_test_revision');
1036 $this->assertEquals($expected, $actual);
1037 // The UUID is not stored on the data table.
1038 $expected = array_merge(array_filter([
1040 $entity_keys['revision'],
1041 $entity_keys['bundle'],
1042 $entity_keys['langcode'],
1043 ]), $base_field_names, $revisionable_field_names);
1044 $actual = $mapping->getFieldNames('entity_test_field_data');
1045 $this->assertEquals($expected, $actual);
1046 // The data revision also does not store the bundle.
1047 $expected = array_merge(array_filter([
1049 $entity_keys['revision'],
1050 $entity_keys['langcode'],
1051 ]), $revisionable_field_names);
1052 $actual = $mapping->getFieldNames('entity_test_field_revision');
1053 $this->assertEquals($expected, $actual);
1056 $actual = $mapping->getExtraColumns('entity_test');
1057 $this->assertEquals($expected, $actual);
1058 $actual = $mapping->getExtraColumns('entity_test_revision');
1059 $this->assertEquals($expected, $actual);
1060 $actual = $mapping->getExtraColumns('entity_test_field_data');
1061 $this->assertEquals($expected, $actual);
1062 $actual = $mapping->getExtraColumns('entity_test_field_revision');
1063 $this->assertEquals($expected, $actual);
1070 public function testCreate() {
1071 $language_manager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface');
1073 $language = new Language(['id' => 'en']);
1074 $language_manager->expects($this->any())
1075 ->method('getCurrentLanguage')
1076 ->will($this->returnValue($language));
1078 $this->container->set('language_manager', $language_manager);
1079 $this->container->set('module_handler', $this->moduleHandler);
1081 $entity = $this->getMockBuilder('Drupal\Core\Entity\ContentEntityBase')
1082 ->disableOriginalConstructor()
1083 ->setMethods(['id'])
1084 ->getMockForAbstractClass();
1086 $this->entityType->expects($this->atLeastOnce())
1088 ->will($this->returnValue($this->entityTypeId));
1089 $this->entityType->expects($this->atLeastOnce())
1090 ->method('getClass')
1091 ->will($this->returnValue(get_class($entity)));
1092 $this->entityType->expects($this->atLeastOnce())
1094 ->will($this->returnValue(['id' => 'id']));
1096 // ContentEntityStorageBase iterates over the entity which calls this method
1097 // internally in ContentEntityBase::getProperties().
1098 $this->entityFieldManager->expects($this->once())
1099 ->method('getFieldDefinitions')
1100 ->will($this->returnValue([]));
1102 $this->entityType->expects($this->atLeastOnce())
1103 ->method('isRevisionable')
1104 ->will($this->returnValue(FALSE));
1105 $this->entityTypeManager->expects($this->atLeastOnce())
1106 ->method('getDefinition')
1107 ->with($this->entityType->id())
1108 ->will($this->returnValue($this->entityType));
1110 $this->setUpEntityStorage();
1112 $entity = $this->entityStorage->create();
1113 $entity->expects($this->atLeastOnce())
1115 ->will($this->returnValue('foo'));
1117 $this->assertInstanceOf('Drupal\Core\Entity\EntityInterface', $entity);
1118 $this->assertSame('foo', $entity->id());
1119 $this->assertTrue($entity->isNew());
1123 * Returns a set of mock field definitions for the given names.
1125 * @param array $field_names
1126 * An array of field names.
1127 * @param array $methods
1128 * (optional) An associative array of mock method return values keyed by
1131 * @return \Drupal\Tests\Core\Field\TestBaseFieldDefinitionInterface[]|\PHPUnit_Framework_MockObject_MockObject[]
1132 * An array of mock base field definitions.
1134 protected function mockFieldDefinitions(array $field_names, $methods = []) {
1135 $field_definitions = [];
1136 $definition = $this->getMock('Drupal\Tests\Core\Field\TestBaseFieldDefinitionInterface');
1138 // Assign common method return values.
1140 'isBaseField' => TRUE,
1142 foreach ($methods as $method => $result) {
1144 ->expects($this->any())
1146 ->will($this->returnValue($result));
1149 // Assign field names to mock definitions.
1150 foreach ($field_names as $field_name) {
1151 $field_definitions[$field_name] = clone $definition;
1152 $field_definitions[$field_name]
1153 ->expects($this->any())
1155 ->will($this->returnValue($field_name));
1158 return $field_definitions;
1162 * Sets up the content entity database storage.
1164 protected function setUpEntityStorage() {
1165 $this->connection = $this->getMockBuilder('Drupal\Core\Database\Connection')
1166 ->disableOriginalConstructor()
1169 $this->entityTypeManager->expects($this->any())
1170 ->method('getDefinition')
1171 ->will($this->returnValue($this->entityType));
1173 $this->entityFieldManager->expects($this->any())
1174 ->method('getFieldStorageDefinitions')
1175 ->will($this->returnValue($this->fieldDefinitions));
1177 $this->entityFieldManager->expects($this->any())
1178 ->method('getBaseFieldDefinitions')
1179 ->will($this->returnValue($this->fieldDefinitions));
1181 $this->entityStorage = new SqlContentEntityStorage($this->entityType, $this->connection, $this->entityManager, $this->cache, $this->languageManager, new MemoryCache());
1185 * @covers ::doLoadMultiple
1186 * @covers ::buildCacheId
1187 * @covers ::getFromPersistentCache
1189 public function testLoadMultiplePersistentCached() {
1190 $this->setUpModuleHandlerNoImplementations();
1192 $key = 'values:' . $this->entityTypeId . ':1';
1194 $entity = $this->getMockBuilder('\Drupal\Tests\Core\Entity\Sql\SqlContentEntityStorageTestEntityInterface')
1195 ->getMockForAbstractClass();
1196 $entity->expects($this->any())
1198 ->will($this->returnValue($id));
1200 $this->entityType->expects($this->atLeastOnce())
1201 ->method('isPersistentlyCacheable')
1202 ->will($this->returnValue(TRUE));
1203 $this->entityType->expects($this->atLeastOnce())
1205 ->will($this->returnValue($this->entityTypeId));
1206 $this->entityType->expects($this->atLeastOnce())
1207 ->method('getClass')
1208 ->will($this->returnValue(get_class($entity)));
1210 $this->cache->expects($this->once())
1211 ->method('getMultiple')
1213 ->will($this->returnValue([$key => (object) ['data' => $entity]]));
1214 $this->cache->expects($this->never())
1217 $this->setUpEntityStorage();
1218 $entities = $this->entityStorage->loadMultiple([$id]);
1219 $this->assertEquals($entity, $entities[$id]);
1223 * @covers ::doLoadMultiple
1224 * @covers ::buildCacheId
1225 * @covers ::getFromPersistentCache
1226 * @covers ::setPersistentCache
1228 public function testLoadMultipleNoPersistentCache() {
1229 $this->setUpModuleHandlerNoImplementations();
1232 $entity = $this->getMockBuilder('\Drupal\Tests\Core\Entity\Sql\SqlContentEntityStorageTestEntityInterface')
1233 ->getMockForAbstractClass();
1234 $entity->expects($this->any())
1236 ->will($this->returnValue($id));
1238 $this->entityType->expects($this->any())
1239 ->method('isPersistentlyCacheable')
1240 ->will($this->returnValue(FALSE));
1241 $this->entityType->expects($this->atLeastOnce())
1243 ->will($this->returnValue($this->entityTypeId));
1244 $this->entityType->expects($this->atLeastOnce())
1245 ->method('getClass')
1246 ->will($this->returnValue(get_class($entity)));
1248 // There should be no calls to the cache backend for an entity type without
1249 // persistent caching.
1250 $this->cache->expects($this->never())
1251 ->method('getMultiple');
1252 $this->cache->expects($this->never())
1255 $entity_storage = $this->getMockBuilder('Drupal\Core\Entity\Sql\SqlContentEntityStorage')
1256 ->setConstructorArgs([$this->entityType, $this->connection, $this->entityManager, $this->cache, $this->languageManager, new MemoryCache()])
1257 ->setMethods(['getFromStorage', 'invokeStorageLoadHook', 'initTableLayout'])
1259 $entity_storage->method('invokeStorageLoadHook')
1261 $entity_storage->method('initTableLayout')
1263 $entity_storage->expects($this->once())
1264 ->method('getFromStorage')
1266 ->will($this->returnValue([$id => $entity]));
1268 $entities = $entity_storage->loadMultiple([$id]);
1269 $this->assertEquals($entity, $entities[$id]);
1273 * @covers ::doLoadMultiple
1274 * @covers ::buildCacheId
1275 * @covers ::getFromPersistentCache
1276 * @covers ::setPersistentCache
1278 public function testLoadMultiplePersistentCacheMiss() {
1279 $this->setUpModuleHandlerNoImplementations();
1282 $entity = $this->getMockBuilder('\Drupal\Tests\Core\Entity\Sql\SqlContentEntityStorageTestEntityInterface')
1283 ->getMockForAbstractClass();
1284 $entity->expects($this->any())
1286 ->will($this->returnValue($id));
1288 $this->entityType->expects($this->any())
1289 ->method('isPersistentlyCacheable')
1290 ->will($this->returnValue(TRUE));
1291 $this->entityType->expects($this->atLeastOnce())
1293 ->will($this->returnValue($this->entityTypeId));
1294 $this->entityType->expects($this->atLeastOnce())
1295 ->method('getClass')
1296 ->will($this->returnValue(get_class($entity)));
1298 // In case of a cache miss, the entity is loaded from the storage and then
1299 // set in the cache.
1300 $key = 'values:' . $this->entityTypeId . ':1';
1301 $this->cache->expects($this->once())
1302 ->method('getMultiple')
1304 ->will($this->returnValue([]));
1305 $this->cache->expects($this->once())
1307 ->with($key, $entity, CacheBackendInterface::CACHE_PERMANENT, [$this->entityTypeId . '_values', 'entity_field_info']);
1309 $entity_storage = $this->getMockBuilder('Drupal\Core\Entity\Sql\SqlContentEntityStorage')
1310 ->setConstructorArgs([$this->entityType, $this->connection, $this->entityManager, $this->cache, $this->languageManager, new MemoryCache()])
1311 ->setMethods(['getFromStorage', 'invokeStorageLoadHook', 'initTableLayout'])
1313 $entity_storage->method('invokeStorageLoadHook')
1315 $entity_storage->method('initTableLayout')
1317 $entity_storage->expects($this->once())
1318 ->method('getFromStorage')
1320 ->will($this->returnValue([$id => $entity]));
1322 $entities = $entity_storage->loadMultiple([$id]);
1323 $this->assertEquals($entity, $entities[$id]);
1329 public function testHasData() {
1330 $query = $this->getMock('Drupal\Core\Entity\Query\QueryInterface');
1331 $query->expects(($this->once()))
1332 ->method('accessCheck')
1334 ->willReturn($query);
1335 $query->expects(($this->once()))
1338 ->willReturn($query);
1339 $query->expects(($this->once()))
1343 $factory = $this->getMock(QueryFactoryInterface::class);
1344 $factory->expects($this->once())
1346 ->with($this->entityType, 'AND')
1347 ->willReturn($query);
1349 $this->container->set('entity.query.sql', $factory);
1351 $database = $this->getMockBuilder('Drupal\Core\Database\Connection')
1352 ->disableOriginalConstructor()
1355 $this->entityTypeManager->expects($this->any())
1356 ->method('getDefinition')
1357 ->will($this->returnValue($this->entityType));
1359 $this->entityFieldManager->expects($this->any())
1360 ->method('getFieldStorageDefinitions')
1361 ->will($this->returnValue($this->fieldDefinitions));
1363 $this->entityFieldManager->expects($this->any())
1364 ->method('getBaseFieldDefinitions')
1365 ->will($this->returnValue($this->fieldDefinitions));
1367 $this->entityStorage = new SqlContentEntityStorage($this->entityType, $database, $this->entityManager, $this->cache, $this->languageManager, new MemoryCache());
1369 $result = $this->entityStorage->hasData();
1371 $this->assertTrue($result, 'hasData returned TRUE');
1375 * Tests entity ID sanitization.
1377 public function testCleanIds() {
1408 $this->fieldDefinitions = $this->mockFieldDefinitions(['id']);
1409 $this->fieldDefinitions['id']->expects($this->any())
1411 ->will($this->returnValue('integer'));
1413 $this->setUpEntityStorage();
1415 $this->entityType->expects($this->any())
1417 ->will($this->returnValueMap(
1421 $method = new \ReflectionMethod($this->entityStorage, 'cleanIds');
1422 $method->setAccessible(TRUE);
1423 $this->assertEquals($valid_ids, $method->invoke($this->entityStorage, $valid_ids));
1439 $this->assertEquals([], $method->invoke($this->entityStorage, $invalid_ids));
1444 * Sets up the module handler with no implementations.
1446 protected function setUpModuleHandlerNoImplementations() {
1447 $this->moduleHandler->expects($this->any())
1448 ->method('getImplementations')
1449 ->will($this->returnValueMap([
1450 ['entity_load', []],
1451 [$this->entityTypeId . '_load', []],
1454 $this->container->set('module_handler', $this->moduleHandler);
1460 * Provides an entity with dummy implementations of static methods, because
1461 * those cannot be mocked.
1463 abstract class SqlContentEntityStorageTestEntityInterface implements EntityInterface {
1468 public static function postLoad(EntityStorageInterface $storage, array &$entities) {