Issue #2310093 by alexpott, hussainweb : Fixed Config install and import should map from storage record not set properties directly.

8.0.x
Nathaniel Catchpole 2014-10-10 19:47:54 +01:00
parent 0803312d9a
commit ac9054bed0
21 changed files with 398 additions and 31 deletions

View File

@ -225,16 +225,12 @@ class ConfigInstaller implements ConfigInstallerInterface {
if ($this->getActiveStorage($collection)->exists($name)) {
$id = $entity_storage->getIDFromConfigName($name, $entity_storage->getEntityType()->getConfigPrefix());
$entity = $entity_storage->load($id);
foreach ($new_config->get() as $property => $value) {
$entity->set($property, $value);
}
$entity->save();
$entity = $entity_storage->updateFromStorageRecord($entity, $new_config->get());
}
else {
$entity_storage
->create($new_config->get())
->save();
$entity = $entity_storage->createFromStorageRecord($new_config->get());
}
$entity->save();
}
else {
$new_config->save();

View File

@ -347,7 +347,7 @@ class ConfigEntityStorage extends EntityStorageBase implements ConfigEntityStora
* {@inheritdoc}
*/
public function importCreate($name, Config $new_config, Config $old_config) {
$entity = $this->create($new_config->get());
$entity = $this->createFromStorageRecord($new_config->get());
$entity->setSyncing(TRUE);
$entity->save();
return TRUE;
@ -363,16 +363,7 @@ class ConfigEntityStorage extends EntityStorageBase implements ConfigEntityStora
throw new ConfigImporterException(String::format('Attempt to update non-existing entity "@id".', array('@id' => $id)));
}
$entity->setSyncing(TRUE);
$entity->original = clone $entity;
foreach ($old_config->get() as $property => $value) {
$entity->original->set($property, $value);
}
foreach ($new_config->get() as $property => $value) {
$entity->set($property, $value);
}
$entity = $this->updateFromStorageRecord($entity, $new_config->get());
$entity->save();
return TRUE;
}
@ -392,15 +383,44 @@ class ConfigEntityStorage extends EntityStorageBase implements ConfigEntityStora
* {@inheritdoc}
*/
public function importRename($old_name, Config $new_config, Config $old_config) {
$id = static::getIDFromConfigName($old_name, $this->entityType->getConfigPrefix());
$entity = $this->load($id);
$entity->setSyncing(TRUE);
$data = $new_config->get();
foreach ($data as $key => $value) {
$entity->set($key, $value);
return $this->importUpdate($old_name, $new_config, $old_config);
}
/**
* {@inheritdoc}
*/
public function createFromStorageRecord(array $values) {
// Assign a new UUID if there is none yet.
if ($this->uuidKey && $this->uuidService && !isset($values[$this->uuidKey])) {
$values[$this->uuidKey] = $this->uuidService->generate();
}
$entity->save();
return TRUE;
$data = $this->mapFromStorageRecords(array($values));
$entity = current($data);
$entity->original = clone $entity;
$entity->enforceIsNew();
$entity->postCreate($this);
// Modules might need to add or change the data initially held by the new
// entity object, for instance to fill-in default values.
$this->invokeHook('create', $entity);
return $entity;
}
/**
* {@inheritdoc}
*/
public function updateFromStorageRecord(ConfigEntityInterface $entity, array $values) {
$entity->original = clone $entity;
$data = $this->mapFromStorageRecords(array($values));
$updated_entity = current($data);
foreach (array_keys($values) as $property) {
$value = $updated_entity->get($property);
$entity->set($property, $value);
}
return $entity;
}
}

View File

@ -29,4 +29,40 @@ interface ConfigEntityStorageInterface extends EntityStorageInterface {
*/
public static function getIDFromConfigName($config_name, $config_prefix);
/**
* Creates a configuration entity from storage values.
*
* Allows the configuration entity storage to massage storage values before
* creating an entity.
*
* @param array $values
* The array of values from the configuration storage.
*
* @return ConfigEntityInterface
* The configuration entity.
*
* @see \Drupal\Core\Entity\EntityStorageBase::mapFromStorageRecords()
* @see \Drupal\field\FieldStorageConfigStorage::mapFromStorageRecords()
*/
public function createFromStorageRecord(array $values);
/**
* Updates a configuration entity from storage values.
*
* Allows the configuration entity storage to massage storage values before
* updating an entity.
*
* @param ConfigEntityInterface $entity
* The configuration entity to update.
* @param array $values
* The array of values from the configuration storage.
*
* @return ConfigEntityInterface
* The configuration entity.
*
* @see \Drupal\Core\Entity\EntityStorageBase::mapFromStorageRecords()
* @see \Drupal\field\FieldStorageConfigStorage::mapFromStorageRecords()
*/
public function updateFromStorageRecord(ConfigEntityInterface $entity, array $values);
}

View File

@ -94,7 +94,8 @@ trait SchemaCheckTrait {
if ($element instanceof PrimitiveInterface) {
$success =
($type == 'integer' && $element instanceof IntegerInterface) ||
($type == 'double' && $element instanceof FloatInterface) ||
// Allow integer values in a float field.
(($type == 'double' || $type == 'integer') && $element instanceof FloatInterface) ||
($type == 'boolean' && $element instanceof BooleanInterface) ||
($type == 'string' && $element instanceof StringInterface) ||
// Null values are allowed for all types.

View File

@ -246,12 +246,16 @@ class ConfigSingleImportForm extends ConfirmFormBase {
$this->config($this->data['config_name'])->setData($this->data['import'])->save();
drupal_set_message($this->t('The %name configuration was imported.', array('%name' => $this->data['config_name'])));
}
// For a config entity, create a new entity and save it.
// For a config entity, create an entity and save it.
else {
try {
$entity = $this->entityManager
->getStorage($this->data['config_type'])
->create($this->data['import']);
$entity_storage = $this->entityManager->getStorage($this->data['config_type']);
if ($this->configExists) {
$entity = $entity_storage->updateFromStorageRecord($this->configExists, $this->data['import']);
}
else {
$entity = $entity_storage->createFromStorageRecord($this->data['import']);
}
$entity->save();
drupal_set_message($this->t('The @entity_type %label was imported.', array('@entity_type' => $entity->getEntityTypeId(), '%label' => $entity->label())));
}

View File

@ -226,6 +226,7 @@ class ConfigCRUDTest extends DrupalUnitTestBase {
'boolean' => TRUE,
'exp' => 1.2e+34,
'float' => 3.14159,
'float_as_integer' => (float) 1,
'hex' => 0xC,
'int' => 99,
'octal' => 0775,

View File

@ -98,6 +98,26 @@ EOD;
$this->assertIdentical($entity->id(), 'second');
$this->assertFalse($entity->status());
$this->assertIdentical($entity->uuid(), $second_uuid);
// Perform an update.
$import = <<<EOD
id: second
uuid: $second_uuid
label: 'Second updated'
weight: 0
style: ''
status: '0'
EOD;
$edit = array(
'config_type' => 'config_test',
'import' => $import,
);
$this->drupalPostForm('admin/config/development/configuration/single/import', $edit, t('Import'));
$this->assertRaw(t('Are you sure you want to update the %name @type?', array('%name' => 'second', '@type' => 'test configuration')));
$this->drupalPostForm(NULL, array(), t('Confirm'));
$entity = $storage->load('second');
$this->assertRaw(t('The @entity_type %label was imported.', array('@entity_type' => 'config_test', '%label' => $entity->label())));
$this->assertIdentical($entity->label(), 'Second updated');
}
/**

View File

@ -2,6 +2,7 @@ array: []
boolean: true
exp: 1.2e+34
float: 3.14159
float_as_integer: 1
hex: 0xC
int: 99
octal: 0775

View File

@ -68,6 +68,9 @@ config_test.types:
float:
type: float
label: 'Float'
float_as_integer:
type: float
label: 'Float'
exp:
type: float
label: 'Exponential'

View File

@ -102,6 +102,16 @@ class FieldImportDeleteUninstallUiTest extends FieldTestBase {
$staging->write('core.extension', $core_extension);
$this->drupalGet('admin/config/development/configuration');
$this->assertText('This synchronization will delete data from the fields: entity_test.field_tel, entity_test.field_text.');
// Delete all the text fields in staging, entity_test_install() adds quite
// a few.
foreach (\Drupal::entityManager()->getFieldMap() as $entity_type => $fields) {
foreach ($fields as $field_name => $info) {
if ($info['type'] == 'text') {
$staging->delete("field.storage.$entity_type.$field_name");
$staging->delete("field.field.$entity_type.$entity_type.$field_name");
}
}
}
// This will purge all the data, delete the field and uninstall the
// Telephone and Text modules.

View File

@ -0,0 +1,77 @@
<?php
/**
* @file
* Contains \Drupal\options\Tests\OptionsFloatFieldImportTest.
*/
namespace Drupal\options\Tests;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\field\Tests\FieldTestBase;
/**
* Tests option fields can be updated and created through config synchronization.
*
* @group options
*/
class OptionsFloatFieldImportTest extends FieldTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('node', 'options', 'field_ui', 'config', 'options_config_install_test');
protected function setUp() {
parent::setUp();
// Create test user.
$admin_user = $this->drupalCreateUser(array('synchronize configuration', 'access content', 'access administration pages', 'administer site configuration', 'administer content types', 'administer nodes', 'bypass node access', 'administer node fields', 'administer node display'));
$this->drupalLogin($admin_user);
}
/**
* Tests that importing list_float fields works.
*/
public function testImport() {
$field_name = 'field_options_float';
$type = 'options_install_test';
// Test the results on installing options_config_install_test. All the
// necessary configuration for this test is created by installing that
// module.
$field_storage = FieldStorageConfig::loadByName('node', $field_name);
$this->assertIdentical($field_storage->getSetting('allowed_values'), $array = array('0' => 'Zero', '0.5' => 'Point five'));
$admin_path = 'admin/structure/types/manage/' . $type . '/fields/node.' . $type . '.' . $field_name . '/storage';
// Export active config to staging
$this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.staging'));
// Set the active to not use dots in the allowed values key names.
$edit = array('field_storage[settings][allowed_values]' => "0|Zero\n1|One");
$this->drupalPostForm($admin_path, $edit, t('Save field settings'));
$field_storage = FieldStorageConfig::loadByName('node', $field_name);
$this->assertIdentical($field_storage->getSetting('allowed_values'), $array = array('0' => 'Zero', '1' => 'One'));
// Import configuration with dots in the allowed values key names. This
// tests \Drupal\Core\Config\Entity\ConfigEntityStorage::importUpdate().
$this->drupalGet('admin/config/development/configuration');
$this->drupalPostForm(NULL, array(), t('Import all'));
$field_storage = FieldStorageConfig::loadByName('node', $field_name);
$this->assertIdentical($field_storage->getSetting('allowed_values'), $array = array('0' => 'Zero', '0.5' => 'Point five'));
// Delete field to test creation. This tests
// \Drupal\Core\Config\Entity\ConfigEntityStorage::importCreate().
FieldConfig::loadByName('node', $type, $field_name)->delete();
$this->drupalGet('admin/config/development/configuration');
$this->drupalPostForm(NULL, array(), t('Import all'));
$field_storage = FieldStorageConfig::loadByName('node', $field_name);
$this->assertIdentical($field_storage->getSetting('allowed_values'), $array = array('0' => 'Zero', '0.5' => 'Point five'));
}
}

View File

@ -0,0 +1,57 @@
langcode: en
status: true
dependencies:
entity:
- field.field.node.options_install_test.body
- node.type.options_install_test
module:
- entity_reference
- text
id: node.options_install_test.default
targetEntityType: node
bundle: options_install_test
mode: default
content:
title:
type: string_textfield
weight: -5
settings:
size: 60
placeholder: ''
third_party_settings: { }
uid:
type: entity_reference_autocomplete
weight: 5
settings:
match_operator: CONTAINS
size: 60
autocomplete_type: tags
placeholder: ''
third_party_settings: { }
created:
type: datetime_timestamp
weight: 10
settings: { }
third_party_settings: { }
promote:
type: boolean_checkbox
settings:
display_label: '1'
weight: 15
third_party_settings: { }
sticky:
type: boolean_checkbox
settings:
display_label: '1'
weight: 16
third_party_settings: { }
body:
type: text_textarea_with_summary
weight: 26
settings:
rows: 9
summary_rows: 3
placeholder: ''
third_party_settings: { }
hidden: { }
third_party_settings: { }

View File

@ -0,0 +1,28 @@
langcode: en
status: true
dependencies:
entity:
- field.field.node.options_install_test.body
- node.type.options_install_test
module:
- text
- user
id: node.options_install_test.default
label: null
targetEntityType: node
bundle: options_install_test
mode: default
content:
links:
weight: 100
body:
label: hidden
type: text_default
weight: 101
settings: { }
third_party_settings: { }
hidden:
langcode: true
third_party_settings:
entity_test:
foo: bar

View File

@ -0,0 +1,30 @@
langcode: en
status: true
dependencies:
entity:
- core.entity_view_mode.node.teaser
- field.field.node.options_install_test.body
- node.type.options_install_test
module:
- text
- user
id: node.options_install_test.teaser
label: null
targetEntityType: node
bundle: options_install_test
mode: teaser
content:
links:
weight: 100
body:
label: hidden
type: text_summary_or_trimmed
weight: 101
settings:
trim_length: 600
third_party_settings: { }
hidden:
langcode: true
third_party_settings:
entity_test:
foo: bar

View File

@ -0,0 +1,20 @@
langcode: en
status: true
dependencies:
entity:
- field.storage.node.body
- node.type.options_install_test
id: node.options_install_test.body
field_name: body
entity_type: node
bundle: options_install_test
label: Body
description: ''
required: false
translatable: true
default_value: { }
default_value_callback: ''
settings:
display_summary: true
third_party_settings: { }
field_type: text_with_summary

View File

@ -0,0 +1,19 @@
langcode: en
status: true
dependencies:
entity:
- field.storage.node.field_options_float
- node.type.options_install_test
id: node.options_install_test.field_options_float
field_name: field_options_float
entity_type: node
bundle: options_install_test
label: field_options_float
description: ''
required: false
translatable: true
default_value: { }
default_value_callback: ''
settings: { }
third_party_settings: { }
field_type: list_float

View File

@ -0,0 +1,24 @@
langcode: en
status: true
dependencies:
module:
- node
- options
id: node.field_options_float
field_name: field_options_float
entity_type: node
type: list_float
settings:
allowed_values:
-
value: 0
label: Zero
-
value: 0.5
label: 'Point five'
allowed_values_function: ''
module: options
locked: false
cardinality: 1
translatable: true
indexes: { }

View File

@ -0,0 +1,11 @@
langcode: en
status: true
dependencies: { }
name: options_install_test
type: options_install_test
description: null
help: null
new_revision: false
preview_mode: 1
display_submitted: true
third_party_settings: { }

View File

@ -0,0 +1,9 @@
name: 'Options config install test'
type: module
description: 'Support module for the Options module tests.'
core: 8.x
package: Testing
version: VERSION
dependencies:
- node
- options