diff --git a/core/includes/update.inc b/core/includes/update.inc index 88e843a..24eeddf 100644 --- a/core/includes/update.inc +++ b/core/includes/update.inc @@ -242,7 +242,9 @@ function update_invoke_post_update($function, &$context) { $ret['results']['query'] = $function($context['sandbox']); $ret['results']['success'] = TRUE; - \Drupal::service('update.post_update_registry')->registerInvokedUpdates([$function]); + if (!isset($context['sandbox']['#finished']) || (isset($context['sandbox']['#finished']) && $context['sandbox']['#finished'] >= 1)) { + \Drupal::service('update.post_update_registry')->registerInvokedUpdates([$function]); + } } // @TODO We may want to do different error handling for different exception // types, but for now we'll just log the exception and return the message diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php index 4dd5231..7e5c3a4 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php @@ -342,7 +342,7 @@ public function preSave(EntityStorageInterface $storage) { throw new ConfigDuplicateUUIDException("Attempt to save a configuration entity '{$this->id()}' with UUID '{$this->uuid()}' when this entity already exists with UUID '{$original->uuid()}'"); } } - if (!$this->isSyncing() && !$this->trustedData) { + if (!$this->isSyncing()) { // Ensure the correct dependencies are present. If the configuration is // being written during a configuration synchronization then there is no // need to recalculate the dependencies. diff --git a/core/lib/Drupal/Core/Field/FieldItemBase.php b/core/lib/Drupal/Core/Field/FieldItemBase.php index 8e50c6a..c73c7fd 100644 --- a/core/lib/Drupal/Core/Field/FieldItemBase.php +++ b/core/lib/Drupal/Core/Field/FieldItemBase.php @@ -270,6 +270,13 @@ public static function calculateDependencies(FieldDefinitionInterface $field_def /** * {@inheritdoc} */ + public static function calculateStorageDependencies(FieldStorageDefinitionInterface $field_definition) { + return []; + } + + /** + * {@inheritdoc} + */ public static function onDependencyRemoval(FieldDefinitionInterface $field_definition, array $dependencies) { return FALSE; } diff --git a/core/lib/Drupal/Core/Field/FieldItemInterface.php b/core/lib/Drupal/Core/Field/FieldItemInterface.php index 23e3757..ad42da5 100644 --- a/core/lib/Drupal/Core/Field/FieldItemInterface.php +++ b/core/lib/Drupal/Core/Field/FieldItemInterface.php @@ -419,6 +419,39 @@ public function fieldSettingsForm(array $form, FormStateInterface $form_state); public static function calculateDependencies(FieldDefinitionInterface $field_definition); /** + * Calculates dependencies for field items on the storage level. + * + * Dependencies are saved in the field storage configuration entity and are + * used to determine configuration synchronization order. For example, if the + * field type storage depends on a particular entity type, this method should + * return an array of dependencies listing the module that provides the entity + * type. + * + * Dependencies returned from this method are stored in field storage + * configuration and are always considered hard dependencies. If the + * dependency is removed the field storage configuration must be deleted. + * + * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $field_storage_definition + * The field storage definition. + * + * @return array + * An array of dependencies grouped by type (config, content, module, + * theme). For example: + * @code + * [ + * 'config' => ['user.role.anonymous', 'user.role.authenticated'], + * 'content' => ['node:article:f0a189e6-55fb-47fb-8005-5bef81c44d6d'], + * 'module' => ['node', 'user'], + * 'theme' => ['seven'], + * ]; + * @endcode + * + * @see \Drupal\Core\Config\Entity\ConfigDependencyManager + * @see \Drupal\Core\Config\Entity\ConfigEntityInterface::getConfigDependencyName() + */ + public static function calculateStorageDependencies(FieldStorageDefinitionInterface $field_storage_definition); + + /** * Informs the plugin that a dependency of the field will be deleted. * * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php index 66c0b00..9e76afb 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php @@ -448,6 +448,16 @@ public static function calculateDependencies(FieldDefinitionInterface $field_def /** * {@inheritdoc} */ + public static function calculateStorageDependencies(FieldStorageDefinitionInterface $field_definition) { + $dependencies = parent::calculateStorageDependencies($field_definition); + $target_entity_type = \Drupal::entityManager()->getDefinition($field_definition->getSetting('target_type')); + $dependencies['module'][] = $target_entity_type->getProvider(); + return $dependencies; + } + + /** + * {@inheritdoc} + */ public static function onDependencyRemoval(FieldDefinitionInterface $field_definition, array $dependencies) { $changed = parent::onDependencyRemoval($field_definition, $dependencies); $entity_manager = \Drupal::entityManager(); diff --git a/core/modules/book/config/install/core.entity_view_mode.node.print.yml b/core/modules/book/config/install/core.entity_view_mode.node.print.yml index d695ac5..d615b03 100644 --- a/core/modules/book/config/install/core.entity_view_mode.node.print.yml +++ b/core/modules/book/config/install/core.entity_view_mode.node.print.yml @@ -1,11 +1,11 @@ langcode: en status: false dependencies: - module: - - node enforced: module: - book + module: + - node id: node.print label: Print targetEntityType: node diff --git a/core/modules/config/src/Tests/ConfigInstallTest.php b/core/modules/config/src/Tests/ConfigInstallTest.php index 2d09548..7358e65 100644 --- a/core/modules/config/src/Tests/ConfigInstallTest.php +++ b/core/modules/config/src/Tests/ConfigInstallTest.php @@ -211,7 +211,11 @@ public function testDependencyChecking() { } $this->installModules(['config_other_module_config_test']); $this->installModules(['config_install_dependency_test']); - $this->assertTrue(entity_load('config_test', 'other_module_test_with_dependency'), 'The config_test.dynamic.other_module_test_with_dependency configuration has been created during install.'); + $entity = \Drupal::entityManager()->getStorage('config_test')->load('other_module_test_with_dependency'); + $this->assertTrue($entity, 'The config_test.dynamic.other_module_test_with_dependency configuration has been created during install.'); + // Ensure that dependencies can be added during module installation by + // hooks. + $this->assertIdentical('config_install_dependency_test', $entity->getDependencies()['module'][0]); } /** diff --git a/core/modules/config/tests/config_install_dependency_test/config_install_dependency_test.module b/core/modules/config/tests/config_install_dependency_test/config_install_dependency_test.module new file mode 100644 index 0000000..08a1697 --- /dev/null +++ b/core/modules/config/tests/config_install_dependency_test/config_install_dependency_test.module @@ -0,0 +1,15 @@ +setEnforcedDependencies(['module' => ['config_install_dependency_test']]); +} diff --git a/core/modules/field/src/Entity/FieldStorageConfig.php b/core/modules/field/src/Entity/FieldStorageConfig.php index 594fb3b..d4c2922 100644 --- a/core/modules/field/src/Entity/FieldStorageConfig.php +++ b/core/modules/field/src/Entity/FieldStorageConfig.php @@ -343,6 +343,11 @@ public function calculateDependencies() { parent::calculateDependencies(); // Ensure the field is dependent on the providing module. $this->addDependency('module', $this->getTypeProvider()); + // Ask the field type for any additional storage dependencies. + // @see \Drupal\Core\Field\FieldItemInterface::calculateStorageDependencies() + $definition = \Drupal::service('plugin.manager.field.field_type')->getDefinition($this->getType(), FALSE); + $this->addDependencies($definition['class']::calculateStorageDependencies($this)); + // Ensure the field is dependent on the provider of the entity type. $entity_type = \Drupal::entityManager()->getDefinition($this->entity_type); $this->addDependency('module', $entity_type->getProvider()); diff --git a/core/modules/field/tests/src/Unit/FieldStorageConfigEntityUnitTest.php b/core/modules/field/tests/src/Unit/FieldStorageConfigEntityUnitTest.php index ab5fa75..e41615b 100644 --- a/core/modules/field/tests/src/Unit/FieldStorageConfigEntityUnitTest.php +++ b/core/modules/field/tests/src/Unit/FieldStorageConfigEntityUnitTest.php @@ -8,6 +8,9 @@ namespace Drupal\Tests\field\Unit; use Drupal\Core\DependencyInjection\ContainerBuilder; +use Drupal\Core\Field\FieldItemBase; +use Drupal\Core\Field\FieldStorageDefinitionInterface; +use Drupal\Core\Field\FieldTypePluginManagerInterface; use Drupal\field\Entity\FieldStorageConfig; use Drupal\Tests\UnitTestCase; @@ -40,15 +43,24 @@ class FieldStorageConfigEntityUnitTest extends UnitTestCase { protected $uuid; /** + * The field type manager. + * + * @var \Drupal\Core\Field\FieldTypePluginManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $fieldTypeManager; + + /** * {@inheritdoc} */ protected function setUp() { $this->entityManager = $this->getMock('\Drupal\Core\Entity\EntityManagerInterface'); $this->uuid = $this->getMock('\Drupal\Component\Uuid\UuidInterface'); + $this->fieldTypeManager = $this->getMock(FieldTypePluginManagerInterface::class); $container = new ContainerBuilder(); $container->set('entity.manager', $this->entityManager); $container->set('uuid', $this->uuid); + $container->set('plugin.manager.field.field_type', $this->fieldTypeManager); \Drupal::setContainer($container); } @@ -73,29 +85,50 @@ public function testCalculateDependencies() { // ConfigEntityBase::addDependency() to get the provider of the field config // entity type and once in FieldStorageConfig::calculateDependencies() to // get the provider of the entity type that field is attached to. - $this->entityManager->expects($this->at(0)) - ->method('getDefinition') - ->with('field_storage_config') - ->will($this->returnValue($fieldStorageConfigentityType)); - $this->entityManager->expects($this->at(1)) + $this->entityManager->expects($this->any()) ->method('getDefinition') - ->with($attached_entity_type_id) - ->will($this->returnValue($attached_entity_type)); - $this->entityManager->expects($this->at(2)) + ->willReturnMap([ + ['field_storage_config', TRUE, $fieldStorageConfigentityType], + [$attached_entity_type_id, TRUE, $attached_entity_type], + ]); + + $this->fieldTypeManager->expects($this->atLeastOnce()) ->method('getDefinition') - ->with('field_storage_config') - ->will($this->returnValue($fieldStorageConfigentityType)); + ->with('test_field_type', FALSE) + ->willReturn([ + 'class' => TestFieldType::class, + ]); - $field_storage = new FieldStorageConfig(array( + $field_storage = new FieldStorageConfig([ 'entity_type' => $attached_entity_type_id, 'field_name' => 'test_field', 'type' => 'test_field_type', 'module' => 'test_module', - )); + ]); $dependencies = $field_storage->calculateDependencies()->getDependencies(); - $this->assertContains('test_module', $dependencies['module']); - $this->assertContains('entity_provider_module', $dependencies['module']); + $this->assertEquals(['entity_provider_module', 'entity_test', 'test_module'], $dependencies['module']); + $this->assertEquals(['stark'], $dependencies['theme']); + } + +} + +/** + * A test class to test field storage dependencies. + * + * @see \Drupal\Core\Field\FieldItemInterface::calculateStorageDependencies() + */ +class TestFieldType { + + /** + * {@inheritdoc} + */ + public static function calculateStorageDependencies(FieldStorageDefinitionInterface $field_definition) { + $dependencies = []; + $dependencies['module'] = ['entity_test']; + $dependencies['theme'] = ['stark']; + + return $dependencies; } } diff --git a/core/modules/forum/config/install/core.entity_view_display.node.forum.default.yml b/core/modules/forum/config/install/core.entity_view_display.node.forum.default.yml index 69fdb79..39389a5 100644 --- a/core/modules/forum/config/install/core.entity_view_display.node.forum.default.yml +++ b/core/modules/forum/config/install/core.entity_view_display.node.forum.default.yml @@ -8,7 +8,6 @@ dependencies: - node.type.forum module: - comment - - taxonomy - text - user id: node.forum.default diff --git a/core/modules/forum/config/install/core.entity_view_display.node.forum.teaser.yml b/core/modules/forum/config/install/core.entity_view_display.node.forum.teaser.yml index 6eb1a0c..4405e71 100644 --- a/core/modules/forum/config/install/core.entity_view_display.node.forum.teaser.yml +++ b/core/modules/forum/config/install/core.entity_view_display.node.forum.teaser.yml @@ -8,7 +8,6 @@ dependencies: - field.field.node.forum.taxonomy_forums - node.type.forum module: - - taxonomy - text - user id: node.forum.teaser diff --git a/core/modules/forum/config/install/field.field.node.forum.taxonomy_forums.yml b/core/modules/forum/config/install/field.field.node.forum.taxonomy_forums.yml index 1f7bc5c..f2a30f2 100644 --- a/core/modules/forum/config/install/field.field.node.forum.taxonomy_forums.yml +++ b/core/modules/forum/config/install/field.field.node.forum.taxonomy_forums.yml @@ -4,8 +4,7 @@ dependencies: config: - field.storage.node.taxonomy_forums - node.type.forum - module: - - taxonomy + - taxonomy.vocabulary.forums id: node.forum.taxonomy_forums field_name: taxonomy_forums entity_type: node diff --git a/core/modules/system/src/Tests/Entity/EntityReferenceFieldTest.php b/core/modules/system/src/Tests/Entity/EntityReferenceFieldTest.php index b78dff29..8e64386 100644 --- a/core/modules/system/src/Tests/Entity/EntityReferenceFieldTest.php +++ b/core/modules/system/src/Tests/Entity/EntityReferenceFieldTest.php @@ -14,6 +14,7 @@ use Drupal\Core\Field\FieldStorageDefinitionInterface; use Drupal\field\Tests\EntityReference\EntityReferenceTestTrait; use Drupal\field\Entity\FieldConfig; +use Drupal\field\Entity\FieldStorageConfig; use Drupal\user\Entity\Role; use Drupal\user\Entity\User; use Drupal\user\RoleInterface; @@ -406,4 +407,35 @@ public function testTargetEntityNoLoad() { } } + /** + * Tests the dependencies entity reference fields are created with. + */ + public function testEntityReferenceFieldDependencies() { + $field_name = 'user_reference_field'; + $entity_type = 'entity_test'; + + $field_storage = FieldStorageConfig::create([ + 'field_name' => $field_name, + 'type' => 'entity_reference', + 'entity_type' => $entity_type, + 'settings' => [ + 'target_type' => 'user', + ], + ]); + $field_storage->save(); + $this->assertEqual(['module' => ['entity_test', 'user']], $field_storage->getDependencies()); + + $field = FieldConfig::create([ + 'field_name' => $field_name, + 'entity_type' => $entity_type, + 'bundle' => 'entity_test', + 'label' => $field_name, + 'settings' => [ + 'handler' => 'default', + ], + ]); + $field->save(); + $this->assertEqual(['config' => ['field.storage.entity_test.user_reference_field'], 'module' => ['entity_test']], $field->getDependencies()); + } + } diff --git a/core/modules/system/src/Tests/Update/RecalculatedDependencyTest.php b/core/modules/system/src/Tests/Update/RecalculatedDependencyTest.php new file mode 100644 index 0000000..4a02723 --- /dev/null +++ b/core/modules/system/src/Tests/Update/RecalculatedDependencyTest.php @@ -0,0 +1,77 @@ +databaseDumpFiles = [ + __DIR__ . '/../../../tests/fixtures/update/drupal-8.bare.standard.php.gz', + ]; + } + + /** + * Ensures that the entities are resaved so they have the new dependency. + */ + public function testUpdate() { + // Test the configuration pre update. + $data = \Drupal::config('field.field.node.article.field_tags')->get(); + $this->assertEqual(['entity_reference'], $data['dependencies']['module']); + $this->assertEqual([ + 'field.storage.node.field_tags', + 'node.type.article', + ], $data['dependencies']['config']); + + $data = \Drupal::config('field.field.user.user.user_picture')->get(); + $this->assertFalse(isset($data['dependencies']['module'])); + + $data = \Drupal::config('field.storage.node.field_image')->get(); + $this->assertEqual(['node', 'image'], $data['dependencies']['module']); + + // Explicitly break an optional configuration dependencies to ensure it is + // recalculated. Use active configuration storage directly so that no events + // are fired. + $config_storage = \Drupal::service('config.storage'); + $data = $config_storage->read('search.page.node_search'); + unset($data['dependencies']); + $config_storage->write('search.page.node_search', $data); + // Ensure the update is successful. + $data = \Drupal::config('search.page.node_search')->get(); + $this->assertFalse(isset($data['dependencies']['module'])); + + // Run the updates. + $this->runUpdates(); + + // Test the configuration post update. + $data = \Drupal::config('field.field.node.article.field_tags')->get(); + $this->assertFalse(isset($data['dependencies']['module'])); + $this->assertEqual([ + 'field.storage.node.field_tags', + 'node.type.article', + 'taxonomy.vocabulary.tags' + ], $data['dependencies']['config']); + + $data = \Drupal::config('field.field.user.user.user_picture')->get(); + $this->assertEqual(['image', 'user'], $data['dependencies']['module']); + + $data = \Drupal::config('field.storage.node.field_image')->get(); + $this->assertEqual(['file', 'image', 'node'], $data['dependencies']['module']); + + $data = \Drupal::config('search.page.node_search')->get(); + $this->assertEqual(['node'], $data['dependencies']['module']); + } + +} diff --git a/core/modules/system/src/Tests/Update/UpdatePostUpdateTest.php b/core/modules/system/src/Tests/Update/UpdatePostUpdateTest.php index 1d6375e..67b2ba2 100644 --- a/core/modules/system/src/Tests/Update/UpdatePostUpdateTest.php +++ b/core/modules/system/src/Tests/Update/UpdatePostUpdateTest.php @@ -63,7 +63,7 @@ public function testPostUpdate() { 'block_post_update_disable_blocks_with_missing_contexts', 'field_post_update_save_custom_storage_property', 'field_post_update_entity_reference_handler_setting', - 'system_post_update_fix_enforced_dependencies', + 'system_post_update_recalculate_configuration_entity_dependencies', 'views_post_update_update_cacheability_metadata', ], $updates); $this->assertEqual($updates, $key_value->get('existing_updates')); diff --git a/core/modules/system/system.post_update.php b/core/modules/system/system.post_update.php index 3a141ce..b75625c 100644 --- a/core/modules/system/system.post_update.php +++ b/core/modules/system/system.post_update.php @@ -11,23 +11,31 @@ */ /** - * Re-save all config objects with enforced dependencies. + * Re-save all configuration entities to recalculate dependencies. */ -function system_post_update_fix_enforced_dependencies() { - $config_factory = \Drupal::configFactory(); +function system_post_update_recalculate_configuration_entity_dependencies(&$sandbox = NULL) { + if (!isset($sandbox['config_names'])) { + $sandbox['config_names'] = \Drupal::configFactory()->listAll(); + $sandbox['count'] = count($sandbox['config_names']); + } /** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */ $config_manager = \Drupal::service('config.manager'); - // Iterate on all configuration entities. - foreach ($config_factory->listAll() as $id) { - $config = $config_factory->get($id); - if ($config->get('dependencies.enforced') !== NULL) { - // Resave the configuration entity. - $entity = $config_manager->loadConfigEntityByName($id); + + $count = 0; + foreach ($sandbox['config_names'] as $key => $config_name) { + if ($entity = $config_manager->loadConfigEntityByName($config_name)) { $entity->save(); } + unset($sandbox['config_names'][$key]); + $count++; + // Do 50 at a time. + if ($count == 50) { + break; + } } - return t('All configuration objects with enforced dependencies re-saved.'); + $sandbox['#finished'] = empty($sandbox['config_names']) ? 1 : ($sandbox['count'] - count($sandbox['config_names'])) / $sandbox['count']; + return t('Configuration dependencies recalculated'); } /** diff --git a/core/profiles/standard/config/install/field.field.node.article.field_tags.yml b/core/profiles/standard/config/install/field.field.node.article.field_tags.yml index c49c64f..1b9c4cc 100644 --- a/core/profiles/standard/config/install/field.field.node.article.field_tags.yml +++ b/core/profiles/standard/config/install/field.field.node.article.field_tags.yml @@ -4,6 +4,7 @@ dependencies: config: - field.storage.node.field_tags - node.type.article + - taxonomy.vocabulary.tags id: node.article.field_tags field_name: field_tags entity_type: node diff --git a/core/profiles/standard/config/install/field.storage.node.field_image.yml b/core/profiles/standard/config/install/field.storage.node.field_image.yml index 8c2f981..e4da708 100644 --- a/core/profiles/standard/config/install/field.storage.node.field_image.yml +++ b/core/profiles/standard/config/install/field.storage.node.field_image.yml @@ -2,8 +2,9 @@ langcode: en status: true dependencies: module: - - node + - file - image + - node id: node.field_image field_name: field_image entity_type: node diff --git a/core/profiles/standard/config/install/field.storage.user.user_picture.yml b/core/profiles/standard/config/install/field.storage.user.user_picture.yml index 2d47c28..8253628 100644 --- a/core/profiles/standard/config/install/field.storage.user.user_picture.yml +++ b/core/profiles/standard/config/install/field.storage.user.user_picture.yml @@ -2,6 +2,7 @@ langcode: en status: true dependencies: module: + - file - image - user id: user.user_picture