diff --git a/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php b/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php index 5969aae..505f997 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php @@ -106,7 +106,7 @@ class ContentEntityDatabaseStorage extends ContentEntityStorageBase implements S /** * The entity schema handler. * - * @var \Drupal\Core\Entity\Schema\EntitySchemaHandlerInterface + * @var \Drupal\Core\Entity\Schema\ContentEntitySchemaHandlerInterface */ protected $schemaHandler; @@ -220,13 +220,6 @@ public function getRevisionDataTable() { } /** - * {@inheritdoc} - */ - public function getSchema() { - return $this->schemaHandler()->getSchema(); - } - - /** * Gets the schema handler for this entity storage. * * @return \Drupal\Core\Entity\Schema\ContentEntitySchemaHandler @@ -234,7 +227,7 @@ public function getSchema() { */ protected function schemaHandler() { if (!isset($this->schemaHandler)) { - $this->schemaHandler = new ContentEntitySchemaHandler($this->entityManager, $this->entityType, $this); + $this->schemaHandler = new ContentEntitySchemaHandler($this->entityManager, $this->entityType, $this, $this->database); } return $this->schemaHandler; } @@ -242,6 +235,12 @@ protected function schemaHandler() { /** * {@inheritdoc} */ + public function customizeSchema(&$schema) { + } + + /** + * {@inheritdoc} + */ public function getTableMapping() { if (!isset($this->tableMapping)) { @@ -1369,6 +1368,27 @@ protected function usesDedicatedTable(FieldStorageDefinitionInterface $definitio /** * {@inheritdoc} */ + public function onEntityTypeDefinitionCreate() { + $this->schemaHandler()->createEntitySchema($this->entityType); + } + + /** + * {@inheritdoc} + */ + public function onEntityTypeDefinitionUpdate(EntityTypeInterface $original) { + $this->schemaHandler()->updateEntitySchema($this->entityType, $original); + } + + /** + * {@inheritdoc} + */ + public function onEntityTypeDefinitionDelete() { + $this->schemaHandler()->dropEntitySchema($this->entityType); + } + + /** + * {@inheritdoc} + */ public function onFieldStorageDefinitionCreate(FieldStorageDefinitionInterface $storage_definition) { $schema = $this->_fieldSqlSchema($storage_definition); foreach ($schema as $name => $table) { diff --git a/core/lib/Drupal/Core/Entity/EntityStorageBase.php b/core/lib/Drupal/Core/Entity/EntityStorageBase.php index b181bb4..815b91f 100644 --- a/core/lib/Drupal/Core/Entity/EntityStorageBase.php +++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php @@ -463,4 +463,22 @@ public function getQuery($conjunction = 'AND') { return \Drupal::entityQuery($this->getEntityTypeId(), $conjunction); } + /** + * {@inheritdoc} + */ + public function onEntityTypeDefinitionCreate() { + } + + /** + * {@inheritdoc} + */ + public function onEntityTypeDefinitionUpdate(EntityTypeInterface $original) { + } + + /** + * {@inheritdoc} + */ + public function onEntityTypeDefinitionDelete() { + } + } diff --git a/core/lib/Drupal/Core/Entity/EntityStorageInterface.php b/core/lib/Drupal/Core/Entity/EntityStorageInterface.php index 32867f4..590f1ad 100644 --- a/core/lib/Drupal/Core/Entity/EntityStorageInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityStorageInterface.php @@ -186,4 +186,22 @@ public function getEntityTypeId(); */ public function getEntityType(); + /** + * Reacts to the creation of the entity type definition. + */ + public function onEntityTypeDefinitionCreate(); + + /** + * Reacts to the update of the entity type definition. + * + * @param \Drupal\Core\Entity\EntityTypeInterface $original + * The original entity type definition. + */ + public function onEntityTypeDefinitionUpdate(EntityTypeInterface $original); + + /** + * Reacts to the deletion of the entity type definition. + */ + public function onEntityTypeDefinitionDelete(); + } diff --git a/core/lib/Drupal/Core/Entity/Schema/ContentEntitySchemaHandler.php b/core/lib/Drupal/Core/Entity/Schema/ContentEntitySchemaHandler.php index 4f30d47..90d3afc 100644 --- a/core/lib/Drupal/Core/Entity/Schema/ContentEntitySchemaHandler.php +++ b/core/lib/Drupal/Core/Entity/Schema/ContentEntitySchemaHandler.php @@ -7,6 +7,7 @@ namespace Drupal\Core\Entity\Schema; +use Drupal\Core\Database\Connection; use Drupal\Core\Entity\ContentEntityDatabaseStorage; use Drupal\Core\Entity\ContentEntityTypeInterface; use Drupal\Core\Entity\EntityManagerInterface; @@ -14,7 +15,7 @@ /** * Defines a schema handler that supports revisionable, translatable entities. */ -class ContentEntitySchemaHandler implements EntitySchemaHandlerInterface { +class ContentEntitySchemaHandler implements ContentEntitySchemaHandlerInterface { /** * The entity type this schema builder is responsible for. @@ -45,6 +46,13 @@ class ContentEntitySchemaHandler implements EntitySchemaHandlerInterface { protected $schema; /** + * The database connection to be used. + * + * @var \Drupal\Core\Database\Connection + */ + protected $database; + + /** * Constructs a ContentEntitySchemaHandler. * * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager @@ -53,22 +61,69 @@ class ContentEntitySchemaHandler implements EntitySchemaHandlerInterface { * The entity type. * @param \Drupal\Core\Entity\ContentEntityDatabaseStorage $storage * The storage of the entity type. This must be an SQL-based storage. + * @param \Drupal\Core\Database\Connection $database + * The database connection to be used. */ - public function __construct(EntityManagerInterface $entity_manager, ContentEntityTypeInterface $entity_type, ContentEntityDatabaseStorage $storage) { + public function __construct(EntityManagerInterface $entity_manager, ContentEntityTypeInterface $entity_type, ContentEntityDatabaseStorage $storage, Connection $database) { $this->entityType = $entity_type; $this->fieldStorageDefinitions = $entity_manager->getFieldStorageDefinitions($entity_type->id()); $this->storage = $storage; + $this->database = $database; + } + + /** + * {@inheritdoc} + */ + public function createEntitySchema(ContentEntityTypeInterface $entity_type) { + $schema_handler = $this->database->schema(); + $schema = $this->getEntitySchema($entity_type, TRUE); + foreach ($schema as $table_name => $table_schema) { + if (!$schema_handler->tableExists($table_name)) { + $schema_handler->createTable($table_name, $table_schema); + } + } } /** * {@inheritdoc} */ - public function getSchema() { - // Prepare basic information about the entity type. - $tables = $this->getTables(); + public function dropEntitySchema(ContentEntityTypeInterface $entity_type) { + $schema_handler = $this->database->schema(); + $schema = $this->getEntitySchema($entity_type, TRUE); + foreach ($schema as $table_name => $table_schema) { + if ($schema_handler->tableExists($table_name)) { + $schema_handler->dropTable($table_name); + } + } + } - if (!isset($this->schema[$this->entityType->id()])) { + /** + * {@inheritdoc} + */ + public function updateEntitySchema(ContentEntityTypeInterface $entity_type, ContentEntityTypeInterface $original) { + // @todo Implement proper updates: https://www.drupal.org/node/1498720 + $this->createEntitySchema($entity_type); + } + + /** + * Returns the entity schema for the specified entity type. + * + * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type + * The entity type definition. + * @param bool $reset + * (optional) If set to TRUE static cache will be ignored and a new schema + * array generation will be performed. Defaults to FALSE. + * + * @return array + * A Schema API array describing the entity schema, excluding dedicated + * field tables. + */ + protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $reset = FALSE) { + $entity_type_id = $entity_type->id(); + + if (!isset($this->schema[$entity_type_id]) || $reset) { // Initialize the table schema. + $tables = $this->getTables(); $schema[$tables['base_table']] = $this->initializeBaseTable(); if (isset($tables['revision_table'])) { $schema[$tables['revision_table']] = $this->initializeRevisionTable(); @@ -108,10 +163,13 @@ public function getSchema() { $this->processRevisionDataTable($schema[$tables['revision_data_table']]); } - $this->schema[$this->entityType->id()] = $schema; + // Perform additional customizations. + $this->storage->customizeSchema($schema); + + $this->schema[$entity_type_id] = $schema; } - return $this->schema[$this->entityType->id()]; + return $this->schema[$entity_type_id]; } /** diff --git a/core/lib/Drupal/Core/Entity/Schema/ContentEntitySchemaHandlerInterface.php b/core/lib/Drupal/Core/Entity/Schema/ContentEntitySchemaHandlerInterface.php new file mode 100644 index 0000000..94c599a --- /dev/null +++ b/core/lib/Drupal/Core/Entity/Schema/ContentEntitySchemaHandlerInterface.php @@ -0,0 +1,47 @@ +schema(); foreach ($entity_manager->getDefinitions() as $entity_type) { - if ($entity_type->getProvider() == $module) { + if ($entity_type instanceof ContentEntityTypeInterface && $entity_type->getProvider() == $module) { $storage = $entity_manager->getStorage($entity_type->id()); - if ($storage instanceof EntitySchemaProviderInterface) { - foreach ($storage->getSchema() as $table_name => $table_schema) { - if (!$schema->tableExists($table_name)) { - $schema->createTable($table_name, $table_schema); - } - } - } + $storage->onEntityTypeDefinitionCreate(); } } @@ -955,17 +948,10 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) { // Remove any entity schemas belonging to the module. $entity_manager = \Drupal::entityManager(); - $schema = \Drupal::database()->schema(); foreach ($entity_manager->getDefinitions() as $entity_type) { - if ($entity_type->getProvider() == $module) { + if ($entity_type instanceof ContentEntityTypeInterface && $entity_type->getProvider() == $module) { $storage = $entity_manager->getStorage($entity_type->id()); - if ($storage instanceof EntitySchemaProviderInterface) { - foreach ($storage->getSchema() as $table_name => $table_schema) { - if ($schema->tableExists($table_name)) { - $schema->dropTable($table_name); - } - } - } + $storage->onEntityTypeDefinitionDelete(); } } diff --git a/core/modules/aggregator/src/FeedStorage.php b/core/modules/aggregator/src/FeedStorage.php index da784f5..ddc054a 100644 --- a/core/modules/aggregator/src/FeedStorage.php +++ b/core/modules/aggregator/src/FeedStorage.php @@ -21,9 +21,7 @@ class FeedStorage extends ContentEntityDatabaseStorage implements FeedStorageInt /** * {@inheritdoc} */ - public function getSchema() { - $schema = parent::getSchema(); - + public function customizeSchema(&$schema) { // Marking the respective fields as NOT NULL makes the indexes more // performant. $schema['aggregator_feed']['fields']['url']['not null'] = TRUE; @@ -37,8 +35,6 @@ public function getSchema() { $schema['aggregator_feed']['unique keys'] += array( 'aggregator_feed__title' => array('title'), ); - - return $schema; } /** diff --git a/core/modules/aggregator/src/ItemStorage.php b/core/modules/aggregator/src/ItemStorage.php index f2b4aa6..bd38a3a 100644 --- a/core/modules/aggregator/src/ItemStorage.php +++ b/core/modules/aggregator/src/ItemStorage.php @@ -22,9 +22,7 @@ class ItemStorage extends ContentEntityDatabaseStorage implements ItemStorageInt /** * {@inheritdoc} */ - public function getSchema() { - $schema = parent::getSchema(); - + public function customizeSchema(&$schema) { // Marking the respective fields as NOT NULL makes the indexes more // performant. $schema['aggregator_item']['fields']['timestamp']['not null'] = TRUE; @@ -38,8 +36,6 @@ public function getSchema() { 'columns' => array('fid' => 'fid'), ), ); - - return $schema; } /** diff --git a/core/modules/block_content/src/BlockContentStorage.php b/core/modules/block_content/src/BlockContentStorage.php index b98a861..8301bba 100644 --- a/core/modules/block_content/src/BlockContentStorage.php +++ b/core/modules/block_content/src/BlockContentStorage.php @@ -17,9 +17,7 @@ class BlockContentStorage extends ContentEntityDatabaseStorage { /** * {@inheritdoc} */ - public function getSchema() { - $schema = parent::getSchema(); - + public function customizeSchema(&$schema) { // Marking the respective fields as NOT NULL makes the indexes more // performant. $schema['block_content_field_data']['fields']['info']['not null'] = TRUE; @@ -27,8 +25,6 @@ public function getSchema() { $schema['block_content_field_data']['unique keys'] += array( 'block_content__info' => array('info', 'langcode'), ); - - return $schema; } } diff --git a/core/modules/comment/src/CommentStorage.php b/core/modules/comment/src/CommentStorage.php index 9639d59..3618fb8 100644 --- a/core/modules/comment/src/CommentStorage.php +++ b/core/modules/comment/src/CommentStorage.php @@ -321,9 +321,7 @@ public function loadThread(EntityInterface $entity, $field_name, $mode, $comment /** * {@inheritdoc} */ - public function getSchema() { - $schema = parent::getSchema(); - + public function customizeSchema(&$schema) { // Marking the respective fields as NOT NULL makes the indexes more // performant. $schema['comment_field_data']['fields']['created']['not null'] = TRUE; @@ -356,8 +354,6 @@ public function getSchema() { 'columns' => array('uid' => 'uid'), ), ); - - return $schema; } /** diff --git a/core/modules/contact/tests/modules/contact_storage_test/contact_storage_test.install b/core/modules/contact/tests/modules/contact_storage_test/contact_storage_test.install index bcbe5df..c749147 100644 --- a/core/modules/contact/tests/modules/contact_storage_test/contact_storage_test.install +++ b/core/modules/contact/tests/modules/contact_storage_test/contact_storage_test.install @@ -9,16 +9,10 @@ * Implements hook_install(). */ function contact_storage_test_install() { - // ModuleHandler won't create the schema automatically because Message entity - // belongs to contact.module. - // @todo Remove this when https://www.drupal.org/node/1498720 is in. + // Recreate the original entity type definition, so we can trigger the entity + // schema creation. $entity_manager = \Drupal::entityManager(); - $schema = \Drupal::database()->schema(); - $entity_type = $entity_manager->getDefinition('contact_message'); - $storage = $entity_manager->getStorage($entity_type->id()); - foreach ($storage->getSchema() as $table_name => $table_schema) { - if (!$schema->tableExists($table_name)) { - $schema->createTable($table_name, $table_schema); - } - } + $original = clone $entity_manager->getDefinition('contact_message'); + $original->setStorageClass('Drupal\Core\Entity\ContentEntityNullStorage'); + $entity_manager->getStorage($original->id())->onEntityTypeDefinitionUpdate($original); } diff --git a/core/modules/file/src/FileStorage.php b/core/modules/file/src/FileStorage.php index 93dae42..8da4cbc 100644 --- a/core/modules/file/src/FileStorage.php +++ b/core/modules/file/src/FileStorage.php @@ -30,9 +30,7 @@ public function spaceUsed($uid = NULL, $status = FILE_STATUS_PERMANENT) { /** * {@inheritdoc} */ - public function getSchema() { - $schema = parent::getSchema(); - + public function customizeSchema(&$schema) { // Marking the respective fields as NOT NULL makes the indexes more // performant. $schema['file_managed']['fields']['status']['not null'] = TRUE; @@ -48,8 +46,6 @@ public function getSchema() { $schema['file_managed']['unique keys'] += array( 'file__uri' => array('uri'), ); - - return $schema; } } diff --git a/core/modules/node/src/NodeStorage.php b/core/modules/node/src/NodeStorage.php index 86cf982..9e216a5 100644 --- a/core/modules/node/src/NodeStorage.php +++ b/core/modules/node/src/NodeStorage.php @@ -62,9 +62,7 @@ public function clearRevisionsLanguage($language) { /** * {@inheritdoc} */ - public function getSchema() { - $schema = parent::getSchema(); - + public function customizeSchema(&$schema) { // Marking the respective fields as NOT NULL makes the indexes more // performant. $schema['node_field_data']['fields']['changed']['not null'] = TRUE; @@ -101,8 +99,6 @@ public function getSchema() { 'node__default_langcode' => array('default_langcode'), 'node__langcode' => array('langcode'), ); - - return $schema; } } diff --git a/core/modules/simpletest/src/KernelTestBase.php b/core/modules/simpletest/src/KernelTestBase.php index e3ab657..d8711e1 100644 --- a/core/modules/simpletest/src/KernelTestBase.php +++ b/core/modules/simpletest/src/KernelTestBase.php @@ -11,10 +11,10 @@ use Drupal\Core\Database\Database; use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\DrupalKernel; +use Drupal\Core\Entity\Sql\SqlEntityStorageInterface; use Drupal\Core\KeyValueStore\KeyValueMemoryFactory; use Drupal\Core\Language\Language; use Drupal\Core\Site\Settings; -use Drupal\Core\Entity\Schema\EntitySchemaProviderInterface; use Symfony\Component\DependencyInjection\Parameter; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\HttpFoundation\Request; @@ -387,24 +387,14 @@ protected function installSchema($module, $tables) { protected function installEntitySchema($entity_type_id) { /** @var \Drupal\Core\Entity\EntityManagerInterface $entity_manager */ $entity_manager = $this->container->get('entity.manager'); - /** @var \Drupal\Core\Database\Schema $schema_handler */ - $schema_handler = $this->container->get('database')->schema(); - $storage = $entity_manager->getStorage($entity_type_id); - if ($storage instanceof EntitySchemaProviderInterface) { - $schema = $storage->getSchema(); - foreach ($schema as $table_name => $table_schema) { - $schema_handler->createTable($table_name, $table_schema); - } + $storage->onEntityTypeDefinitionCreate(); + if ($storage instanceof SqlEntityStorageInterface) { + $table_mapping = $storage->getTableMapping(); $this->pass(String::format('Installed entity type tables for the %entity_type entity type: %tables', array( '%entity_type' => $entity_type_id, - '%tables' => '{' . implode('}, {', array_keys($schema)) . '}', - ))); - } - else { - throw new \RuntimeException(String::format('Entity type %entity_type does not support automatic schema installation.', array( - '%entity-type' => $entity_type_id, + '%tables' => '{' . implode('}, {', $table_mapping->getTableNames()) . '}', ))); } } diff --git a/core/modules/taxonomy/src/TermStorage.php b/core/modules/taxonomy/src/TermStorage.php index 345921c..38472ba 100644 --- a/core/modules/taxonomy/src/TermStorage.php +++ b/core/modules/taxonomy/src/TermStorage.php @@ -157,9 +157,7 @@ public function resetWeights($vid) { /** * {@inheritdoc} */ - public function getSchema() { - $schema = parent::getSchema(); - + public function customizeSchema(&$schema) { // Marking the respective fields as NOT NULL makes the indexes more // performant. $schema['taxonomy_term_field_data']['fields']['weight']['not null'] = TRUE; @@ -249,8 +247,6 @@ public function getSchema() { ), ), ); - - return $schema; } /** diff --git a/core/modules/user/src/UserStorage.php b/core/modules/user/src/UserStorage.php index 65209b8..7d039c3 100644 --- a/core/modules/user/src/UserStorage.php +++ b/core/modules/user/src/UserStorage.php @@ -171,9 +171,7 @@ public function updateLastAccessTimestamp(AccountInterface $account, $timestamp) /** * {@inheritdoc} */ - public function getSchema() { - $schema = parent::getSchema(); - + public function customizeSchema(&$schema) { // The "users" table does not use serial identifiers. $schema['users']['fields']['uid']['type'] = 'int'; @@ -220,8 +218,6 @@ public function getSchema() { ), ), ); - - return $schema; } } diff --git a/core/tests/Drupal/Tests/Core/Entity/ContentEntityDatabaseStorageTest.php b/core/tests/Drupal/Tests/Core/Entity/ContentEntityDatabaseStorageTest.php index fde0d84..18b507c 100644 --- a/core/tests/Drupal/Tests/Core/Entity/ContentEntityDatabaseStorageTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/ContentEntityDatabaseStorageTest.php @@ -11,6 +11,7 @@ use Drupal\Core\Entity\ContentEntityDatabaseStorage; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityStorageInterface; +use Drupal\Core\Entity\Schema\ContentEntitySchemaHandler; use Drupal\Core\Field\BaseFieldDefinition; use Drupal\Core\Language\Language; use Drupal\Tests\UnitTestCase; @@ -266,21 +267,23 @@ public function providerTestGetRevisionDataTable() { } /** - * Tests ContentEntityDatabaseStorage::getSchema(). + * Tests ContentEntityDatabaseStorage::onEntityTypeDefinitionCreate(). * * @covers ::__construct() - * @covers ::getSchema() - * @covers ::schemaHandler() + * @covers ::onEntityTypeDefinitionCreate() * @covers ::getTableMapping() */ - public function testGetSchema() { + public function testOnEntityDefinitionCreate() { $columns = array( 'value' => array( 'type' => 'int', ), ); - $this->fieldDefinitions['id'] = $this->getMock('Drupal\Core\Field\FieldStorageDefinitionInterface'); + $this->fieldDefinitions['id'] = $this->getMock('Drupal\Tests\Core\Field\TestBaseFieldDefinitionInterface'); + $this->fieldDefinitions['id']->expects($this->any()) + ->method('getName') + ->will($this->returnValue('id')); $this->fieldDefinitions['id']->expects($this->once()) ->method('getColumns') ->will($this->returnValue($columns)); @@ -305,35 +308,47 @@ public function testGetSchema() { array('id' => 'id'), ))); - $this->entityManager->expects($this->once()) - ->method('getFieldStorageDefinitions') - ->with($this->entityType->id()) - ->will($this->returnValue($this->fieldDefinitions)); - $this->setUpEntityStorage(); $expected = array( - 'entity_test' => array( - 'description' => 'The base table for entity_test entities.', - 'fields' => array( - 'id' => array( - 'type' => 'serial', - 'description' => NULL, - 'not null' => TRUE, - ), + 'description' => 'The base table for entity_test entities.', + 'fields' => array( + 'id' => array( + 'type' => 'serial', + 'description' => NULL, + 'not null' => TRUE, ), - 'primary key' => array('id'), - 'unique keys' => array(), - 'indexes' => array(), - 'foreign keys' => array(), ), + 'primary key' => array('id'), + 'unique keys' => array(), + 'indexes' => array(), + 'foreign keys' => array(), ); - $this->assertEquals($expected, $this->entityStorage->getSchema()); - // Test that repeated calls do not result in repeatedly instantiating - // ContentEntitySchemaHandler as getFieldStorageDefinitions() is only - // expected to be called once. - $this->assertEquals($expected, $this->entityStorage->getSchema()); + $schema_handler = $this->getMockBuilder('Drupal\Core\Database\Schema') + ->disableOriginalConstructor() + ->getMock(); + $schema_handler->expects($this->any()) + ->method('createTable') + ->with($this->equalTo('entity_test'), $this->equalTo($expected)); + + $this->connection->expects($this->once()) + ->method('schema') + ->will($this->returnValue($schema_handler)); + + $storage = $this->getMockBuilder('Drupal\Core\Entity\ContentEntityDatabaseStorage') + ->setConstructorArgs(array($this->entityType, $this->connection, $this->entityManager, $this->cache)) + ->setMethods(array('schemaHandler')) + ->getMock(); + + $schema_handler = new ContentEntitySchemaHandler($this->entityManager, $this->entityType, $storage, $this->connection); + + $storage + ->expects($this->any()) + ->method('schemaHandler') + ->will($this->returnValue($schema_handler)); + + $storage->onEntityTypeDefinitionCreate(); } /** @@ -397,18 +412,7 @@ public function testGetTableMappingSimple(array $entity_keys) { public function testGetTableMappingSimpleWithFields(array $entity_keys) { $base_field_names = array('title', 'description', 'owner'); $field_names = array_merge(array_values(array_filter($entity_keys)), $base_field_names); - - $definition = $this->getMock('Drupal\Core\Field\FieldStorageDefinitionInterface'); - $this->fieldDefinitions = array_fill_keys($field_names, $definition); - - $this->entityType->expects($this->any()) - ->method('getKey') - ->will($this->returnValueMap(array( - array('id', $entity_keys['id']), - array('uuid', $entity_keys['uuid']), - array('bundle', $entity_keys['bundle']), - ))); - + $this->fieldDefinitions = $this->mockFieldDefinitions($field_names); $this->setUpEntityStorage(); $mapping = $this->entityStorage->getTableMapping(); @@ -533,25 +537,11 @@ public function testGetTableMappingRevisionableWithFields(array $entity_keys) { $base_field_names = array('title'); $field_names = array_merge(array_values(array_filter($entity_keys)), $base_field_names); - - $definition = $this->getMock('Drupal\Core\Field\FieldStorageDefinitionInterface'); - $this->fieldDefinitions = array_fill_keys($field_names, $definition); + $this->fieldDefinitions = $this->mockFieldDefinitions($field_names); $revisionable_field_names = array('description', 'owner'); - $definition = $this->getMock('Drupal\Core\Field\FieldStorageDefinitionInterface'); - // isRevisionable() is only called once, but we re-use the same definition - // for all revisionable fields. - $definition->expects($this->any()) - ->method('isRevisionable') - ->will($this->returnValue(TRUE)); - $field_names = array_merge( - $field_names, - $revisionable_field_names - ); - $this->fieldDefinitions += array_fill_keys( - array_merge($revisionable_field_names, $revision_metadata_field_names), - $definition - ); + $field_names = array_merge($field_names, $revisionable_field_names); + $this->fieldDefinitions += $this->mockFieldDefinitions(array_merge($revisionable_field_names, $revision_metadata_field_names), array('isRevisionable' => TRUE)); $this->entityType->expects($this->exactly(2)) ->method('isRevisionable') @@ -658,9 +648,7 @@ public function testGetTableMappingTranslatableWithFields(array $entity_keys) { $base_field_names = array('title', 'description', 'owner'); $field_names = array_merge(array_values(array_filter($entity_keys)), $base_field_names); - - $definition = $this->getMock('Drupal\Core\Field\FieldStorageDefinitionInterface'); - $this->fieldDefinitions = array_fill_keys($field_names, $definition); + $this->fieldDefinitions = $this->mockFieldDefinitions($field_names); $this->entityType->expects($this->exactly(2)) ->method('isTranslatable') @@ -840,21 +828,10 @@ public function testGetTableMappingRevisionableTranslatableWithFields(array $ent $base_field_names = array('title'); $field_names = array_merge(array_values(array_filter($entity_keys)), $base_field_names); - - $definition = $this->getMock('Drupal\Core\Field\FieldStorageDefinitionInterface'); - $this->fieldDefinitions = array_fill_keys($field_names, $definition); + $this->fieldDefinitions = $this->mockFieldDefinitions($field_names); $revisionable_field_names = array('description', 'owner'); - $definition = $this->getMock('Drupal\Core\Field\FieldStorageDefinitionInterface'); - // isRevisionable() is only called once, but we re-use the same definition - // for all revisionable fields. - $definition->expects($this->any()) - ->method('isRevisionable') - ->will($this->returnValue(TRUE)); - $this->fieldDefinitions += array_fill_keys( - array_merge($revisionable_field_names, $revision_metadata_field_names), - $definition - ); + $this->fieldDefinitions += $this->mockFieldDefinitions(array_merge($revisionable_field_names, $revision_metadata_field_names), array('isRevisionable' => TRUE)); $this->entityType->expects($this->exactly(2)) ->method('isRevisionable') @@ -1078,9 +1055,56 @@ public function testCreate() { } /** + * Returns a set of mock field definitions for the given names. + * + * @param array $field_names + * An array of field names. + * @param array $methods + * (optional) An associative array of mock method return values keyed by + * method name. + * + * @return \Drupal\Core\Field\FieldDefinition[]|\PHPUnit_Framework_MockObject_MockObject[] + * An array of mock field definitions. + */ + protected function mockFieldDefinitions(array $field_names, $methods = array()) { + $field_definitions = array(); + $definition = $this->getMock('Drupal\Tests\Core\Field\TestBaseFieldDefinitionInterface'); + + // Assign common method return values. + foreach ($methods as $method => $result) { + $definition + ->expects($this->any()) + ->method($method) + ->will($this->returnValue($result)); + } + + // Assign field names to mock definitions. + foreach ($field_names as $field_name) { + $field_definitions[$field_name] = clone $definition; + $field_definitions[$field_name] + ->expects($this->any()) + ->method('getName') + ->will($this->returnValue($field_name)); + } + + return $field_definitions; + } + + /** * Sets up the content entity database storage. */ protected function setUpEntityStorage() { + $this->connection = $this->getMockBuilder('Drupal\Core\Database\Connection') + ->disableOriginalConstructor() + ->getMock(); + + $this->entityManager->expects($this->any()) + ->method('getDefinition') + ->will($this->returnValue($this->entityType)); + + $this->entityManager->expects($this->any()) + ->method('getFieldStorageDefinitions') + ->will($this->returnValue($this->fieldDefinitions)); $this->entityManager->expects($this->any()) ->method('getBaseFieldDefinitions') @@ -1223,7 +1247,6 @@ public function testLoadMultiplePersistentCacheMiss() { $entities = $entity_storage->loadMultiple(array($id)); $this->assertEquals($entity, $entities[$id]); - } /** @@ -1239,6 +1262,7 @@ protected function setUpModuleHandlerNoImplementations() { $this->container->set('module_handler', $this->moduleHandler); } + } /** diff --git a/core/tests/Drupal/Tests/Core/Entity/Schema/ContentEntitySchemaHandlerTest.php b/core/tests/Drupal/Tests/Core/Entity/Schema/ContentEntitySchemaHandlerTest.php index 0a4afe7..0c9765c 100644 --- a/core/tests/Drupal/Tests/Core/Entity/Schema/ContentEntitySchemaHandlerTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/Schema/ContentEntitySchemaHandlerTest.php @@ -82,7 +82,7 @@ protected function setUp() { * * @covers ::__construct() * @covers ::getSchema() - * @covers ::getTables() + * @covers ::getEntitySchemaTables() * @covers ::initializeBaseTable() * @covers ::addTableDefaults() * @covers ::getEntityIndexName() @@ -246,16 +246,6 @@ public function testGetSchemaBase() { ), )); - $this->setUpSchemaHandler(); - - $table_mapping = new DefaultTableMapping($this->storageDefinitions); - $table_mapping->setFieldNames('entity_test', array_keys($this->storageDefinitions)); - $table_mapping->setExtraColumns('entity_test', array('default_langcode')); - - $this->storage->expects($this->once()) - ->method('getTableMapping') - ->will($this->returnValue($table_mapping)); - $expected = array( 'entity_test' => array( 'description' => 'The base table for entity_test entities.', @@ -381,9 +371,18 @@ public function testGetSchemaBase() { ), ), ); - $actual = $this->schemaHandler->getSchema(); - $this->assertEquals($expected, $actual); + $this->setUpEntitySchemaHandler($expected); + + $table_mapping = new DefaultTableMapping($this->storageDefinitions); + $table_mapping->setFieldNames('entity_test', array_keys($this->storageDefinitions)); + $table_mapping->setExtraColumns('entity_test', array('default_langcode')); + + $this->storage->expects($this->any()) + ->method('getTableMapping') + ->will($this->returnValue($table_mapping)); + + $this->schemaHandler->createEntitySchema($this->entityType); } /** @@ -391,7 +390,7 @@ public function testGetSchemaBase() { * * @covers ::__construct() * @covers ::getSchema() - * @covers ::getTables() + * @covers ::getEntitySchemaTables() * @covers ::initializeBaseTable() * @covers ::initializeRevisionTable() * @covers ::addTableDefaults() @@ -420,16 +419,6 @@ public function testGetSchemaRevisionable() { ), )); - $this->setUpSchemaHandler(); - - $table_mapping = new DefaultTableMapping($this->storageDefinitions); - $table_mapping->setFieldNames('entity_test', array_keys($this->storageDefinitions)); - $table_mapping->setFieldNames('entity_test_revision', array_keys($this->storageDefinitions)); - - $this->storage->expects($this->once()) - ->method('getTableMapping') - ->will($this->returnValue($table_mapping)); - $expected = array( 'entity_test' => array( 'description' => 'The base table for entity_test entities.', @@ -483,9 +472,17 @@ public function testGetSchemaRevisionable() { ), ); - $actual = $this->schemaHandler->getSchema(); + $this->setUpEntitySchemaHandler($expected); + + $table_mapping = new DefaultTableMapping($this->storageDefinitions); + $table_mapping->setFieldNames('entity_test', array_keys($this->storageDefinitions)); + $table_mapping->setFieldNames('entity_test_revision', array_keys($this->storageDefinitions)); + + $this->storage->expects($this->any()) + ->method('getTableMapping') + ->will($this->returnValue($table_mapping)); - $this->assertEquals($expected, $actual); + $this->schemaHandler->createEntitySchema($this->entityType); } /** @@ -493,7 +490,7 @@ public function testGetSchemaRevisionable() { * * @covers ::__construct() * @covers ::getSchema() - * @covers ::getTables() + * @covers ::getEntitySchemaTables() * @covers ::initializeDataTable() * @covers ::addTableDefaults() * @covers ::getEntityIndexName() @@ -507,7 +504,7 @@ public function testGetSchemaTranslatable() { ), )); - $this->storage->expects($this->once()) + $this->storage->expects($this->any()) ->method('getDataTable') ->will($this->returnValue('entity_test_field_data')); @@ -519,16 +516,6 @@ public function testGetSchemaTranslatable() { ), )); - $this->setUpSchemaHandler(); - - $table_mapping = new DefaultTableMapping($this->storageDefinitions); - $table_mapping->setFieldNames('entity_test', array_keys($this->storageDefinitions)); - $table_mapping->setFieldNames('entity_test_field_data', array_keys($this->storageDefinitions)); - - $this->storage->expects($this->once()) - ->method('getTableMapping') - ->will($this->returnValue($table_mapping)); - $expected = array( 'entity_test' => array( 'description' => 'The base table for entity_test entities.', @@ -575,9 +562,17 @@ public function testGetSchemaTranslatable() { ), ); - $actual = $this->schemaHandler->getSchema(); + $this->setUpEntitySchemaHandler($expected); - $this->assertEquals($expected, $actual); + $table_mapping = new DefaultTableMapping($this->storageDefinitions); + $table_mapping->setFieldNames('entity_test', array_keys($this->storageDefinitions)); + $table_mapping->setFieldNames('entity_test_field_data', array_keys($this->storageDefinitions)); + + $this->storage->expects($this->any()) + ->method('getTableMapping') + ->will($this->returnValue($table_mapping)); + + $this->schemaHandler->createEntitySchema($this->entityType); } /** @@ -585,7 +580,7 @@ public function testGetSchemaTranslatable() { * * @covers ::__construct() * @covers ::getSchema() - * @covers ::getTables() + * @covers ::getEntitySchemaTables() * @covers ::initializeDataTable() * @covers ::addTableDefaults() * @covers ::getEntityIndexName() @@ -626,18 +621,6 @@ public function testGetSchemaRevisionableTranslatable() { ), )); - $this->setUpSchemaHandler(); - - $table_mapping = new DefaultTableMapping($this->storageDefinitions); - $table_mapping->setFieldNames('entity_test', array_keys($this->storageDefinitions)); - $table_mapping->setFieldNames('entity_test_revision', array_keys($this->storageDefinitions)); - $table_mapping->setFieldNames('entity_test_field_data', array_keys($this->storageDefinitions)); - $table_mapping->setFieldNames('entity_test_revision_field_data', array_keys($this->storageDefinitions)); - - $this->storage->expects($this->once()) - ->method('getTableMapping') - ->will($this->returnValue($table_mapping)); - $expected = array( 'entity_test' => array( 'description' => 'The base table for entity_test entities.', @@ -763,26 +746,73 @@ public function testGetSchemaRevisionableTranslatable() { ), ); - $actual = $this->schemaHandler->getSchema(); + $this->setUpEntitySchemaHandler($expected); - $this->assertEquals($expected, $actual); + $table_mapping = new DefaultTableMapping($this->storageDefinitions); + $table_mapping->setFieldNames('entity_test', array_keys($this->storageDefinitions)); + $table_mapping->setFieldNames('entity_test_revision', array_keys($this->storageDefinitions)); + $table_mapping->setFieldNames('entity_test_field_data', array_keys($this->storageDefinitions)); + $table_mapping->setFieldNames('entity_test_revision_field_data', array_keys($this->storageDefinitions)); + + $this->storage->expects($this->any()) + ->method('getTableMapping') + ->will($this->returnValue($table_mapping)); + + $this->schemaHandler->createEntitySchema($this->entityType); } /** * Sets up the schema handler. * - * This uses the field definitions set in $this->fieldDefinitions. + * This uses the field definitions set in $this->storageDefinitions. + * + * @param array $expected + * (optional) An associative array describing the expected entity schema to + * be created. Defaults to expecting nothing. */ - protected function setUpSchemaHandler() { - $this->entityManager->expects($this->once()) + protected function setUpEntitySchemaHandler(array $expected = array()) { + $this->entityManager->expects($this->any()) + ->method('getDefinition') + ->with($this->entityType->id()) + ->will($this->returnValue($this->entityType)); + + $this->entityManager->expects($this->any()) ->method('getFieldStorageDefinitions') ->with($this->entityType->id()) ->will($this->returnValue($this->storageDefinitions)); - $this->schemaHandler = new ContentEntitySchemaHandler( - $this->entityManager, - $this->entityType, - $this->storage - ); + + $db_schema_handler = $this->getMockBuilder('Drupal\Core\Database\Schema') + ->disableOriginalConstructor() + ->getMock(); + + if ($expected) { + $invocation_count = 0; + $expected_table_names = array_keys($expected); + $expected_table_schemas = array_values($expected); + + $db_schema_handler->expects($this->any()) + ->method('createTable') + ->with( + $this->callback(function($table_name) use (&$invocation_count, $expected_table_names) { + return $expected_table_names[$invocation_count] == $table_name; + }), + $this->callback(function($table_schema) use (&$invocation_count, $expected_table_schemas) { + return $expected_table_schemas[$invocation_count] == $table_schema; + }) + ) + ->will($this->returnCallback(function() use (&$invocation_count) { + $invocation_count++; + })); + } + + $connection = $this->getMockBuilder('Drupal\Core\Database\Connection') + ->disableOriginalConstructor() + ->getMock(); + $connection->expects($this->any()) + ->method('schema') + ->will($this->returnValue($db_schema_handler)); + + $this->schemaHandler = new ContentEntitySchemaHandler($this->entityManager, $this->entityType, $this->storage, $connection); } /** @@ -795,7 +825,11 @@ protected function setUpSchemaHandler() { * FieldStorageDefinitionInterface::getSchema(). */ public function setUpStorageDefinition($field_name, array $schema) { - $this->storageDefinitions[$field_name] = $this->getMock('Drupal\Core\Field\FieldStorageDefinitionInterface'); + $this->storageDefinitions[$field_name] = $this->getMock('Drupal\Tests\Core\Field\TestBaseFieldDefinitionInterface'); + // getDescription() is called once for each table. + $this->storageDefinitions[$field_name]->expects($this->any()) + ->method('getName') + ->will($this->returnValue($field_name)); // getDescription() is called once for each table. $this->storageDefinitions[$field_name]->expects($this->any()) ->method('getDescription') @@ -804,7 +838,7 @@ public function setUpStorageDefinition($field_name, array $schema) { $this->storageDefinitions[$field_name]->expects($this->any()) ->method('getSchema') ->will($this->returnValue($schema)); - $this->storageDefinitions[$field_name]->expects($this->once()) + $this->storageDefinitions[$field_name]->expects($this->any()) ->method('getColumns') ->will($this->returnValue($schema['columns'])); } diff --git a/core/tests/Drupal/Tests/Core/Field/TestBaseFieldDefinitionInterface.php b/core/tests/Drupal/Tests/Core/Field/TestBaseFieldDefinitionInterface.php new file mode 100644 index 0000000..f685edd --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Field/TestBaseFieldDefinitionInterface.php @@ -0,0 +1,17 @@ +