Commit e1984e60 authored by effulgentsia's avatar effulgentsia

Issue #2520526 by alexpott, dawehner, pwolanin, nlisgo, Eli-T,...

Issue #2520526 by alexpott, dawehner, pwolanin, nlisgo, Eli-T, claudiu.cristea, catch, cosmicdreams, Wim Leers, yched, jibran, Gábor Hojtsy, YesCT, swentel, heddn, xjm: Calculate configuration entity dependencies on install
parent 525430b7
......@@ -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
......
......@@ -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.
......
......@@ -267,6 +267,13 @@ public static function calculateDependencies(FieldDefinitionInterface $field_def
return array();
}
/**
* {@inheritdoc}
*/
public static function calculateStorageDependencies(FieldStorageDefinitionInterface $field_definition) {
return [];
}
/**
* {@inheritdoc}
*/
......
......@@ -418,6 +418,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.
*
......
......@@ -445,6 +445,16 @@ public static function calculateDependencies(FieldDefinitionInterface $field_def
return $dependencies;
}
/**
* {@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}
*/
......
langcode: en
status: false
dependencies:
module:
- node
enforced:
module:
- book
module:
- node
id: node.print
label: Print
targetEntityType: node
......
......@@ -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]);
}
/**
......
<?php
/**
* @file
* Provides hook implementations for testing purposes.
*/
/**
* Implements hook_ENTITY_TYPE_create.
*/
function config_install_dependency_test_config_test_create(\Drupal\Core\Entity\EntityInterface $entity) {
// Add an enforced dependency on this module so that we can test if this is
// possible during module installation.
$entity->setEnforcedDependencies(['module' => ['config_install_dependency_test']]);
}
......@@ -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());
......
......@@ -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;
......@@ -39,16 +42,25 @@ 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;
}
}
......@@ -8,7 +8,6 @@ dependencies:
- node.type.forum
module:
- comment
- taxonomy
- text
- user
id: node.forum.default
......
......@@ -8,7 +8,6 @@ dependencies:
- field.field.node.forum.taxonomy_forums
- node.type.forum
module:
- taxonomy
- text
- user
id: node.forum.teaser
......
......@@ -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
......
......@@ -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());
}
}
<?php
/**
* @file
* Contains \Drupal\system\Tests\Update\RecalculatedDependencyTest.
*/
namespace Drupal\system\Tests\Update;
/**
* Tests system_post_update_recalculate_dependencies_for_installed_config_entities().
*
* @group Update
*/
class RecalculatedDependencyTest extends UpdatePathTestBase {
/**
* {@inheritdoc}
*/
protected function setDatabaseDumpFiles() {
$this->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']);
}
}
......@@ -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'));
......
......@@ -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');
}
/**
......
......@@ -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
......
......@@ -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
......
......@@ -2,6 +2,7 @@ langcode: en
status: true
dependencies:
module:
- file
- image
- user
id: user.user_picture
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment