diff --git a/core/lib/Drupal/Core/Config/Schema/Mapping.php b/core/lib/Drupal/Core/Config/Schema/Mapping.php index a85fac4eff976aa22f8d42d81819648cb0d806b5..eed1da87e0c9d86256d51c6f8e1c8f8d186ac942 100644 --- a/core/lib/Drupal/Core/Config/Schema/Mapping.php +++ b/core/lib/Drupal/Core/Config/Schema/Mapping.php @@ -88,7 +88,7 @@ public function set($property_name, $value, $notify = TRUE) { if ($notify && isset($this->parent)) { $this->parent->onChange($this->name); } - return $property; + return $this; } /** diff --git a/core/lib/Drupal/Core/Entity/ContentEntityBase.php b/core/lib/Drupal/Core/Entity/ContentEntityBase.php index 83c9c736961e0219a32c9745d01dd7881e01560d..071f584931ad01f715e71bbcfe1d8c8cd06c2d15 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityBase.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php @@ -385,9 +385,10 @@ protected function getTranslatedField($name, $langcode) { * {@inheritdoc} */ public function set($name, $value, $notify = TRUE) { - // If default language or an entity key changes we need to react to that. - $notify = $name == 'langcode' || in_array($name, $this->getEntityType()->getKeys()); - $this->get($name)->setValue($value, $notify); + // Assign the value on the child and overrule notify such that we get + // notified to handle changes afterwards. We can ignore notify as there is + // no parent to notify anyway. + $this->get($name)->setValue($value, TRUE); } /** diff --git a/core/lib/Drupal/Core/Entity/Plugin/DataType/EntityAdapter.php b/core/lib/Drupal/Core/Entity/Plugin/DataType/EntityAdapter.php index 0fb2330a9313314f4ea9319446cb74de649f9afd..bbcb0bb90416c39d0289b75ea0b755ae9c40d3ea 100644 --- a/core/lib/Drupal/Core/Entity/Plugin/DataType/EntityAdapter.php +++ b/core/lib/Drupal/Core/Entity/Plugin/DataType/EntityAdapter.php @@ -105,7 +105,8 @@ public function set($property_name, $value, $notify = TRUE) { throw new \InvalidArgumentException(String::format('Unable to set unknown property @name.', array('@name' => $property_name))); } // This will throw an exception for unknown fields. - return $this->entity->set($property_name, $value, $notify); + $this->entity->set($property_name, $value, $notify); + return $this; } /** diff --git a/core/lib/Drupal/Core/Field/FieldItemBase.php b/core/lib/Drupal/Core/Field/FieldItemBase.php index db8601cc45258e5bfb910bbd2ff561c659114193..b411b881700a80bb8503fb48d96acd9e0bb2afe2 100644 --- a/core/lib/Drupal/Core/Field/FieldItemBase.php +++ b/core/lib/Drupal/Core/Field/FieldItemBase.php @@ -105,10 +105,7 @@ protected function getSetting($setting_name) { } /** - * Overrides \Drupal\Core\TypedData\TypedData::setValue(). - * - * @param array|null $values - * An array of property values. + * {@inheritdoc} */ public function setValue($values, $notify = TRUE) { // Treat the values as property value of the first property, if no array is @@ -117,53 +114,39 @@ public function setValue($values, $notify = TRUE) { $keys = array_keys($this->definition->getPropertyDefinitions()); $values = array($keys[0] => $values); } - $this->values = $values; - // Update any existing property objects. - foreach ($this->properties as $name => $property) { - $value = NULL; - if (isset($values[$name])) { - $value = $values[$name]; - } - $property->setValue($value, FALSE); - unset($this->values[$name]); - } - // Notify the parent of any changes. - if ($notify && isset($this->parent)) { - $this->parent->onChange($this->name); - } - } - - /** - * {@inheritdoc} - */ - public function __get($name) { - // There is either a property object or a plain value - possibly for a - // not-defined property. If we have a plain value, directly return it. - if (isset($this->values[$name])) { - return $this->values[$name]; - } - elseif (isset($this->properties[$name])) { - return $this->properties[$name]->getValue(); - } + parent::setValue($values, $notify); } /** * {@inheritdoc} + * + * Different to the parent Map class, we avoid creating property objects as + * far as possible in order to optimize performance. Thus we just update + * $this->values if no property object has been created yet. */ - public function set($property_name, $value, $notify = TRUE) { + protected function writePropertyValue($property_name, $value) { // For defined properties there is either a property object or a plain // value that needs to be updated. if (isset($this->properties[$property_name])) { $this->properties[$property_name]->setValue($value, FALSE); - unset($this->values[$property_name]); } // Allow setting plain values for not-defined properties also. else { $this->values[$property_name] = $value; } - // Directly notify ourselves. - if ($notify) { - $this->onChange($property_name); + } + + /** + * {@inheritdoc} + */ + public function __get($name) { + // There is either a property object or a plain value - possibly for a + // not-defined property. If we have a plain value, directly return it. + if (isset($this->properties[$name])) { + return $this->properties[$name]->getValue(); + } + elseif (isset($this->values[$name])) { + return $this->values[$name]; } } @@ -183,29 +166,23 @@ public function __set($name, $value) { * {@inheritdoc} */ public function __isset($name) { - return isset($this->values[$name]) || (isset($this->properties[$name]) && $this->properties[$name]->getValue() !== NULL); + if (isset($this->properties[$name])) { + return $this->properties[$name]->getValue() !== NULL; + } + return isset($this->values[$name]); } /** * {@inheritdoc} */ public function __unset($name) { - $this->set($name, NULL); - unset($this->values[$name]); - } - - /** - * Overrides \Drupal\Core\TypedData\Map::onChange(). - */ - public function onChange($property_name) { - // Notify the parent of changes. - if (isset($this->parent)) { - $this->parent->onChange($this->name); + if ($this->definition->getPropertyDefinition($name)) { + $this->set($name, NULL); } - // Remove the plain value, such that any further __get() calls go via the - // updated property object. - if (isset($this->properties[$property_name])) { - unset($this->values[$property_name]); + else { + // Explicitly unset the property in $this->values if a non-defined + // property is unset, such that its key is removed from $this->values. + unset($this->values[$name]); } } 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 ae21f53292c3d611db7dc8f7d0f051d01fc16ad4..44063ad557a524aea2cf8bab1888a7f99f8a0a1e 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php @@ -141,13 +141,9 @@ public static function schema(FieldStorageDefinitionInterface $field_definition) */ public function setValue($values, $notify = TRUE) { if (isset($values) && !is_array($values)) { - // Directly update the property instead of invoking the parent, so it can - // handle objects and IDs. - $this->properties['entity']->setValue($values, $notify); - // If notify was FALSE, ensure the target_id property gets synched. - if (!$notify) { - $this->set('target_id', $this->properties['entity']->getTargetIdentifier(), FALSE); - } + // If either a scalar or an object was passed as the value for the item, + // assign it to the 'entity' property since that works for both cases. + $this->set('entity', $values, $notify); } else { // Make sure that the 'entity' property gets set as 'target_id'. @@ -175,15 +171,15 @@ public function getValue() { /** * {@inheritdoc} */ - public function onChange($property_name) { + public function onChange($property_name, $notify = TRUE) { // Make sure that the target ID and the target property stay in sync. if ($property_name == 'target_id') { - $this->properties['entity']->setValue($this->target_id, FALSE); + $this->writePropertyValue('entity', $this->target_id); } elseif ($property_name == 'entity') { - $this->set('target_id', $this->properties['entity']->getTargetIdentifier(), FALSE); + $this->writePropertyValue('target_id', $this->get('entity')->getTargetIdentifier()); } - parent::onChange($property_name); + parent::onChange($property_name, $notify); } /** diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/LanguageItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/LanguageItem.php index 961eb67d8ed9dba145c32e0b45f507d8e81fe9ee..851b818cdc0e1b7dce4972f7a45ca9888a8bd3ee 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/LanguageItem.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/LanguageItem.php @@ -69,14 +69,7 @@ public function setValue($values, $notify = TRUE) { // Treat the values as property value of the language property, if no array // is given as this handles language codes and objects. if (isset($values) && !is_array($values)) { - // Directly update the property instead of invoking the parent, so that - // the language property can take care of updating the language code - // property. - $this->properties['language']->setValue($values, $notify); - // If notify was FALSE, ensure the value property gets synched. - if (!$notify) { - $this->set('value', $this->properties['language']->getTargetIdentifier(), FALSE); - } + $this->set('language', $values, $notify); } else { // Make sure that the 'language' property gets set as 'value'. @@ -100,14 +93,15 @@ public function applyDefaultValue($notify = TRUE) { /** * {@inheritdoc} */ - public function onChange($property_name) { + public function onChange($property_name, $notify = TRUE) { // Make sure that the value and the language property stay in sync. if ($property_name == 'value') { - $this->properties['language']->setValue($this->value, FALSE); + $this->writePropertyValue('language', $this->value); } elseif ($property_name == 'language') { - $this->set('value', $this->properties['language']->getTargetIdentifier(), FALSE); + $this->writePropertyValue('value', $this->get('language')->getTargetIdentifier()); } - parent::onChange($property_name); + parent::onChange($property_name, $notify); } + } diff --git a/core/lib/Drupal/Core/TypedData/ComplexDataInterface.php b/core/lib/Drupal/Core/TypedData/ComplexDataInterface.php index e7b91fc26fce27fe652f391d5e74aed5571119a7..b60f84d0351e7fca1c0c4d3bb404229c379d0f88 100644 --- a/core/lib/Drupal/Core/TypedData/ComplexDataInterface.php +++ b/core/lib/Drupal/Core/TypedData/ComplexDataInterface.php @@ -53,8 +53,7 @@ public function get($property_name); * TRUE. If the update stems from a parent object, set it to FALSE to avoid * being notified again. * - * @return \Drupal\Core\TypedData\TypedDataInterface - * The property object. + * @return $this * * @throws \InvalidArgumentException * If the specified property does not exist. diff --git a/core/lib/Drupal/Core/TypedData/Plugin/DataType/Map.php b/core/lib/Drupal/Core/TypedData/Plugin/DataType/Map.php index 56bdfa26bc4e1b064226f3bd37cf8725cc41694b..7694db550ed1e677e46f0555263eb7f411e28ac7 100644 --- a/core/lib/Drupal/Core/TypedData/Plugin/DataType/Map.php +++ b/core/lib/Drupal/Core/TypedData/Plugin/DataType/Map.php @@ -94,11 +94,11 @@ public function setValue($values, $notify = TRUE) { // Update any existing property objects. foreach ($this->properties as $name => $property) { - $value = NULL; - if (isset($values[$name])) { - $value = $values[$name]; - } + $value = isset($values[$name]) ? $values[$name] : NULL; $property->setValue($value, FALSE); + // Remove the value from $this->values to ensure it does not contain any + // value for computed properties. + unset($this->values[$name]); } // Notify the parent of any changes. if ($notify && isset($this->parent)) { @@ -134,19 +134,34 @@ public function get($property_name) { } /** - * Implements \Drupal\Core\TypedData\ComplexDataInterface::set(). + * {@inheritdoc} */ public function set($property_name, $value, $notify = TRUE) { + // Separate the writing in a protected method, such that onChange + // implementations can make use of it. + $this->writePropertyValue($property_name, $value); + $this->onChange($property_name, $notify); + return $this; + } + + /** + * Writes the value of a property without handling changes. + * + * Implementations of onChange() should use this method instead of set() in + * order to avoid onChange() being triggered again. + * + * @param string $property_name + * The name of the property to be written. + * @param $value + * The value to set. + */ + protected function writePropertyValue($property_name, $value) { if ($this->definition->getPropertyDefinition($property_name)) { - $this->get($property_name)->setValue($value, $notify); + $this->get($property_name)->setValue($value, FALSE); } else { // Just set the plain value, which allows adding a new entry to the map. $this->values[$property_name] = $value; - // Directly notify ourselves. - if ($notify) { - $this->onChange($property_name, $value); - } } } @@ -212,11 +227,16 @@ public function __clone() { } /** - * Implements \Drupal\Core\TypedData\ComplexDataInterface::onChange(). + * {@inheritdoc} + * + * @param bool $notify + * (optional) Whether to forward the notification to the parent. Defaults to + * TRUE. By passing FALSE, overrides of this method can re-use the logic + * of parent classes without triggering notification. */ - public function onChange($property_name) { + public function onChange($property_name, $notify = TRUE) { // Notify the parent of changes. - if (isset($this->parent)) { + if ($notify && isset($this->parent)) { $this->parent->onChange($this->name); } } diff --git a/core/modules/datetime/src/Plugin/Field/FieldType/DateTimeItem.php b/core/modules/datetime/src/Plugin/Field/FieldType/DateTimeItem.php index f63ffd62a05b9496dce189bbe22f8524b3fee7c6..6ecf3194124070a5be265352c8f87ff4092522d7 100644 --- a/core/modules/datetime/src/Plugin/Field/FieldType/DateTimeItem.php +++ b/core/modules/datetime/src/Plugin/Field/FieldType/DateTimeItem.php @@ -131,13 +131,12 @@ public function isEmpty() { /** * {@inheritdoc} */ - public function onChange($property_name) { - parent::onChange($property_name); - + public function onChange($property_name, $notify = TRUE) { // Enforce that the computed date is recalculated. if ($property_name == 'value') { $this->date = NULL; } + parent::onChange($property_name, $notify); } } diff --git a/core/modules/system/src/Tests/Entity/EntityFieldTest.php b/core/modules/system/src/Tests/Entity/EntityFieldTest.php index 41dbb44fbac68f9f08df5274e4a35dfd1e5ae62c..45709b9b8a5069511e7bb16098f067c04bc3fc20 100644 --- a/core/modules/system/src/Tests/Entity/EntityFieldTest.php +++ b/core/modules/system/src/Tests/Entity/EntityFieldTest.php @@ -130,16 +130,16 @@ protected function doTestReadWrite($entity_type) { $this->assertEqual($this->entity_user->getUsername(), $entity->user_id->entity->name->value, format_string('%entity_type: User name can be read.', array('%entity_type' => $entity_type))); // Change the assigned user by entity. - $new_user = $this->createUser(); - $entity->user_id->entity = $new_user; - $this->assertEqual($new_user->id(), $entity->user_id->target_id, format_string('%entity_type: Updated user id can be read.', array('%entity_type' => $entity_type))); - $this->assertEqual($new_user->getUsername(), $entity->user_id->entity->name->value, format_string('%entity_type: Updated username value can be read.', array('%entity_type' => $entity_type))); + $new_user1 = $this->createUser(); + $entity->user_id->entity = $new_user1; + $this->assertEqual($new_user1->id(), $entity->user_id->target_id, format_string('%entity_type: Updated user id can be read.', array('%entity_type' => $entity_type))); + $this->assertEqual($new_user1->getUsername(), $entity->user_id->entity->name->value, format_string('%entity_type: Updated username value can be read.', array('%entity_type' => $entity_type))); // Change the assigned user by id. - $new_user = $this->createUser(); - $entity->user_id->target_id = $new_user->id(); - $this->assertEqual($new_user->id(), $entity->user_id->target_id, format_string('%entity_type: Updated user id can be read.', array('%entity_type' => $entity_type))); - $this->assertEqual($new_user->getUsername(), $entity->user_id->entity->name->value, format_string('%entity_type: Updated username value can be read.', array('%entity_type' => $entity_type))); + $new_user2 = $this->createUser(); + $entity->user_id->target_id = $new_user2->id(); + $this->assertEqual($new_user2->id(), $entity->user_id->target_id, format_string('%entity_type: Updated user id can be read.', array('%entity_type' => $entity_type))); + $this->assertEqual($new_user2->getUsername(), $entity->user_id->entity->name->value, format_string('%entity_type: Updated username value can be read.', array('%entity_type' => $entity_type))); // Try unsetting a field. $entity->name->value = NULL; @@ -148,6 +148,34 @@ protected function doTestReadWrite($entity_type) { $this->assertNull($entity->user_id->target_id, format_string('%entity_type: User ID field is not set.', array('%entity_type' => $entity_type))); $this->assertNull($entity->user_id->entity, format_string('%entity_type: User entity field is not set.', array('%entity_type' => $entity_type))); + // Test setting the values via the typed data API works as well. + // Change the assigned user by entity. + $entity->user_id->first()->get('entity')->setValue($new_user2); + $this->assertEqual($new_user2->id(), $entity->user_id->target_id, format_string('%entity_type: Updated user id can be read.', array('%entity_type' => $entity_type))); + $this->assertEqual($new_user2->getUsername(), $entity->user_id->entity->name->value, format_string('%entity_type: Updated user name value can be read.', array('%entity_type' => $entity_type))); + + // Change the assigned user by id. + $entity->user_id->first()->get('target_id')->setValue($new_user2->id()); + $this->assertEqual($new_user2->id(), $entity->user_id->target_id, format_string('%entity_type: Updated user id can be read.', array('%entity_type' => $entity_type))); + $this->assertEqual($new_user2->getUsername(), $entity->user_id->entity->name->value, format_string('%entity_type: Updated user name value can be read.', array('%entity_type' => $entity_type))); + + // Try unsetting a field. + $entity->name->first()->get('value')->setValue(NULL); + $entity->user_id->first()->get('target_id')->setValue(NULL); + $this->assertNull($entity->name->value, format_string('%entity_type: Name field is not set.', array('%entity_type' => $entity_type))); + $this->assertNull($entity->user_id->target_id, format_string('%entity_type: User ID field is not set.', array('%entity_type' => $entity_type))); + $this->assertNull($entity->user_id->entity, format_string('%entity_type: User entity field is not set.', array('%entity_type' => $entity_type))); + + // Create a fresh entity so target_id does not get its property object + // instantiated, then verify setting a new value via typed data API works. + $entity2 = entity_create($entity_type, array( + 'user_id' => array('target_id' => $new_user1->id()), + )); + // Access the property object, and set a value. + $entity2->user_id->first()->get('target_id')->setValue($new_user2->id()); + $this->assertEqual($new_user2->id(), $entity2->user_id->target_id, format_string('%entity_type: Updated user id can be read.', array('%entity_type' => $entity_type))); + $this->assertEqual($new_user2->name->value, $entity2->user_id->entity->name->value, format_string('%entity_type: Updated user name value can be read.', array('%entity_type' => $entity_type))); + // Test using isset(), empty() and unset(). $entity->name->value = 'test unset'; unset($entity->name->value); diff --git a/core/modules/text/src/Plugin/Field/FieldType/TextItemBase.php b/core/modules/text/src/Plugin/Field/FieldType/TextItemBase.php index e08f9513734beea9cf8eb08f81e629525a56b47a..972b6e274e30a1c621961c7e188a6538b7280b38 100644 --- a/core/modules/text/src/Plugin/Field/FieldType/TextItemBase.php +++ b/core/modules/text/src/Plugin/Field/FieldType/TextItemBase.php @@ -59,20 +59,16 @@ public function isEmpty() { /** * {@inheritdoc} */ - public function onChange($property_name) { - // Notify the parent of changes. - if (isset($this->parent)) { - $this->parent->onChange($this->name); - } - + public function onChange($property_name, $notify = TRUE) { // Unset processed properties that are affected by the change. foreach ($this->definition->getPropertyDefinitions() as $property => $definition) { if ($definition->getClass() == '\Drupal\text\TextProcessed') { if ($property_name == 'format' || ($definition->getSetting('text source') == $property_name)) { - $this->set($property, NULL, FALSE); + $this->writePropertyValue($property, NULL); } } } + parent::onChange($property_name, $notify); } /**