diff --git a/core/lib/Drupal/Core/Cache/ConditionalCacheabilityMetadataBubblingTrait.php b/core/lib/Drupal/Core/Cache/ConditionalCacheabilityMetadataBubblingTrait.php new file mode 100644 index 0000000..b8c4b33 --- /dev/null +++ b/core/lib/Drupal/Core/Cache/ConditionalCacheabilityMetadataBubblingTrait.php @@ -0,0 +1,32 @@ +renderer->hasRenderContext()) { + $build = []; + CacheableMetadata::createFromObject($object)->applyTo($build); + $this->renderer->render($build); + } + } + +} diff --git a/core/modules/hal/src/Normalizer/ContentEntityNormalizer.php b/core/modules/hal/src/Normalizer/ContentEntityNormalizer.php index e1efc20..6a54b3c 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. * @@ -29,20 +32,12 @@ class ContentEntityNormalizer extends NormalizerBase { protected $linkManager; /** - * The entity manager. - * - * @var \Drupal\Core\Entity\EntityManagerInterface - */ - protected $entityManager; - - /** * The module handler. * * @var \Drupal\Core\Extension\ModuleHandlerInterface */ 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/image/src/ImageServiceProvider.php b/core/modules/image/src/ImageServiceProvider.php new file mode 100644 index 0000000..694df8d --- /dev/null +++ b/core/modules/image/src/ImageServiceProvider.php @@ -0,0 +1,35 @@ +getParameter('container.modules'); + if (isset($modules['serialization'])) { + // Add an ImageItem normalizer. + $service_definition = new Definition(ImageItemNormalizer::class, [ + new Reference('entity_type.manager'), + new Reference('renderer'), + ]); + // Priority should be higher than + // serializer.normalizer.entity_reference_field_item but lower than + // serializer.normalizer.entity_reference_item.hal. + $service_definition->addTag('normalizer', ['priority' => 7]); + $container->setDefinition('image.normalizer.image_item', $service_definition); + } + } + +} diff --git a/core/modules/image/src/Normalizer/ImageItemNormalizer.php b/core/modules/image/src/Normalizer/ImageItemNormalizer.php new file mode 100644 index 0000000..d661a41 --- /dev/null +++ b/core/modules/image/src/Normalizer/ImageItemNormalizer.php @@ -0,0 +1,79 @@ +entityTypeManager = $entity_type_manager; + $this->renderer = $renderer; + } + + /** + * {@inheritdoc} + */ + public function normalize($object, $format = NULL, array $context = array()) { + $data = parent::normalize($object, $format, $context); + if (empty($data['target_id'])) { + return $data; + } + /** @var \Drupal\file\FileInterface $image */ + $image = File::load($data['target_id']); + $uri = $image->getFileUri(); + /** @var \Drupal\image\ImageStyleInterface[] $styles */ + $styles = ImageStyle::loadMultiple(); + $data['image_styles'] = []; + foreach ($styles as $id => $style) { + $dimensions = ['width' => $data['width'], 'height' => $data['height']]; + $style->transformDimensions($dimensions, $uri); + $data['image_styles'][$id] = [ + 'url' => file_url_transform_relative($style->buildUrl($uri)), + 'height' => empty($dimensions['height']) ? NULL : $dimensions['height'], + 'width' => empty($dimensions['width']) ? NULL : $dimensions['width'], + ]; + $this->bubble($style); + } + return $data; + } + +} diff --git a/core/modules/image/tests/src/Kernel/Normalizer/ImageItemNormalizerTest.php b/core/modules/image/tests/src/Kernel/Normalizer/ImageItemNormalizerTest.php new file mode 100644 index 0000000..06f32f0 --- /dev/null +++ b/core/modules/image/tests/src/Kernel/Normalizer/ImageItemNormalizerTest.php @@ -0,0 +1,140 @@ +serializer = \Drupal::service('serializer'); + + $this->installEntitySchema('entity_test'); + $this->installEntitySchema('user'); + $this->installEntitySchema('file'); + $this->installConfig('system'); + $this->installConfig('image'); + $this->installSchema('file', ['file_usage']); + + FieldStorageConfig::create(array( + 'entity_type' => 'entity_test', + 'field_name' => 'image_test', + 'type' => 'image', + 'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED, + ))->save(); + FieldConfig::create([ + 'entity_type' => 'entity_test', + 'field_name' => 'image_test', + 'bundle' => 'entity_test', + 'settings' => [ + 'file_extensions' => 'jpg', + ], + ])->save(); + + // Change all images style scale effect to upscale. + /** @var \Drupal\image\Entity\ImageStyle $image_style */ + foreach (ImageStyle::loadMultiple() as $image_style) { + foreach ($image_style->getEffects() as $effect) { + $config = $effect->getConfiguration(); + $config['data']['upscale'] = TRUE; + $effect->setConfiguration($config); + } + $image_style->save(); + } + + file_unmanaged_copy(\Drupal::root() . '/core/misc/druplicon.png', 'public://example.jpg'); + $this->image = File::create([ + 'uri' => 'public://example.jpg', + ]); + $this->image->save(); + } + + /** + * @covers ::normalize + */ + public function testNormalize() { + // Create a test entity with the image field set. + $original_entity = EntityTest::create(); + $original_entity->image_test->target_id = $this->image->id(); + $original_entity->image_test->alt = $alt = $this->randomMachineName(); + $original_entity->image_test->title = $title = $this->randomMachineName(); + $original_entity->name->value = $this->randomMachineName(); + $original_entity->save(); + + $entity = clone $original_entity; + $context = new RenderContext(); + $data = $this->container->get('renderer') + ->executeInRenderContext($context, function () use ($entity) { + return $this->serializer->normalize($entity); + }); + $cacheability = new BubbleableMetadata(); + $cache_tags = []; + /** @var \Drupal\image\ImageStyleInterface $image_style */ + foreach (ImageStyle::loadMultiple() as $image_style) { + $cache_tags = Cache::mergeTags($cache_tags, $image_style->getCacheTags()); + } + $cacheability->setCacheTags($cache_tags); + + $image_style_ids = array_keys($data['image_test'][0]['image_styles']); + $expected_image_style_keys = ['large', 'medium', 'thumbnail']; + $this->assertEquals($cacheability, $context->pop()); + $this->assertEquals($expected_image_style_keys, $image_style_ids); + $expect_dimensions = [ + 'large' => [ + 'width' => '422', + 'height' => '480', + ], + 'medium' => [ + 'width' => '194', + 'height' => '220', + ], + 'thumbnail' => [ + 'width' => '88', + 'height' => '100', + ], + ]; + foreach ($data['image_test'][0]['image_styles'] as $id => $img_info) { + $this->assertNotEmpty(strstr($img_info['url'], "files/styles/$id/public/example.jpg")); + $this->assertEquals($expect_dimensions[$id]['height'], $img_info['height'], "Style $id matches height."); + $this->assertEquals($expect_dimensions[$id]['width'], $img_info['width'], "Style $id matches width."); + } + } + +} diff --git a/core/modules/serialization/serialization.services.yml b/core/modules/serialization/serialization.services.yml index 8b570c0..cfb71ad 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 0fb5300..b47eeef 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 b800e81..84baec9 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 0000000..a3cfcf2 --- /dev/null +++ b/core/modules/serialization/src/Normalizer/FieldItemNormalizer.php @@ -0,0 +1,57 @@ +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 0000000..1847379 --- /dev/null +++ b/core/modules/serialization/src/Normalizer/FieldNormalizer.php @@ -0,0 +1,47 @@ +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 0000000..d97d3a4 --- /dev/null +++ b/core/modules/serialization/src/Normalizer/FieldableEntityNormalizerTrait.php @@ -0,0 +1,140 @@ +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 0000000..4ba215e --- /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 0000000..36243e7 --- /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 0000000..7187bdf --- /dev/null +++ b/core/modules/serialization/tests/modules/field_normalization_test/src/Normalization/TextItemSillyNormalizer.php @@ -0,0 +1,36 @@ + '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 7021070..b3b2780 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') @@ -113,6 +119,11 @@ public function testDenormalizeWithValidBundle() { ->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') @@ -201,6 +242,11 @@ public function testDenormalizeWithInvalidBundle() { ->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'])); } /**