diff -u b/core/modules/system/src/Tests/Entity/EntityDefinitionUpdateTest.php b/core/modules/system/src/Tests/Entity/EntityDefinitionUpdateTest.php --- b/core/modules/system/src/Tests/Entity/EntityDefinitionUpdateTest.php +++ b/core/modules/system/src/Tests/Entity/EntityDefinitionUpdateTest.php @@ -9,6 +9,8 @@ use Drupal\Core\Entity\EntityStorageException; use Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException; +use Drupal\Core\Field\BaseFieldDefinition; +use Drupal\entity_test\FieldStorageDefinition; /** * Tests EntityDefinitionUpdateManager functionality. @@ -38,17 +40,18 @@ parent::setUp(); $this->entityDefinitionUpdateManager = $this->container->get('entity.definition_update_manager'); $this->database = $this->container->get('database'); + + // Install every entity type's schema that wasn't installed in the parent + // method. + foreach (array_diff_key($this->entityManager->getDefinitions(), array_flip(array('user', 'entity_test'))) as $entity_type_id => $entity_type) { + $this->installEntitySchema($entity_type_id); + } } /** * Tests when no definition update is needed. */ public function testNoUpdates() { - // Install every entity type's schema. - foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) { - $this->installEntitySchema($entity_type_id); - } - // Ensure that the definition update manager reports no updates. $this->assertFalse($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that no updates are needed.'); $this->assertIdentical($this->entityDefinitionUpdateManager->getChangeSummary(), array(), 'EntityDefinitionUpdateManager reports an empty change summary.'); @@ -62,51 +65,36 @@ * Tests updating entity schema when there are no existing entities. */ public function testEntityTypeUpdateWithoutData() { - // Install every entity type's schema. Start with entity_test_rev not - // supporting revisions, and ensure its revision table isn't created. - $this->state->set('entity_test.entity_test_rev.disable_revisable', TRUE); - $this->entityManager->clearCachedDefinitions(); - foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) { - $this->installEntitySchema($entity_type_id); - } - $this->assertFalse($this->database->schema()->tableExists('entity_test_rev_revision'), 'Revision table not created for entity_test_rev.'); - - // Restore entity_test_rev back to supporting revisions and ensure the - // definition update manager reports that an update is needed. - $this->state->delete('entity_test.entity_test_rev.disable_revisable'); + // The 'entity_test_update' entity type starts out non-revisionable, so + // ensure the revision table hasn't been created during setUp(). + $this->assertFalse($this->database->schema()->tableExists('entity_test_update_revision'), 'Revision table not created for entity_test_update.'); + + // Update it to be revisionable and ensure the definition update manager + // reports that an update is needed. + $this->updateEntityTypeToRevisionable(); $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.'); $expected = array( - 'entity_test_rev' => array( - t('Update the %entity_type entity type.', array('%entity_type' => $this->entityManager->getDefinition('entity_test_rev')->getLabel())), + 'entity_test_update' => array( + t('Update the %entity_type entity type.', array('%entity_type' => $this->entityManager->getDefinition('entity_test_update')->getLabel())), ), ); $this->assertIdentical($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.'); // Run the update and ensure the revision table is created. $this->entityDefinitionUpdateManager->applyUpdates(); - $this->assertTrue($this->database->schema()->tableExists('entity_test_rev_revision'), 'Revision table created for entity_test_rev.'); + $this->assertTrue($this->database->schema()->tableExists('entity_test_update_revision'), 'Revision table created for entity_test_update.'); } /** * Tests updating entity schema when there are existing entities. */ public function testEntityTypeUpdateWithData() { - // Install every entity type's schema. Start with entity_test_rev not - // supporting revisions. - $this->state->set('entity_test.entity_test_rev.disable_revisable', TRUE); - $this->entityManager->clearCachedDefinitions(); - foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) { - $entity_type_id = 'entity_test_rev'; - $this->installEntitySchema($entity_type_id); - break; - } - // Save an entity. - $this->entityManager->getStorage('entity_test_rev')->create()->save(); + $this->entityManager->getStorage('entity_test_update')->create()->save(); - // Restore entity_test_rev back to supporting revisions and try to apply - // the update. It's expected to throw an exception. - $this->state->delete('entity_test.entity_test_rev.disable_revisable'); + // Update the entity type to be revisionable and try to apply the update. + // It's expected to throw an exception. + $this->updateEntityTypeToRevisionable(); try { $this->entityDefinitionUpdateManager->applyUpdates(); $this->fail('EntityStorageException thrown when trying to apply an update that requires data migration.'); @@ -117,91 +105,130 @@ } /** - * Tests creating and deleting fields when there are no existing entities. + * Tests creating and deleting a base field when there are no existing entities. */ - public function testFieldCreateDeleteWithoutData() { - // Install every entity type's schema. - foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) { - $this->installEntitySchema($entity_type_id); - } + public function testBaseFieldCreateDeleteWithoutData() { + // Add a base field and ensure the update manager reports the addition. + $this->addBaseField(); + $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.'); + $expected = array( + 'entity_test_update' => array( + t('Create the %field_name field.', array('%field_name' => t('A new base field'))), + ), + ); + $this->assertIdentical($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.'); - // Install the entity_schema_test module, which adds a custom base field - // and a custom code-defined bundle field. Ensure the update manager - // reports those additions. - $this->installModule('entity_schema_test'); + // Run the update and ensure the new base field's column is created. + $this->entityDefinitionUpdateManager->applyUpdates(); + $this->assertTrue($this->database->schema()->fieldExists('entity_test_update', 'new_base_field'), 'Column created in shared table for new_base_field.'); + + // Remove the base field and ensure that field deletions are reported. + $this->removeBaseField(); $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.'); $expected = array( - 'entity_test' => array( - t('Create the %field_name field.', array('%field_name' => t('A custom base field'))), - t('Create the %field_name field.', array('%field_name' => t('A custom bundle field'))), + 'entity_test_update' => array( + t('Delete the %field_name field.', array('%field_name' => t('A new base field'))), ), ); $this->assertIdentical($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.'); - // Run the update and ensure the base and bundle field schema are created. - // The base field uses shared table storage and the bundle field uses - // dedicated table storage. - $this->entityDefinitionUpdateManager->applyUpdates(); - $this->assertTrue($this->database->schema()->fieldExists('entity_test', 'custom_base_field'), 'Column created in shared table for custom_base_field.'); - $this->assertTrue($this->database->schema()->tableExists('entity_test__custom_bundle_field'), 'Dedicated table created for custom_bundle_field.'); - - // Uninstall the entity_schema_test module and ensure that field deletions - // are reported. - $this->uninstallModule('entity_schema_test'); + // Run the update and ensure the base field's column is deleted. + $this->entityDefinitionUpdateManager->applyUpdates(); + $this->assertFalse($this->database->schema()->fieldExists('entity_test_update', 'new_base_field'), 'Column deleted from shared table for new_base_field.'); + } + + /** + * Tests creating and deleting a bundle field when there are no existing entities. + */ + public function testBundleFieldCreateDeleteWithoutData() { + // Add a bundle field and ensure the update manager reports the addition. + $this->addBundleField(); $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.'); $expected = array( - 'entity_test' => array( - t('Delete the %field_name field.', array('%field_name' => t('A custom base field'))), - t('Delete the %field_name field.', array('%field_name' => t('A custom bundle field'))), + 'entity_test_update' => array( + t('Create the %field_name field.', array('%field_name' => t('A new bundle field'))), ), ); $this->assertIdentical($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.'); - // Run the update and ensure the base and bundle field schema are deleted. + // Run the update and ensure the new bundle field's table is created. $this->entityDefinitionUpdateManager->applyUpdates(); - $this->assertFalse($this->database->schema()->fieldExists('entity_test', 'custom_base_field'), 'Column deleted from shared table for custom_base_field.'); - $this->assertFalse($this->database->schema()->tableExists('entity_test__custom_bundle_field'), 'Dedicated table deleted for custom_bundle_field.'); + $this->assertTrue($this->database->schema()->tableExists('entity_test_update__new_bundle_field'), 'Dedicated table created for new_bundle_field.'); + + // Remove the bundle field and ensure that field deletions are reported. + $this->removeBundleField(); + $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.'); + $expected = array( + 'entity_test_update' => array( + t('Delete the %field_name field.', array('%field_name' => t('A new bundle field'))), + ), + ); + $this->assertIdentical($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.'); + + // Run the update and ensure the bundle field's table is deleted. + $this->entityDefinitionUpdateManager->applyUpdates(); + $this->assertFalse($this->database->schema()->tableExists('entity_test_update__new_bundle_field'), 'Dedicated table deleted for new_bundle_field.'); } /** - * Tests creating and deleting fields when there are existing entities. + * Tests creating and deleting a base field when there are existing entities. * - * This tests deletion when there are existing entites, but not existing data + * This tests deletion when there are existing entities, but not existing data * for the field being deleted. * * @see testBaseFieldDeleteWithExistingData() - * @see testBundleFieldDeleteWithExistingData() */ - public function testFieldCreateDeleteWithExistingEntities() { - // Install every entity type's schema. - foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) { - $this->installEntitySchema($entity_type_id); - } + public function testBaseFieldCreateDeleteWithExistingEntities() { + // Save an entity. + $name = $this->randomString(); + $entity = $this->entityManager->getStorage('entity_test_update')->create(array('name' => $name)); + $entity->save(); + + // Add a base field and run the update. Ensure the base field's column is + // created and the prior saved entity data is still there. + $this->addBaseField(); + $this->entityDefinitionUpdateManager->applyUpdates(); + $this->assertTrue($this->database->schema()->fieldExists('entity_test_update', 'new_base_field'), 'Column created in shared table for new_base_field.'); + $entity = $this->entityManager->getStorage('entity_test_update')->load($entity->id()); + $this->assertIdentical($entity->name->value, $name, 'Entity data preserved during field creation.'); + + // Remove the base field and run the update. Ensure the base field's column + // is deleted and the prior saved entity data is still there. + $this->removeBaseField(); + $this->entityDefinitionUpdateManager->applyUpdates(); + $this->assertFalse($this->database->schema()->fieldExists('entity_test_update', 'new_base_field'), 'Column deleted from shared table for new_base_field.'); + $entity = $this->entityManager->getStorage('entity_test_update')->load($entity->id()); + $this->assertIdentical($entity->name->value, $name, 'Entity data preserved during field deletion.'); + } + /** + * Tests creating and deleting a bundle field when there are existing entities. + * + * This tests deletion when there are existing entities, but not existing data + * for the field being deleted. + * + * @see testBundleFieldDeleteWithExistingData() + */ + public function testBundleFieldCreateDeleteWithExistingEntities() { // Save an entity. $name = $this->randomString(); - $entity = $this->entityManager->getStorage('entity_test')->create(array('name' => $name)); + $entity = $this->entityManager->getStorage('entity_test_update')->create(array('name' => $name)); $entity->save(); - // Install the entity_schema_test module, which adds a custom base field - // and a custom code-defined bundle field. Run the update. Ensure the base - // field's column and the bundle field's table are created. Also ensure the - // prior saved entity data is still there. - $this->installModule('entity_schema_test'); - $this->entityDefinitionUpdateManager->applyUpdates(); - $this->assertTrue($this->database->schema()->fieldExists('entity_test', 'custom_base_field'), 'Column created in shared table for custom_base_field.'); - $this->assertTrue($this->database->schema()->tableExists('entity_test__custom_bundle_field'), 'Dedicated table created for custom_bundle_field.'); - $entity = $this->entityManager->getStorage('entity_test')->load($entity->id()); + // Add a bundle field and run the update. Ensure the bundle field's table + // is created and the prior saved entity data is still there. + $this->addBundleField(); + $this->entityDefinitionUpdateManager->applyUpdates(); + $this->assertTrue($this->database->schema()->tableExists('entity_test_update__new_bundle_field'), 'Dedicated table created for new_bundle_field.'); + $entity = $this->entityManager->getStorage('entity_test_update')->load($entity->id()); $this->assertIdentical($entity->name->value, $name, 'Entity data preserved during field creation.'); - // Uninstall the entity_schema_test module. Run the update. Ensure the base - // field's column and the bundle field's table are deleted. Also ensure the - // prior saved entity data is still there. - $this->uninstallModule('entity_schema_test'); - $this->entityDefinitionUpdateManager->applyUpdates(); - $this->assertFalse($this->database->schema()->fieldExists('entity_test', 'custom_base_field'), 'Column deleted from shared table for custom_base_field.'); - $this->assertFalse($this->database->schema()->tableExists('entity_test__custom_bundle_field'), 'Dedicated table deleted for custom_bundle_field.'); - $entity = $this->entityManager->getStorage('entity_test')->load($entity->id()); + // Remove the base field and run the update. Ensure the bundle field's + // table is deleted and the prior saved entity data is still there. + $this->removeBundleField(); + $this->entityDefinitionUpdateManager->applyUpdates(); + $this->assertFalse($this->database->schema()->tableExists('entity_test_update__new_bundle_field'), 'Dedicated table deleted for new_bundle_field.'); + $entity = $this->entityManager->getStorage('entity_test_update')->load($entity->id()); $this->assertIdentical($entity->name->value, $name, 'Entity data preserved during field deletion.'); } @@ -209,22 +236,18 @@ * Tests deleting a base field when it has existing data. */ public function testBaseFieldDeleteWithExistingData() { - // Install every entity type's schema. - foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) { - $this->installEntitySchema($entity_type_id); - } - - // Install the module and run the update. - $this->installModule('entity_schema_test'); + // Add the base field and run the update. + $this->addBaseField(); $this->entityDefinitionUpdateManager->applyUpdates(); // Save an entity with the base field populated. - $this->entityManager->getStorage('entity_test')->create(array('custom_base_field' => 'foo'))->save(); + $this->entityManager->getStorage('entity_test_update')->create(array('new_base_field' => 'foo'))->save(); - // Uninstall and apply updates. It's expected to throw an exception. + // Remove the base field and apply updates. It's expected to throw an + // exception. // @todo Revisit that expectation once purging is implemented for // all fields: https://www.drupal.org/node/2282119. - $this->uninstallModule('entity_schema_test'); + $this->removeBaseField(); try { $this->entityDefinitionUpdateManager->applyUpdates(); $this->fail('FieldStorageDefinitionUpdateForbiddenException thrown when trying to apply an update that deletes a non-purgeable field with data.'); @@ -238,23 +261,19 @@ * Tests deleting a bundle field when it has existing data. */ public function testBundleFieldDeleteWithExistingData() { - // Install every entity type's schema. - foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) { - $this->installEntitySchema($entity_type_id); - } - - // Install the module and run the update. - $this->installModule('entity_schema_test'); + // Add the bundle field and run the update. + $this->addBundleField(); $this->entityDefinitionUpdateManager->applyUpdates(); // Save an entity with the bundle field populated. entity_test_create_bundle('custom'); - $this->entityManager->getStorage('entity_test')->create(array('type' => 'custom', 'custom_bundle_field' => 'foo'))->save(); + $this->entityManager->getStorage('entity_test_update')->create(array('type' => 'test_bundle', 'new_bundle_field' => 'foo'))->save(); - // Uninstall and apply updates. It's expected to throw an exception. + // Remove the bundle field and apply updates. It's expected to throw an + // exception. // @todo Revisit that expectation once purging is implemented for // all fields: https://www.drupal.org/node/2282119. - $this->uninstallModule('entity_schema_test'); + $this->removeBundleField(); try { $this->entityDefinitionUpdateManager->applyUpdates(); $this->fail('FieldStorageDefinitionUpdateForbiddenException thrown when trying to apply an update that deletes a non-purgeable field with data.'); @@ -265,12 +284,56 @@ } /** - * {@inheritdoc} + * Updates the 'entity_test_update' entity type to revisionable. */ - protected function refreshServices() { - parent::refreshServices(); - $this->entityDefinitionUpdateManager = $this->container->get('entity.definition_update_manager'); - $this->database = $this->container->get('database'); + protected function updateEntityTypeToRevisionable() { + $entity_type = clone $this->entityManager->getDefinition('entity_test_update'); + $keys = $entity_type->getKeys(); + $keys['revision'] = 'revision_id'; + $entity_type->set('entity_keys', $keys); + $this->state->set('entity_test_update.entity_type', $entity_type); + $this->entityManager->clearCachedDefinitions(); + } + + /** + * Adds a new base field to the 'entity_test_update' entity type. + */ + protected function addBaseField() { + $definitions['new_base_field'] = BaseFieldDefinition::create('string') + ->setName('new_base_field') + ->setLabel(t('A new base field')); + $this->state->set('entity_test_update.additional_base_field_definitions', $definitions); + $this->entityManager->clearCachedDefinitions(); + } + + /** + * Removes the new base field from the 'entity_test_update' entity type. + */ + protected function removeBaseField() { + $this->state->delete('entity_test_update.additional_base_field_definitions'); + $this->entityManager->clearCachedDefinitions(); + } + + /** + * Adds a new bundle field to the 'entity_test_update' entity type. + */ + protected function addBundleField() { + $definitions['new_bundle_field'] = FieldStorageDefinition::create('string') + ->setName('new_bundle_field') + ->setLabel(t('A new bundle field')) + ->setTargetEntityTypeId('entity_test_update'); + $this->state->set('entity_test_update.additional_field_storage_definitions', $definitions); + $this->state->set('entity_test_update.additional_bundle_field_definitions.test_bundle', $definitions); + $this->entityManager->clearCachedDefinitions(); + } + + /** + * Removes the new bundle field from the 'entity_test_update' entity type. + */ + protected function removeBundleField() { + $this->state->delete('entity_test_update.additional_field_storage_definitions'); + $this->state->delete('entity_test_update.additional_bundle_field_definitions.test_bundle'); + $this->entityManager->clearCachedDefinitions(); } } diff -u b/core/modules/system/tests/modules/entity_schema_test/entity_schema_test.module b/core/modules/system/tests/modules/entity_schema_test/entity_schema_test.module --- b/core/modules/system/tests/modules/entity_schema_test/entity_schema_test.module +++ b/core/modules/system/tests/modules/entity_schema_test/entity_schema_test.module @@ -7,7 +7,7 @@ use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Field\BaseFieldDefinition; -use Drupal\entity_schema_test\FieldStorageDefinition; +use Drupal\entity_test\FieldStorageDefinition; use Drupal\entity_test\Entity\EntityTestMulRev; /** @@ -44,7 +44,7 @@ */ function entity_schema_test_entity_field_storage_info(EntityTypeInterface $entity_type) { if ($entity_type->id() == 'entity_test') { - $definitions['custom_bundle_field'] = FieldStorageDefinition ::create('string') + $definitions['custom_bundle_field'] = FieldStorageDefinition::create('string') ->setName('custom_bundle_field') ->setLabel(t('A custom bundle field')) ->setTargetEntityTypeId($entity_type->id()); reverted: --- b/core/modules/system/tests/modules/entity_schema_test/src/FieldStorageDefinition.php +++ /dev/null @@ -1,29 +0,0 @@ -get('entity_test.entity_test_rev.disable_revisable')) { - $keys = $entity_types['entity_test_rev']->getKeys(); - unset($keys['revision']); - $entity_types['entity_test_rev']->set('entity_keys', $keys); - } + // Allow entity_test_update tests to override the entity type definition. + $entity_types['entity_test_update'] = \Drupal::state()->get('entity_test_update.entity_type', $entity_types['entity_test_update']); } /** @@ -97,6 +92,15 @@ function entity_test_entity_base_field_info_alter(&$fields, EntityTypeInterface } /** + * Implements hook_entity_field_storage_info(). + */ +function entity_test_entity_field_storage_info(EntityTypeInterface $entity_type) { + if ($entity_type->id() == 'entity_test_update') { + return \Drupal::state()->get('entity_test_update.additional_field_storage_definitions', array()); + } +} + +/** * Creates a new bundle for entity_test entities. * * @param string $bundle only in patch2: unchanged: --- /dev/null +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestUpdate.php @@ -0,0 +1,55 @@ +get('entity_test_update.additional_base_field_definitions', array()); + return $fields; + } + + /** + * {@inheritdoc} + */ + public static function bundleFieldDefinitions(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) { + $fields = parent::bundleFieldDefinitions($entity_type, $bundle, $base_field_definitions); + $fields += \Drupal::state()->get('entity_test_update.additional_bundle_field_definitions.' . $bundle, array()); + return $fields; + } + +} only in patch2: unchanged: --- /dev/null +++ b/core/modules/system/tests/modules/entity_test/src/FieldStorageDefinition.php @@ -0,0 +1,30 @@ +