diff --git a/core/lib/Drupal/Core/Config/Config.php b/core/lib/Drupal/Core/Config/Config.php index c1f0632..7177b23 100644 --- a/core/lib/Drupal/Core/Config/Config.php +++ b/core/lib/Drupal/Core/Config/Config.php @@ -9,6 +9,7 @@ use Drupal\Component\Utility\NestedArray; use Drupal\Core\Config\ConfigNameException; +use Drupal\Core\TypedData\TypedDataInterface; use Symfony\Component\EventDispatcher\EventDispatcher; /** @@ -326,7 +327,12 @@ public function set($key, $value) { $this->load(); } // Type-cast value into a string. - $value = $this->castValue($value); + if ($value instanceof TypedDataInterface) { + $value = $value->getString(); + } + else { + $value = $this->castValue($value); + } // The dot/period is a reserved character; it may appear between keys, but // not within keys. diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityNGBase.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityNGBase.php new file mode 100644 index 0000000..7a1397f --- /dev/null +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityNGBase.php @@ -0,0 +1,108 @@ +id(); + if ($original_id !== NULL && $original_id !== '') { + $this->setOriginalID($original_id); + } + } + + /** + * Implements ConfigEntityInterface::getOriginalID(). + */ + public function getOriginalID() { + return $this->get('originalID')->value; + } + + /** + * Implements ConfigEntityInterface::setOriginalID(). + */ + public function setOriginalID($id) { + $this->set('originalID', $id); + } + + /** + * Overrides Entity::isNew(). + * + * EntityInterface::enforceIsNew() is only supported for newly created + * configuration entities but has no effect after saving, since each + * configuration entity is unique. + */ + final public function isNew() { + // Configuration entity IDs are strings, and '0' is a valid ID. + return !empty($this->enforceIsNew) || $this->id() === NULL || $this->id() === ''; + } + + /** + * Overrides Entity::createDuplicate(). + */ + public function createDuplicate() { + $duplicate = parent::createDuplicate(); + // Prevent the new duplicate from being misinterpreted as a rename. + $duplicate->setOriginalID(NULL); + return $duplicate; + } + + /** + * Helper callback for uasort() to sort configuration entities by weight and label. + */ + public static function sort($a, $b) { + $a_weight = isset($a->weight) ? $a->weight : 0; + $b_weight = isset($b->weight) ? $b->weight : 0; + if ($a_weight == $b_weight) { + $a_label = $a->label(); + $b_label = $b->label(); + return strnatcasecmp($a_label, $b_label); + } + return ($a_weight < $b_weight) ? -1 : 1; + } + + /** + * Overrides \Drupal\Core\Entity\Entity::getExportProperties(). + */ + public function getExportProperties() { + // Configuration objects do not have a schema. Extract all key names from + // class properties. + $class_info = new \ReflectionClass($this); + $properties = array(); + foreach ($class_info->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) { + $name = $property->getName(); + $properties[$name] = $this->get($name); + } + return $properties; + } + +} diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php index 3dd0b0a..b87e2f1 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php @@ -108,7 +108,7 @@ public function load(array $ids = NULL) { // Remove any invalid ids from the array. $passed_ids = array_intersect_key($passed_ids, $entities); foreach ($entities as $entity) { - $passed_ids[$entity->{$this->idKey}] = $entity; + $passed_ids[$entity->id()] = $entity; } $entities = $passed_ids; } diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigStorageControllerNG.php b/core/lib/Drupal/Core/Config/Entity/ConfigStorageControllerNG.php new file mode 100644 index 0000000..fe0fc89 --- /dev/null +++ b/core/lib/Drupal/Core/Config/Entity/ConfigStorageControllerNG.php @@ -0,0 +1,179 @@ +getConfigPrefix(); + + // Load all of the configuration entities. + if ($ids === NULL) { + $names = drupal_container()->get('config.storage')->listAll($prefix); + $result = array(); + foreach ($names as $name) { + $config = config($name); + $result[$config->get($this->idKey)] = $config->get(); + } + return $result; + } + else { + $result = array(); + foreach ($ids as $id) { + // Add the prefix to the ID to serve as the configuration object name. + $config = config($prefix . $id); + if (!$config->isNew()) { + $result[$id] = $config->get(); + } + } + return $result; + } + } + + /** + * Overrides ConfigStorageController::attachLoad(). + */ + protected function attachLoad(&$queried_entities, $revision_id = FALSE) { + // Map the loaded records into entity objects and according fields. + $queried_entities = $this->mapFromStorageRecords($queried_entities, $revision_id); + + parent::attachLoad($queried_entities, $revision_id); + } + + /** + * Maps from storage records to entity objects. + * + * @param array $records + * Associative array of query results, keyed on the entity ID. + * @param boolean $load_revision + * (optional) TRUE if the revision should be loaded, defaults to FALSE. + * + * @return array + * An array of entity objects implementing the EntityInterface. + */ + protected function mapFromStorageRecords(array $records, $load_revision = FALSE) { + $class = $this->entityInfo['class']; + $entities = array(); + foreach ($records as $id => $record) { + $values = array(); + foreach ($record as $name => $value) { + // Skip the item delta and item value levels but let the field assign + // the value as suiting. This avoids unnecessary array hierarchies and + // saves memory here. + $values[$name][LANGUAGE_DEFAULT] = $value; + } + // Turn the record into an entity class. + $entities[$id] = new $class($values, $this->entityType); + } + return $entities; + } + + /** + * Overrides ConfigStorageController::create(). + */ + public function create(array $values) { + $entity = parent::create($values); + + // Assign a new UUID if there is none yet. + if (!isset($entity->{$this->uuidKey}->value)) { + $uuid = new Uuid(); + $entity->{$this->uuidKey} = $uuid->generate(); + } + + // Set all other given values. + foreach ($values as $name => $value) { + $entity->$name = $value; + } + + return $entity; + } + + /** + * Overrides ConfigStorageController::postSave(). + */ + protected function postSave(EntityInterface $entity, $update) { + // Delete the original configuration entity, in case the entity ID was + // renamed. + if ($update && !empty($entity->original) && $entity->{$this->idKey}->value !== $entity->original->{$this->idKey}->value) { + // @todo This should just delete the original config object without going + // through the API, no? + $entity->original->delete(); + } + } + + /** + * Implements Drupal\Core\Entity\EntityStorageControllerInterface::getFieldDefinitions(). + */ + public function getFieldDefinitions(array $constraints) { + // @todo: Add caching for $this->entityFieldInfo. + if (!isset($this->entityFieldInfo)) { + $this->entityFieldInfo = array( + 'definitions' => $this->baseFieldDefinitions(), + // Contains definitions of optional (per-bundle) fields. + 'optional' => array(), + // An array keyed by bundle name containing the optional fields added by + // the bundle. + 'bundle map' => array(), + ); + + // Invoke hooks. + $result = module_invoke_all($this->entityType . '_property_info'); + $this->entityFieldInfo = NestedArray::mergeDeep($this->entityFieldInfo, $result); + $result = module_invoke_all('entity_field_info', $this->entityType); + $this->entityFieldInfo = NestedArray::mergeDeep($this->entityFieldInfo, $result); + + $hooks = array('entity_field_info', $this->entityType . '_property_info'); + drupal_alter($hooks, $this->entityFieldInfo, $this->entityType); + + // Enforce fields to be multiple by default. + foreach ($this->entityFieldInfo['definitions'] as &$definition) { + $definition['list'] = TRUE; + } + foreach ($this->entityFieldInfo['optional'] as &$definition) { + $definition['list'] = TRUE; + } + } + + $bundle = !empty($constraints['Bundle']) ? $constraints['Bundle'] : FALSE; + + // Add in per-bundle fields. + if (!isset($this->fieldDefinitions[$bundle])) { + $this->fieldDefinitions[$bundle] = $this->entityFieldInfo['definitions']; + + if ($bundle && isset($this->entityFieldInfo['bundle map'][$bundle])) { + $this->fieldDefinitions[$bundle] += array_intersect_key($this->entityFieldInfo['optional'], array_flip($this->entityFieldInfo['bundle map'][$bundle])); + } + } + return $this->fieldDefinitions[$bundle]; + } + + /** + * Implements \Drupal\Core\Entity\DataBaseStorageControllerNG::baseFieldDefinitions(). + */ + public function baseFieldDefinitions() { + $fields['originalID'] = array( + 'label' => t('Original ID'), + 'description' => t('The ID of the original entity.'), + 'type' => 'string_field', + ); + return $fields; + } + +} diff --git a/core/lib/Drupal/Core/TypedData/TypedData.php b/core/lib/Drupal/Core/TypedData/TypedData.php index d6ad4be..33d169e 100644 --- a/core/lib/Drupal/Core/TypedData/TypedData.php +++ b/core/lib/Drupal/Core/TypedData/TypedData.php @@ -34,6 +34,10 @@ public function __construct(array $definition) { $this->definition = $definition; } + public function __toString() { + return $this->getString(); + } + /** * Implements \Drupal\Core\TypedData\TypedDataInterface::getType(). */ diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigEntityTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigEntityTest.php index e93b79f..aad5ebc 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigEntityTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigEntityTest.php @@ -36,11 +36,11 @@ public static function getInfo() { function testCRUD() { // Verify default properties on a newly created empty entity. $empty = entity_create('config_test', array()); - $this->assertIdentical($empty->id, NULL); - $this->assertTrue($empty->uuid); - $this->assertIdentical($empty->label, NULL); - $this->assertIdentical($empty->style, NULL); - $this->assertIdentical($empty->langcode, LANGUAGE_NOT_SPECIFIED); + $this->assertIdentical($empty->id->value, NULL); + $this->assertTrue($empty->uuid->value); + $this->assertIdentical($empty->label->value, NULL); + $this->assertIdentical($empty->style->value, NULL); + $this->assertIdentical($empty->langcode->value, LANGUAGE_NOT_SPECIFIED); // Verify ConfigEntity properties/methods on the newly created empty entity. $this->assertIdentical($empty->isNew(), TRUE); @@ -50,11 +50,11 @@ function testCRUD() { $this->assertTrue($empty->uuid()); $this->assertIdentical($empty->label(), NULL); - $this->assertIdentical($empty->get('id'), NULL); - $this->assertTrue($empty->get('uuid')); - $this->assertIdentical($empty->get('label'), NULL); - $this->assertIdentical($empty->get('style'), NULL); - $this->assertIdentical($empty->get('langcode'), LANGUAGE_NOT_SPECIFIED); + $this->assertIdentical($empty->get('id')->value, NULL); + $this->assertTrue($empty->get('uuid')->value); + $this->assertIdentical($empty->get('label')->value, NULL); + $this->assertIdentical($empty->get('style')->value, NULL); + $this->assertIdentical($empty->get('langcode')->value, LANGUAGE_NOT_SPECIFIED); // Verify Entity properties/methods on the newly created empty entity. $this->assertIdentical($empty->isNewRevision(), FALSE); @@ -91,12 +91,12 @@ function testCRUD() { 'label' => $this->randomString(), 'style' => $this->randomName(), )); - $this->assertIdentical($config_test->id, $expected['id']); - $this->assertTrue($config_test->uuid); - $this->assertNotEqual($config_test->uuid, $empty->uuid); - $this->assertIdentical($config_test->label, $expected['label']); - $this->assertIdentical($config_test->style, $expected['style']); - $this->assertIdentical($config_test->langcode, LANGUAGE_NOT_SPECIFIED); + $this->assertIdentical($config_test->id->value, $expected['id']); + $this->assertTrue($config_test->uuid->value); + $this->assertNotEqual($config_test->uuid->value, $empty->uuid->value); + $this->assertIdentical($config_test->label->value, $expected['label']); + $this->assertIdentical($config_test->style->value, $expected['style']); + $this->assertIdentical($config_test->langcode->value, LANGUAGE_NOT_SPECIFIED); // Verify methods on the newly created entity. $this->assertIdentical($config_test->isNew(), TRUE); diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php index 5e62521..f484945 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php @@ -19,7 +19,7 @@ class ConfigImportTest extends DrupalUnitTestBase { * * @var array */ - public static $modules = array('config_test'); + public static $modules = array('config_test', 'system'); public static function getInfo() { return array( diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigInstallTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigInstallTest.php index 2842446..0b363ef 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigInstallTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigInstallTest.php @@ -13,6 +13,14 @@ * Tests installation of configuration objects in installation functionality. */ class ConfigInstallTest extends DrupalUnitTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = array('system'); + public static function getInfo() { return array( 'name' => 'Installation functionality', diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigOverrideTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigOverrideTest.php index e2ddb59..91da2a4 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigOverrideTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigOverrideTest.php @@ -19,7 +19,7 @@ class ConfigOverrideTest extends DrupalUnitTestBase { * * @var array */ - public static $modules = array('config_test'); + public static $modules = array('config_test', 'system'); public static function getInfo() { return array( diff --git a/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigQueryTestStorageController.php b/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigQueryTestStorageController.php new file mode 100644 index 0000000..35ae538 --- /dev/null +++ b/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigQueryTestStorageController.php @@ -0,0 +1,34 @@ + t('Number'), + 'description' => t('A number used by the sort tests.'), + 'type' => 'integer_field', + ); + $fields['array'] = array( + 'label' => t('Array'), + 'description' => t('An array used by the wildcard tests.'), + 'type' => 'string_field', + ); + return $fields; + } + +} + diff --git a/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTestFormController.php b/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTestFormController.php index 7e54e1c..51d1943 100644 --- a/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTestFormController.php +++ b/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTestFormController.php @@ -8,12 +8,12 @@ namespace Drupal\config_test; use Drupal\Core\Entity\EntityInterface; -use Drupal\Core\Entity\EntityFormController; +use Drupal\Core\Entity\EntityFormControllerNG; /** * Form controller for the test config edit forms. */ -class ConfigTestFormController extends EntityFormController { +class ConfigTestFormController extends EntityFormControllerNG { /** * Overrides Drupal\Core\Entity\EntityFormController::form(). diff --git a/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTestStorageController.php b/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTestStorageController.php index e1fe4fe..f5c909d 100644 --- a/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTestStorageController.php +++ b/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTestStorageController.php @@ -7,13 +7,13 @@ namespace Drupal\config_test; -use Drupal\Core\Config\Entity\ConfigStorageController; +use Drupal\Core\Config\Entity\ConfigStorageControllerNG; use Drupal\Core\Config\Config; /** * @todo. */ -class ConfigTestStorageController extends ConfigStorageController { +class ConfigTestStorageController extends ConfigStorageControllerNG { /** * Overrides \Drupal\Core\Config\Entity\ConfigStorageController::importCreate(). @@ -45,4 +45,43 @@ public function importDelete($name, Config $new_config, Config $old_config) { return parent::importDelete($name, $new_config, $old_config); } + /** + * Implements \Drupal\Core\Entity\DataBaseStorageControllerNG::baseFieldDefinitions(). + */ + public function baseFieldDefinitions() { + $fields = parent::baseFieldDefinitions(); + $fields['id'] = array( + 'label' => t('ID'), + 'description' => t('The ID of the test entity.'), + 'type' => 'string_field', + ); + $fields['uuid'] = array( + 'label' => t('UUID'), + 'description' => t('The UUID of the test entity.'), + 'type' => 'string_field', + ); + $fields['langcode'] = array( + 'label' => t('Language code'), + 'description' => t('The language code of the test entity.'), + 'type' => 'language_field', + ); + $fields['label'] = array( + 'label' => t('Name'), + 'description' => t('The name of the test entity.'), + 'type' => 'string_field', + ); + $fields['style'] = array( + 'label' => t('Style'), + 'description' => t('The ID of the associated image style.'), + 'type' => 'entity_reference_field', + 'settings' => array('target_type' => 'image_style'), + ); + $fields['protected_property'] = array( + 'label' => t('Protected Property'), + 'description' => t('A protected property of the test entity.'), + 'type' => 'string_field', + ); + return $fields; + } + } diff --git a/core/modules/config/tests/config_test/lib/Drupal/config_test/Plugin/Core/Entity/ConfigQueryTest.php b/core/modules/config/tests/config_test/lib/Drupal/config_test/Plugin/Core/Entity/ConfigQueryTest.php index 88e7583..51d5df1 100644 --- a/core/modules/config/tests/config_test/lib/Drupal/config_test/Plugin/Core/Entity/ConfigQueryTest.php +++ b/core/modules/config/tests/config_test/lib/Drupal/config_test/Plugin/Core/Entity/ConfigQueryTest.php @@ -17,7 +17,7 @@ * id = "config_query_test", * label = @Translation("Test configuration for query"), * module = "config_test", - * controller_class = "Drupal\config_test\ConfigTestStorageController", + * controller_class = "Drupal\config_test\ConfigQueryTestStorageController", * list_controller_class = "Drupal\Core\Config\Entity\ConfigEntityListController", * form_controller_class = { * "default" = "Drupal\config_test\ConfigTestFormController" diff --git a/core/modules/config/tests/config_test/lib/Drupal/config_test/Plugin/Core/Entity/ConfigTest.php b/core/modules/config/tests/config_test/lib/Drupal/config_test/Plugin/Core/Entity/ConfigTest.php index 574c100..ee55bcc 100644 --- a/core/modules/config/tests/config_test/lib/Drupal/config_test/Plugin/Core/Entity/ConfigTest.php +++ b/core/modules/config/tests/config_test/lib/Drupal/config_test/Plugin/Core/Entity/ConfigTest.php @@ -7,7 +7,7 @@ namespace Drupal\config_test\Plugin\Core\Entity; -use Drupal\Core\Config\Entity\ConfigEntityBase; +use Drupal\Core\Config\Entity\ConfigEntityNGBase; use Drupal\Core\Annotation\Plugin; use Drupal\Core\Annotation\Translation; @@ -32,7 +32,7 @@ * } * ) */ -class ConfigTest extends ConfigEntityBase { +class ConfigTest extends ConfigEntityNGBase { /** * The machine name for the configuration entity. @@ -69,6 +69,17 @@ class ConfigTest extends ConfigEntityBase { */ protected $protected_property; + protected function init() { + parent::init(); + // We unset all defined properties, so magic getters apply. + unset($this->id); + unset($this->label); + unset($this->uuid); + unset($this->style); + unset($this->protected_property); + unset($this->originalID); + } + /** * Overrides \Drupal\Core\Config\Entity\ConfigEntityBase::getExportProperties(); */ diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/ConfigEntityQueryTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/ConfigEntityQueryTest.php index bfafc9e..eae8928 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Entity/ConfigEntityQueryTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Entity/ConfigEntityQueryTest.php @@ -21,7 +21,7 @@ class ConfigEntityQueryTest extends DrupalUnitTestBase { * * @var array */ - static $modules = array('config_test'); + static $modules = array('config_test', 'system'); /** * Stores the search results for alter comparision.