diff --git a/core/modules/hal/src/Normalizer/ContentEntityNormalizer.php b/core/modules/hal/src/Normalizer/ContentEntityNormalizer.php index e1efc207de9ec62aecdc8e5bb35e6776cfb962bc..6a54b3c50205760e73d381dcc29618ad5dbf80cd 100644 --- a/core/modules/hal/src/Normalizer/ContentEntityNormalizer.php +++ b/core/modules/hal/src/Normalizer/ContentEntityNormalizer.php @@ -7,6 +7,7 @@ use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\rest\LinkManager\LinkManagerInterface; +use Drupal\serialization\Normalizer\FieldableEntityNormalizerTrait; use Symfony\Component\Serializer\Exception\UnexpectedValueException; /** @@ -14,6 +15,8 @@ */ class ContentEntityNormalizer extends NormalizerBase { + use FieldableEntityNormalizerTrait; + /** * The interface or class that this Normalizer supports. * @@ -28,13 +31,6 @@ class ContentEntityNormalizer extends NormalizerBase { */ protected $linkManager; - /** - * The entity manager. - * - * @var \Drupal\Core\Entity\EntityManagerInterface - */ - protected $entityManager; - /** * The module handler. * @@ -42,7 +38,6 @@ class ContentEntityNormalizer extends NormalizerBase { */ protected $moduleHandler; - /** * Constructs an ContentEntityNormalizer object. * @@ -128,7 +123,7 @@ public function denormalize($data, $class, $format = NULL, array $context = arra // Create the entity. $typed_data_ids = $this->getTypedDataIds($data['_links']['type'], $context); - $entity_type = $this->entityManager->getDefinition($typed_data_ids['entity_type']); + $entity_type = $this->getEntityTypeDefinition($typed_data_ids['entity_type']); $default_langcode_key = $entity_type->getKey('default_langcode'); $langcode_key = $entity_type->getKey('langcode'); $values = array(); @@ -174,24 +169,12 @@ public function denormalize($data, $class, $format = NULL, array $context = arra } } + $this->denormalizeFieldData($data, $entity, $format, $context); + // Pass the names of the fields whose values can be merged. + // @todo https://www.drupal.org/node/2456257 remove this. $entity->_restSubmittedFields = array_keys($data); - // Iterate through remaining items in data array. These should all - // correspond to fields. - foreach ($data as $field_name => $field_data) { - $items = $entity->get($field_name); - // Remove any values that were set as a part of entity creation (e.g - // uuid). If the incoming field data is set to an empty array, this will - // also have the effect of emptying the field in REST module. - $items->setValue(array()); - if ($field_data) { - // Denormalize the field data into the FieldItemList object. - $context['target_instance'] = $items; - $this->serializer->denormalize($field_data, get_class($items), $format, $context); - } - } - return $entity; } diff --git a/core/modules/serialization/serialization.services.yml b/core/modules/serialization/serialization.services.yml index 8b570c076308abff5f3948b3468102b5cb235db5..cfb71adcf99e792b213c55383d25093aaa0f91c5 100644 --- a/core/modules/serialization/serialization.services.yml +++ b/core/modules/serialization/serialization.services.yml @@ -25,13 +25,28 @@ services: class: Drupal\serialization\Normalizer\EntityReferenceFieldItemNormalizer tags: # Set the priority lower than the hal entity reference field item - # normalizer, so that we do not replace that for hal_json. + # normalizer, so that we do not replace that for hal_json but higher than + # this modules generic field item normalizer. # @todo Find a better way for this in https://www.drupal.org/node/2575761. - - { name: normalizer, priority: 5 } + - { name: normalizer, priority: 8 } + serialization.normalizer.field_item: + class: Drupal\serialization\Normalizer\FieldItemNormalizer + tags: + # Priority must be lower than serializer.normalizer.field_item.hal and any + # field type specific normalizer such as + # serializer.normalizer.entity_reference_field_item. + - { name: normalizer, priority: 6 } + serialization.normalizer.field: + class: Drupal\serialization\Normalizer\FieldNormalizer + tags: + # Priority must be lower than serializer.normalizer.field.hal. + - { name: normalizer, priority: 6 } serializer.normalizer.list: class: Drupal\serialization\Normalizer\ListNormalizer tags: - - { name: normalizer } + # Priority must be higher than serialization.normalizer.field but less + # than hal field normalizer. + - { name: normalizer, priority: 9 } serializer.normalizer.password_field_item: class: Drupal\serialization\Normalizer\NullNormalizer arguments: ['Drupal\Core\Field\Plugin\Field\FieldType\PasswordItem'] diff --git a/core/modules/serialization/src/Normalizer/ContentEntityNormalizer.php b/core/modules/serialization/src/Normalizer/ContentEntityNormalizer.php index 0fb5300ad890a62c1ad411f85074fdb7992a7eec..b47eeef6cc93be0610d1371f5d4c023b09ce608d 100644 --- a/core/modules/serialization/src/Normalizer/ContentEntityNormalizer.php +++ b/core/modules/serialization/src/Normalizer/ContentEntityNormalizer.php @@ -8,9 +8,7 @@ class ContentEntityNormalizer extends EntityNormalizer { /** - * The interface or class that this Normalizer supports. - * - * @var array + * {@inheritdoc} */ protected $supportedInterfaceOrClass = ['Drupal\Core\Entity\ContentEntityInterface']; diff --git a/core/modules/serialization/src/Normalizer/EntityNormalizer.php b/core/modules/serialization/src/Normalizer/EntityNormalizer.php index b800e812af35fb67815f2ea68f96644d6c5013eb..84baec94be87fbfdb16780ee33f34244ba0e9613 100644 --- a/core/modules/serialization/src/Normalizer/EntityNormalizer.php +++ b/core/modules/serialization/src/Normalizer/EntityNormalizer.php @@ -2,8 +2,9 @@ namespace Drupal\serialization\Normalizer; +use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityManagerInterface; -use Symfony\Component\Serializer\Exception\UnexpectedValueException; +use Drupal\Core\Entity\FieldableEntityInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; /** @@ -11,19 +12,14 @@ */ class EntityNormalizer extends ComplexDataNormalizer implements DenormalizerInterface { + use FieldableEntityNormalizerTrait; + /** * The interface or class that this Normalizer supports. * * @var array */ - protected $supportedInterfaceOrClass = array('Drupal\Core\Entity\EntityInterface'); - - /** - * The entity manager. - * - * @var \Drupal\Core\Entity\EntityManagerInterface - */ - protected $entityManager; + protected $supportedInterfaceOrClass = [EntityInterface::class]; /** * Constructs an EntityNormalizer object. @@ -39,48 +35,25 @@ public function __construct(EntityManagerInterface $entity_manager) { * {@inheritdoc} */ public function denormalize($data, $class, $format = NULL, array $context = []) { - // Get the entity type ID while letting context override the $class param. - $entity_type_id = !empty($context['entity_type']) ? $context['entity_type'] : $this->entityManager->getEntityTypeFromClass($class); - - /** @var \Drupal\Core\Entity\EntityTypeInterface $entity_type_definition */ - // Get the entity type definition. - $entity_type_definition = $this->entityManager->getDefinition($entity_type_id, FALSE); + $entity_type_id = $this->determineEntityTypeId($class, $context); + $entity_type_definition = $this->getEntityTypeDefinition($entity_type_id); - // Don't try to create an entity without an entity type id. - if (!$entity_type_definition) { - throw new UnexpectedValueException(sprintf('The specified entity type "%s" does not exist. A valid etnity type is required for denormalization', $entity_type_id)); - } + // The bundle property will be required to denormalize a bundleable + // fieldable entity. + if ($entity_type_definition->hasKey('bundle') && $entity_type_definition->isSubclassOf(FieldableEntityInterface::class)) { + // Get an array containing the bundle only. This also remove the bundle + // key from the $data array. + $bundle_data = $this->extractBundleData($data, $entity_type_definition); - // The bundle property will be required to denormalize a bundleable entity. - if ($entity_type_definition->hasKey('bundle')) { - $bundle_key = $entity_type_definition->getKey('bundle'); - // Get the base field definitions for this entity type. - $base_field_definitions = $this->entityManager->getBaseFieldDefinitions($entity_type_id); + // Create the entity from bundle data only, then apply field values after. + $entity = $this->entityManager->getStorage($entity_type_id)->create($bundle_data); - // Get the ID key from the base field definition for the bundle key or - // default to 'value'. - $key_id = isset($base_field_definitions[$bundle_key]) ? $base_field_definitions[$bundle_key]->getFieldStorageDefinition()->getMainPropertyName() : 'value'; - - // Normalize the bundle if it is not explicitly set. - $data[$bundle_key] = isset($data[$bundle_key][0][$key_id]) ? $data[$bundle_key][0][$key_id] : (isset($data[$bundle_key]) ? $data[$bundle_key] : NULL); - - // Get the bundle entity type from the entity type definition. - $bundle_type_id = $entity_type_definition->getBundleEntityType(); - $bundle_types = $bundle_type_id ? $this->entityManager->getStorage($bundle_type_id)->getQuery()->execute() : []; - - // Make sure a bundle has been provided. - if (!is_string($data[$bundle_key])) { - throw new UnexpectedValueException('A string must be provided as a bundle value.'); - } - - // Make sure the submitted bundle is a valid bundle for the entity type. - if ($bundle_types && !in_array($data[$bundle_key], $bundle_types)) { - throw new UnexpectedValueException(sprintf('"%s" is not a valid bundle type for denormalization.', $data[$bundle_key])); - } + $this->denormalizeFieldData($data, $entity, $format, $context); + } + else { + // Create the entity from all data. + $entity = $this->entityManager->getStorage($entity_type_id)->create($data); } - - // Create the entity from data. - $entity = $this->entityManager->getStorage($entity_type_id)->create($data); // Pass the names of the fields whose values can be merged. // @todo https://www.drupal.org/node/2456257 remove this. diff --git a/core/modules/serialization/src/Normalizer/FieldItemNormalizer.php b/core/modules/serialization/src/Normalizer/FieldItemNormalizer.php new file mode 100644 index 0000000000000000000000000000000000000000..a3cfcf206f5e64ca8cf87c7a452fe187dbb7e1c3 --- /dev/null +++ b/core/modules/serialization/src/Normalizer/FieldItemNormalizer.php @@ -0,0 +1,57 @@ +<?php + +namespace Drupal\serialization\Normalizer; + +use Drupal\Core\Field\FieldItemInterface; +use Symfony\Component\Serializer\Exception\InvalidArgumentException; +use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; + +/** + * Denormalizes field item object structure by updating the entity field values. + */ +class FieldItemNormalizer extends ComplexDataNormalizer implements DenormalizerInterface { + + /** + * {@inheritdoc} + */ + protected $supportedInterfaceOrClass = FieldItemInterface::class; + + /** + * {@inheritdoc} + */ + public function denormalize($data, $class, $format = NULL, array $context = array()) { + if (!isset($context['target_instance'])) { + throw new InvalidArgumentException('$context[\'target_instance\'] must be set to denormalize with the FieldItemNormalizer'); + } + + if ($context['target_instance']->getParent() == NULL) { + throw new InvalidArgumentException('The field item passed in via $context[\'target_instance\'] must have a parent set.'); + } + + /** @var \Drupal\Core\Field\FieldItemInterface $field_item */ + $field_item = $context['target_instance']; + + $field_item->setValue($this->constructValue($data, $context)); + return $field_item; + } + + /** + * Build the field item value using the incoming data. + * + * Most normalizers that extend this class can simply use this method to + * construct the denormalized value without having to override denormalize() + * and reimplementing its validation logic or its call to set the field value. + * + * @param mixed $data + * The incoming data for this field item. + * @param array $context + * The context passed into the Normalizer. + * + * @return mixed + * The value to use in Entity::setValue(). + */ + protected function constructValue($data, $context) { + return $data; + } + +} diff --git a/core/modules/serialization/src/Normalizer/FieldNormalizer.php b/core/modules/serialization/src/Normalizer/FieldNormalizer.php new file mode 100644 index 0000000000000000000000000000000000000000..1847379b23af24402e1bf507f29478f370deadd6 --- /dev/null +++ b/core/modules/serialization/src/Normalizer/FieldNormalizer.php @@ -0,0 +1,47 @@ +<?php + +namespace Drupal\serialization\Normalizer; + +use Drupal\Core\Field\FieldItemListInterface; +use Symfony\Component\Serializer\Exception\InvalidArgumentException; +use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; + +/** + * Denormalizes data to Drupal field values. + * + * This class simply calls denormalize() on the individual FieldItems. The + * FieldItem normalizers are responsible for setting the field values for each + * item. + * + * @see \Drupal\serialization\Normalizer\FieldItemNormalizer. + */ +class FieldNormalizer extends ListNormalizer implements DenormalizerInterface { + + /** + * {@inheritdoc} + */ + protected $supportedInterfaceOrClass = FieldItemListInterface::class; + + /** + * {@inheritdoc} + */ + public function denormalize($data, $class, $format = NULL, array $context = array()) { + if (!isset($context['target_instance'])) { + throw new InvalidArgumentException('$context[\'target_instance\'] must be set to denormalize with the FieldNormalizer'); + } + + /** @var FieldItemListInterface $items */ + $items = $context['target_instance']; + $item_class = $items->getItemDefinition()->getClass(); + foreach ($data as $item_data) { + // Create a new item and pass it as the target for the unserialization of + // $item_data. All items in field should have removed before this method + // was called. + // @see \Drupal\serialization\Normalizer\ContentEntityNormalizer::denormalize(). + $context['target_instance'] = $items->appendItem(); + $this->serializer->denormalize($item_data, $item_class, $format, $context); + } + return $items; + } + +} diff --git a/core/modules/serialization/src/Normalizer/FieldableEntityNormalizerTrait.php b/core/modules/serialization/src/Normalizer/FieldableEntityNormalizerTrait.php new file mode 100644 index 0000000000000000000000000000000000000000..d97d3a4795550e87bb442a20c406c2d2e74a5fc7 --- /dev/null +++ b/core/modules/serialization/src/Normalizer/FieldableEntityNormalizerTrait.php @@ -0,0 +1,140 @@ +<?php + +namespace Drupal\serialization\Normalizer; + +use Drupal\Core\Entity\EntityTypeInterface; +use Drupal\Core\Entity\FieldableEntityInterface; +use Symfony\Component\Serializer\Exception\UnexpectedValueException; + +/** + * A trait for providing fieldable entity normalization/denormalization methods. + * + * @todo Move this into a FieldableEntityNormalizer in Drupal 9. This is a trait + * used in \Drupal\serialization\Normalizer\EntityNormalizer to maintain BC. + * @see https://www.drupal.org/node/2834734 + */ +trait FieldableEntityNormalizerTrait { + + /** + * The entity manager. + * + * @var \Drupal\Core\Entity\EntityManagerInterface + */ + protected $entityManager; + + /** + * Determines the entity type ID to denormalize as. + * + * @param string $class + * The entity type class to be denormalized to. + * @param array $context + * The serialization context data. + * + * @return string + * The entity type ID. + */ + protected function determineEntityTypeId($class, $context) { + // Get the entity type ID while letting context override the $class param. + return !empty($context['entity_type']) ? $context['entity_type'] : $this->entityManager->getEntityTypeFromClass($class); + } + + /** + * Gets the entity type definition. + * + * @param string $entity_type_id + * The entity type ID to load the definition for. + * + * @return \Drupal\Core\Entity\EntityTypeInterface + * The loaded entity type definition. + * + * @throws \Symfony\Component\Serializer\Exception\UnexpectedValueException + */ + protected function getEntityTypeDefinition($entity_type_id) { + /** @var \Drupal\Core\Entity\EntityTypeInterface $entity_type_definition */ + // Get the entity type definition. + $entity_type_definition = $this->entityManager->getDefinition($entity_type_id, FALSE); + + // Don't try to create an entity without an entity type id. + if (!$entity_type_definition) { + throw new UnexpectedValueException(sprintf('The specified entity type "%s" does not exist. A valid entity type is required for denormalization', $entity_type_id)); + } + + return $entity_type_definition; + } + + /** + * Denormalizes the bundle property so entity creation can use it. + * + * @param array $data + * The data being denormalized. + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type_definition + * The entity type definition. + * + * @throws \Symfony\Component\Serializer\Exception\UnexpectedValueException + * + * @return string + * The valid bundle name. + */ + protected function extractBundleData(array &$data, EntityTypeInterface $entity_type_definition) { + $bundle_key = $entity_type_definition->getKey('bundle'); + // Get the base field definitions for this entity type. + $base_field_definitions = $this->entityManager->getBaseFieldDefinitions($entity_type_definition->id()); + + // Get the ID key from the base field definition for the bundle key or + // default to 'value'. + $key_id = isset($base_field_definitions[$bundle_key]) ? $base_field_definitions[$bundle_key]->getFieldStorageDefinition()->getMainPropertyName() : 'value'; + + // Normalize the bundle if it is not explicitly set. + $bundle_value = isset($data[$bundle_key][0][$key_id]) ? $data[$bundle_key][0][$key_id] : (isset($data[$bundle_key]) ? $data[$bundle_key] : NULL); + // Unset the bundle from the data. + unset($data[$bundle_key]); + + // Get the bundle entity type from the entity type definition. + $bundle_type_id = $entity_type_definition->getBundleEntityType(); + $bundle_types = $bundle_type_id ? $this->entityManager->getStorage($bundle_type_id)->getQuery()->execute() : []; + + // Make sure a bundle has been provided. + if (!is_string($bundle_value)) { + throw new UnexpectedValueException('A string must be provided as a bundle value.'); + } + + // Make sure the submitted bundle is a valid bundle for the entity type. + if ($bundle_types && !in_array($bundle_value, $bundle_types)) { + throw new UnexpectedValueException(sprintf('"%s" is not a valid bundle type for denormalization.', $bundle_value)); + } + + return [$bundle_key => $bundle_value]; + } + + /** + * Denormalizes entity data by denormalizing each field individually. + * + * @param array $data + * The data to denormalize. + * @param \Drupal\Core\Entity\FieldableEntityInterface $entity + * The fieldable entity to set field values for. + * @param string $format + * The serialization format. + * @param array $context + * The context data. + */ + protected function denormalizeFieldData(array $data, FieldableEntityInterface $entity, $format, array $context) { + foreach ($data as $field_name => $field_data) { + $field_item_list = $entity->get($field_name); + + // Remove any values that were set as a part of entity creation (e.g + // uuid). If the incoming field data is set to an empty array, this will + // also have the effect of emptying the field in REST module. + $field_item_list->setValue([]); + $field_item_list_class = get_class($field_item_list); + + if ($field_data) { + // The field instance must be passed in the context so that the field + // denormalizer can update field values for the parent entity. + $context['target_instance'] = $field_item_list; + $this->serializer->denormalize($field_data, $field_item_list_class, $format, $context); + } + } + } + +} diff --git a/core/modules/serialization/tests/modules/field_normalization_test/field_normalization_test.info.yml b/core/modules/serialization/tests/modules/field_normalization_test/field_normalization_test.info.yml new file mode 100644 index 0000000000000000000000000000000000000000..4ba215ecbace96073e3915ac6fb9b3faba515fff --- /dev/null +++ b/core/modules/serialization/tests/modules/field_normalization_test/field_normalization_test.info.yml @@ -0,0 +1,6 @@ +name: 'FieldItem normalization test support' +type: module +description: 'Provides test support for fieldItem normalization test support.' +package: Testing +version: VERSION +core: 8.x diff --git a/core/modules/serialization/tests/modules/field_normalization_test/field_normalization_test.services.yml b/core/modules/serialization/tests/modules/field_normalization_test/field_normalization_test.services.yml new file mode 100644 index 0000000000000000000000000000000000000000..36243e795459ea9e204bd7c5adb73aa934360fc0 --- /dev/null +++ b/core/modules/serialization/tests/modules/field_normalization_test/field_normalization_test.services.yml @@ -0,0 +1,6 @@ +services: + serializer.normalizer.silly_fielditem: + class: Drupal\field_normalization_test\Normalization\TextItemSillyNormalizer + tags: + # The priority must be higher than serialization.normalizer.field_item. + - { name: normalizer , priority: 9 } diff --git a/core/modules/serialization/tests/modules/field_normalization_test/src/Normalization/TextItemSillyNormalizer.php b/core/modules/serialization/tests/modules/field_normalization_test/src/Normalization/TextItemSillyNormalizer.php new file mode 100644 index 0000000000000000000000000000000000000000..7187bdf267366240a8da616ea4a9c3f18f125a2c --- /dev/null +++ b/core/modules/serialization/tests/modules/field_normalization_test/src/Normalization/TextItemSillyNormalizer.php @@ -0,0 +1,36 @@ +<?php + +namespace Drupal\field_normalization_test\Normalization; + +use Drupal\serialization\Normalizer\FieldItemNormalizer; +use Drupal\text\Plugin\Field\FieldType\TextItemBase; + +/** + * A test TextItem normalizer to test denormalization. + */ +class TextItemSillyNormalizer extends FieldItemNormalizer { + + /** + * {@inheritdoc} + */ + protected $supportedInterfaceOrClass = TextItemBase::class; + + /** + * {@inheritdoc} + */ + public function normalize($object, $format = NULL, array $context = array()) { + $data = parent::normalize($object, $format, $context); + $data['value'] .= '::silly_suffix'; + return $data; + } + + /** + * {@inheritdoc} + */ + protected function constructValue($data, $context) { + $value = parent::constructValue($data, $context); + $value['value'] = str_replace('::silly_suffix', '', $value['value']); + return $value; + } + +} diff --git a/core/modules/serialization/tests/src/Kernel/FieldItemSerializationTest.php b/core/modules/serialization/tests/src/Kernel/FieldItemSerializationTest.php new file mode 100644 index 0000000000000000000000000000000000000000..43fc9a90cd3df41be11d0c392927ef543bd33474 --- /dev/null +++ b/core/modules/serialization/tests/src/Kernel/FieldItemSerializationTest.php @@ -0,0 +1,120 @@ +<?php + +namespace Drupal\Tests\serialization\Kernel; + +use Drupal\entity_test\Entity\EntityTestMulRev; +use Drupal\field\Entity\FieldConfig; +use Drupal\field\Entity\FieldStorageConfig; + +/** + * Test field level normalization process. + * + * @group serialization + */ +class FieldItemSerializationTest extends NormalizerTestBase { + + /** + * {@inheritdoc} + */ + public static $modules = array('serialization', 'system', 'field', 'entity_test', 'text', 'filter', 'user', 'field_normalization_test'); + + /** + * The class name of the test class. + * + * @var string + */ + protected $entityClass = 'Drupal\entity_test\Entity\EntityTestMulRev'; + + /** + * The test values. + * + * @var array + */ + protected $values; + + /** + * The test entity. + * + * @var \Drupal\Core\Entity\ContentEntityBase + */ + protected $entity; + + /** + * The serializer service. + * + * @var \Symfony\Component\Serializer\Serializer. + */ + protected $serializer; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + // Auto-create a field for testing default field values. + FieldStorageConfig::create(array( + 'entity_type' => 'entity_test_mulrev', + 'field_name' => 'field_test_text_default', + 'type' => 'text', + 'cardinality' => 1, + 'translatable' => FALSE, + ))->save(); + FieldConfig::create(array( + 'entity_type' => 'entity_test_mulrev', + 'field_name' => 'field_test_text_default', + 'bundle' => 'entity_test_mulrev', + 'label' => 'Test text-field with default', + 'default_value' => [ + [ + 'value' => 'This is the default', + 'format' => 'full_html', + ], + ], + 'widget' => array( + 'type' => 'text_textfield', + 'weight' => 0, + ), + ))->save(); + + // Create a test entity to serialize. + $this->values = array( + 'name' => $this->randomMachineName(), + 'field_test_text' => array( + 'value' => $this->randomMachineName(), + 'format' => 'full_html', + ), + ); + $this->entity = EntityTestMulRev::create($this->values); + $this->entity->save(); + + $this->serializer = $this->container->get('serializer'); + + $this->installConfig(array('field')); + } + + /** + * Tests normalizing and denormalizing an entity with field item normalizer. + */ + public function testFieldNormalizeDenormalize() { + $normalized = $this->serializer->normalize($this->entity, 'json'); + + $expected_field_value = $this->entity->field_test_text[0]->getValue()['value'] . '::silly_suffix'; + $this->assertEquals($expected_field_value, $normalized['field_test_text'][0]['value'], 'Text field item normalized'); + $denormalized = $this->serializer->denormalize($normalized, $this->entityClass, 'json'); + + $this->assertEquals($denormalized->field_test_text[0]->getValue(), $this->entity->field_test_text[0]->getValue(), 'Text field item denormalized.'); + $this->assertEquals($denormalized->field_test_text_default[0]->getValue(), $this->entity->field_test_text_default[0]->getValue(), 'Text field item with default denormalized.'); + + // Unset the values for text field that has a default value. + unset($normalized['field_test_text_default']); + $denormalized_without_all_fields = $this->serializer->denormalize($normalized, $this->entityClass, 'json'); + // Check that denormalized entity is still the same even if not all fields + // are not provided. + $this->assertEquals($denormalized_without_all_fields->field_test_text[0]->getValue(), $this->entity->field_test_text[0]->getValue(), 'Text field item denormalized.'); + // Even though field_test_text_default value was unset before + // denormalization it should still have the default values for the field. + $this->assertEquals($denormalized_without_all_fields->field_test_text_default[0]->getValue(), $this->entity->field_test_text_default[0]->getValue(), 'Text field item with default denormalized.'); + } + +} diff --git a/core/modules/serialization/tests/src/Unit/Normalizer/EntityNormalizerTest.php b/core/modules/serialization/tests/src/Unit/Normalizer/EntityNormalizerTest.php index 702107015501febd584c71bed3a939ea768a59e3..b3b2780fd0118f1c3c985e45258231f5320aaddd 100644 --- a/core/modules/serialization/tests/src/Unit/Normalizer/EntityNormalizerTest.php +++ b/core/modules/serialization/tests/src/Unit/Normalizer/EntityNormalizerTest.php @@ -2,6 +2,8 @@ namespace Drupal\Tests\serialization\Unit\Normalizer; +use Drupal\Core\Entity\FieldableEntityInterface; +use Drupal\Core\Field\FieldItemListInterface; use Drupal\serialization\Normalizer\EntityNormalizer; use Drupal\Tests\UnitTestCase; @@ -104,6 +106,10 @@ public function testDenormalizeWithValidBundle() { ]; $entity_type = $this->getMock('Drupal\Core\Entity\EntityTypeInterface'); + + $entity_type->expects($this->once()) + ->method('id') + ->willReturn('test'); $entity_type->expects($this->once()) ->method('hasKey') ->with('bundle') @@ -112,6 +118,11 @@ public function testDenormalizeWithValidBundle() { ->method('getKey') ->with('bundle') ->will($this->returnValue('test_type')); + $entity_type->expects($this->once()) + ->method('isSubClassOf') + ->with(FieldableEntityInterface::class) + ->willReturn(TRUE); + $entity_type->expects($this->once()) ->method('getBundleEntityType') ->will($this->returnValue('test_bundle')); @@ -154,24 +165,50 @@ public function testDenormalizeWithValidBundle() { ->with('test_bundle') ->will($this->returnValue($entity_type_storage)); - // The expected test data should have a modified test_type property. + $key_1 = $this->getMock(FieldItemListInterface::class); + $key_2 = $this->getMock(FieldItemListInterface::class); + + $entity = $this->getMock(FieldableEntityInterface::class); + $entity->expects($this->at(0)) + ->method('get') + ->with('key_1') + ->willReturn($key_1); + $entity->expects($this->at(1)) + ->method('get') + ->with('key_2') + ->willReturn($key_2); + + $storage = $this->getMock('Drupal\Core\Entity\EntityStorageInterface'); + // Create should only be called with the bundle property at first. $expected_test_data = array( - 'key_1' => 'value_1', - 'key_2' => 'value_2', 'test_type' => 'test_bundle', ); - $storage = $this->getMock('Drupal\Core\Entity\EntityStorageInterface'); $storage->expects($this->once()) ->method('create') ->with($expected_test_data) - ->will($this->returnValue($this->getMock('Drupal\Core\Entity\EntityInterface'))); + ->will($this->returnValue($entity)); $this->entityManager->expects($this->at(3)) ->method('getStorage') ->with('test') ->will($this->returnValue($storage)); + // Setup expectations for the serializer. This will be called for each field + // item. + $serializer = $this->getMockBuilder('Symfony\Component\Serializer\Serializer') + ->disableOriginalConstructor() + ->setMethods(array('denormalize')) + ->getMock(); + $serializer->expects($this->at(0)) + ->method('denormalize') + ->with('value_1', get_class($key_1), NULL, ['target_instance' => $key_1, 'entity_type' => 'test']); + $serializer->expects($this->at(1)) + ->method('denormalize') + ->with('value_2', get_class($key_2), NULL, ['target_instance' => $key_2, 'entity_type' => 'test']); + + $this->entityNormalizer->setSerializer($serializer); + $this->assertNotNull($this->entityNormalizer->denormalize($test_data, 'Drupal\Core\Entity\ContentEntityBase', NULL, ['entity_type' => 'test'])); } @@ -192,6 +229,10 @@ public function testDenormalizeWithInvalidBundle() { ]; $entity_type = $this->getMock('Drupal\Core\Entity\EntityTypeInterface'); + + $entity_type->expects($this->once()) + ->method('id') + ->willReturn('test'); $entity_type->expects($this->once()) ->method('hasKey') ->with('bundle') @@ -200,6 +241,11 @@ public function testDenormalizeWithInvalidBundle() { ->method('getKey') ->with('bundle') ->will($this->returnValue('test_type')); + $entity_type->expects($this->once()) + ->method('isSubClassOf') + ->with(FieldableEntityInterface::class) + ->willReturn(TRUE); + $entity_type->expects($this->once()) ->method('getBundleEntityType') ->will($this->returnValue('test_bundle')); @@ -242,8 +288,7 @@ public function testDenormalizeWithInvalidBundle() { ->with('test_bundle') ->will($this->returnValue($entity_type_storage)); - - $this->entityNormalizer->denormalize($test_data, 'Drupal\Core\Entity\ContentEntityBase', NULL, ['entity_type' => 'test']); + $this->assertNotNull($this->entityNormalizer->denormalize($test_data, 'Drupal\Core\Entity\ContentEntityBase', NULL, ['entity_type' => 'test'])); } /**