diff --git a/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php b/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php index 2d45275..c9532f4 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php @@ -34,7 +34,7 @@ * * @ingroup entity_api */ -class ContentEntityDatabaseStorage extends ContentEntityStorageBase implements SqlEntityStorageInterface { +class ContentEntityDatabaseStorage extends ContentEntityStorageBase implements SqlEntityStorageInterface, EntityTypeListenerInterface { /** * The mapping of field columns to SQL tables. @@ -218,13 +218,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\SqlContentEntityStorageSchema @@ -233,7 +226,7 @@ public function getSchema() { protected function schemaHandler() { if (!isset($this->schemaHandler)) { $schema_handler_class = $this->entityType->getHandlerClass('storage_schema') ?: 'Drupal\Core\Entity\Schema\SqlContentEntityStorageSchema'; - $this->schemaHandler = new $schema_handler_class($this->entityManager, $this->entityType, $this); + $this->schemaHandler = new $schema_handler_class($this->entityManager, $this->entityType, $this, $this->database); } return $this->schemaHandler; } @@ -1368,6 +1361,27 @@ protected function usesDedicatedTable(FieldStorageDefinitionInterface $definitio /** * {@inheritdoc} */ + public function onEntityTypeCreate(EntityTypeInterface $entity_type) { + $this->schemaHandler()->onEntityTypeCreate($entity_type); + } + + /** + * {@inheritdoc} + */ + public function onEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeInterface $original) { + $this->schemaHandler()->onEntityTypeUpdate($entity_type, $original); + } + + /** + * {@inheritdoc} + */ + public function onEntityTypeDelete(EntityTypeInterface $entity_type) { + $this->schemaHandler()->onEntityTypeDelete($entity_type); + } + + /** + * {@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/EntityManager.php b/core/lib/Drupal/Core/Entity/EntityManager.php index 26eac4e..746b253 100644 --- a/core/lib/Drupal/Core/Entity/EntityManager.php +++ b/core/lib/Drupal/Core/Entity/EntityManager.php @@ -958,6 +958,42 @@ public function getEntityTypeFromClass($class_name) { } /** + * {@inheritdoc} + */ + public function onEntityTypeCreate(EntityTypeInterface $entity_type) { + // @todo Forward this to all interested handlers, not only storage, once + // iterating handlers is possible: https://www.drupal.org/node/2332857. + $storage = $this->getStorage($entity_type->id()); + if ($storage instanceof EntityTypeListenerInterface) { + $storage->onEntityTypeCreate($entity_type); + } + } + + /** + * {@inheritdoc} + */ + public function onEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeInterface $original) { + // @todo Forward this to all interested handlers, not only storage, once + // iterating handlers is possible: https://www.drupal.org/node/2332857. + $storage = $this->getStorage($entity_type->id()); + if ($storage instanceof EntityTypeListenerInterface) { + $storage->onEntityTypeUpdate($entity_type, $original); + } + } + + /** + * {@inheritdoc} + */ + public function onEntityTypeDelete(EntityTypeInterface $entity_type) { + // @todo Forward this to all interested handlers, not only storage, once + // iterating handlers is possible: https://www.drupal.org/node/2332857. + $storage = $this->getStorage($entity_type->id()); + if ($storage instanceof EntityTypeListenerInterface) { + $storage->onEntityTypeDelete($entity_type); + } + } + + /** * Acts on entity bundle rename. * * @param string $entity_type_id diff --git a/core/lib/Drupal/Core/Entity/EntityManagerInterface.php b/core/lib/Drupal/Core/Entity/EntityManagerInterface.php index 5bf03cc..bf52202 100644 --- a/core/lib/Drupal/Core/Entity/EntityManagerInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityManagerInterface.php @@ -12,7 +12,7 @@ /** * Provides an interface for entity type managers. */ -interface EntityManagerInterface extends PluginManagerInterface { +interface EntityManagerInterface extends PluginManagerInterface, EntityTypeListenerInterface { /** * Builds a list of entity type labels suitable for a Form API options list. diff --git a/core/lib/Drupal/Core/Entity/EntityTypeListenerInterface.php b/core/lib/Drupal/Core/Entity/EntityTypeListenerInterface.php new file mode 100644 index 0000000..c42667c --- /dev/null +++ b/core/lib/Drupal/Core/Entity/EntityTypeListenerInterface.php @@ -0,0 +1,43 @@ +entityType = $entity_type; $this->fieldStorageDefinitions = $entity_manager->getFieldStorageDefinitions($entity_type->id()); $this->storage = $storage; + $this->database = $database; } /** * {@inheritdoc} */ - public function getSchema() { - return $this->getEntitySchema($this->entityType); + public function onEntityTypeCreate(EntityTypeInterface $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 onEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeInterface $original) { + // @todo Implement proper updates: https://www.drupal.org/node/1498720. + // Meanwhile, treat a change from non-Sql storage to Sql storage as + // identical to creation with respect to Sql schema handling. + if (!is_subclass_of($original->getStorageClass(), '\Drupal\Core\Entity\Sql\SqlEntityStorageInterface')) { + $this->onEntityTypeCreate($entity_type); + } + } + + /** + * {@inheritdoc} + */ + public function onEntityTypeDelete(EntityTypeInterface $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); + } + } } /** diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlEntityStorageInterface.php b/core/lib/Drupal/Core/Entity/Sql/SqlEntityStorageInterface.php index 107ed26..b0a9b05 100644 --- a/core/lib/Drupal/Core/Entity/Sql/SqlEntityStorageInterface.php +++ b/core/lib/Drupal/Core/Entity/Sql/SqlEntityStorageInterface.php @@ -8,12 +8,11 @@ namespace Drupal\Core\Entity\Sql; use Drupal\Core\Entity\EntityStorageInterface; -use Drupal\Core\Entity\Schema\EntitySchemaProviderInterface; /** * A common interface for SQL-based entity storage implementations. */ -interface SqlEntityStorageInterface extends EntityStorageInterface, EntitySchemaProviderInterface { +interface SqlEntityStorageInterface extends EntityStorageInterface { /** * Gets a table mapping for the entity's SQL tables. diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php index aa36d42..f4c0170 100644 --- a/core/lib/Drupal/Core/Extension/ModuleHandler.php +++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php @@ -12,7 +12,6 @@ use Drupal\Component\Utility\NestedArray; use Drupal\Component\Utility\String; use Drupal\Core\Cache\CacheBackendInterface; -use Drupal\Core\Entity\Schema\EntitySchemaProviderInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -845,19 +844,14 @@ public function install(array $module_list, $enable_dependencies = TRUE) { } drupal_set_installed_schema_version($module, $version); - // Install any entity schemas belonging to the module. + // Notify the entity manager that this module's entity types are new, + // so that it can notify all interested handlers. For example, a + // SQL-based storage handler can use this as an opportunity to create + // the necessary database tables. $entity_manager = \Drupal::entityManager(); - $schema = \Drupal::database()->schema(); foreach ($entity_manager->getDefinitions() as $entity_type) { if ($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); - } - } - } + $entity_manager->onEntityTypeCreate($entity_type); } } @@ -965,19 +959,13 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) { // Remove all configuration belonging to the module. \Drupal::service('config.manager')->uninstall('module', $module); - // Remove any entity schemas belonging to the module. - - $schema = \Drupal::database()->schema(); + // Notify the entity manager that this module's entity types are being + // deleted, so that it can notify all interested handlers. For example, + // a SQL-based storage handler can use this as an opportunity to drop + // the corresponding database tables. foreach ($entity_manager->getDefinitions() as $entity_type) { if ($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); - } - } - } + $entity_manager->onEntityTypeDelete($entity_type); } } 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..c56d5b5 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,15 @@ * 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. $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); - } - } + + // Recreate the original entity type definition, in order to notify the + // manager of what changed. The change of storage backend will trigger + // schema installation. + // @see contact_storage_test_entity_type_alter() + $original = clone $entity_type; + $original->setStorageClass('Drupal\Core\Entity\ContentEntityNullStorage'); + + $entity_manager->onEntityTypeUpdate($entity_type, $original); } diff --git a/core/modules/simpletest/src/KernelTestBase.php b/core/modules/simpletest/src/KernelTestBase.php index 4ef5e10..fe39d46 100644 --- a/core/modules/simpletest/src/KernelTestBase.php +++ b/core/modules/simpletest/src/KernelTestBase.php @@ -11,10 +11,11 @@ use Drupal\Core\Database\Database; use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\DrupalKernel; +use Drupal\Core\Entity\EntityTypeListenerInterface; +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; @@ -377,36 +378,39 @@ protected function installSchema($module, $tables) { /** - * Installs the tables for a specific entity type. + * Installs the storage schema for a specific entity type. * * @param string $entity_type_id * The ID of the entity type. - * - * @throws \RuntimeException - * Thrown when the entity type does not support automatic schema installation. */ 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(); + $entity_type = $entity_manager->getDefinition($entity_type_id); + $entity_manager->onEntityTypeCreate($entity_type); + // For test runs, the most common storage backend is a SQL database. For + // this case, ensure the tables got created. $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); + if ($storage instanceof SqlEntityStorageInterface) { + $tables = $storage->getTableMapping()->getTableNames(); + $db_schema = $this->container->get('database')->schema(); + $all_tables_exist = TRUE; + foreach ($tables as $table) { + if (!$db_schema->tableExists($table)) { + $this->fail(String::format('Installed entity type table for the %entity_type entity type: %table', array( + '%entity_type' => $entity_type_id, + '%table' => $table, + ))); + $all_tables_exist = FALSE; + } + } + if ($all_tables_exist) { + $this->pass(String::format('Installed entity type tables for the %entity_type entity type: %tables', array( + '%entity_type' => $entity_type_id, + '%tables' => '{' . implode('}, {', $tables) . '}', + ))); } - - $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, - ))); } } diff --git a/core/tests/Drupal/Tests/Core/Entity/ContentEntityDatabaseStorageTest.php b/core/tests/Drupal/Tests/Core/Entity/ContentEntityDatabaseStorageTest.php index 51ac5df..fa82039 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\SqlContentEntityStorageSchema; 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::onEntityTypeCreate(). * * @covers ::__construct() - * @covers ::getSchema() - * @covers ::schemaHandler() + * @covers ::onEntityTypeCreate() * @covers ::getTableMapping() */ - public function testGetSchema() { + public function testOnEntityTypeCreate() { $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 - // SqlContentEntityStorageSchema 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 SqlContentEntityStorageSchema($this->entityManager, $this->entityType, $storage, $this->connection); + + $storage + ->expects($this->any()) + ->method('schemaHandler') + ->will($this->returnValue($schema_handler)); + + $storage->onEntityTypeCreate($this->entityType); } /** @@ -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/SqlContentEntityStorageSchemaTest.php b/core/tests/Drupal/Tests/Core/Entity/Schema/SqlContentEntityStorageSchemaTest.php index f1e6a5b..d404b58 100644 --- a/core/tests/Drupal/Tests/Core/Entity/Schema/SqlContentEntityStorageSchemaTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/Schema/SqlContentEntityStorageSchemaTest.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->onEntityTypeCreate($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->onEntityTypeCreate($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->onEntityTypeCreate($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->onEntityTypeCreate($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 SqlContentEntityStorageSchema( - $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 SqlContentEntityStorageSchema($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 @@ +