From 1506b0af4dfd48f3fbd7c4681e7813fb9e8d08fc Mon Sep 17 00:00:00 2001 From: Nathaniel Catchpole <catch@35733.no-reply.drupal.org> Date: Wed, 13 Mar 2019 17:04:47 +0000 Subject: [PATCH] Issue #3037970 by Berdir, Baysaa: Custom serialized field's data should be normalized even if it's empty --- .../hal/tests/src/Kernel/DenormalizeTest.php | 49 ++++++++++ .../src/Kernel/LinkItemSerializationTest.php | 21 +++++ .../src/Kernel/EntitySerializationTest.php | 89 +++++++++++++++++++ ...EntityReferenceFieldItemNormalizerTest.php | 2 +- .../TimestampItemNormalizerTest.php | 2 +- .../src/Entity/EntitySerializedField.php | 49 ++++++++++ .../Plugin/Field/FieldType/SerializedItem.php | 48 ++++++++++ .../FieldType/SerializedPropertyItem.php | 50 +++++++++++ 8 files changed, 308 insertions(+), 2 deletions(-) create mode 100644 core/modules/system/tests/modules/entity_test/src/Entity/EntitySerializedField.php create mode 100644 core/modules/system/tests/modules/entity_test/src/Plugin/Field/FieldType/SerializedItem.php create mode 100644 core/modules/system/tests/modules/entity_test/src/Plugin/Field/FieldType/SerializedPropertyItem.php diff --git a/core/modules/hal/tests/src/Kernel/DenormalizeTest.php b/core/modules/hal/tests/src/Kernel/DenormalizeTest.php index 87eb97329f00..6c6fb731c98a 100644 --- a/core/modules/hal/tests/src/Kernel/DenormalizeTest.php +++ b/core/modules/hal/tests/src/Kernel/DenormalizeTest.php @@ -3,6 +3,7 @@ namespace Drupal\Tests\hal\Kernel; use Drupal\Core\Url; +use Drupal\entity_test\Entity\EntitySerializedField; use Drupal\field\Entity\FieldConfig; use Symfony\Component\Serializer\Exception\UnexpectedValueException; @@ -139,4 +140,52 @@ public function testMarkFieldForDeletion() { $this->assertEqual($entity->field_test_text->count(), 0); } + /** + * Tests normalizing/denormalizing serialized columns. + */ + public function testDenormalizeSerializedItem() { + $entity = EntitySerializedField::create(['serialized' => 'boo']); + $normalized = $this->serializer->normalize($entity, $this->format); + $this->setExpectedException(\LogicException::class, 'The generic FieldItemNormalizer cannot denormalize string values for "value" properties of the "serialized" field (field item class: Drupal\entity_test\Plugin\Field\FieldType\SerializedItem).'); + $this->serializer->denormalize($normalized, EntitySerializedField::class, $this->format); + } + + /** + * Tests normalizing/denormalizing invalid custom serialized fields. + */ + public function testDenormalizeInvalidCustomSerializedField() { + $entity = EntitySerializedField::create(['serialized_long' => serialize(['Hello world!'])]); + $normalized = $this->serializer->normalize($entity); + $this->assertEquals($normalized['serialized_long'][0]['value'], ['Hello world!']); + + $normalized['serialized_long'][0]['value'] = 'boo'; + $this->setExpectedException(\LogicException::class, 'The generic FieldItemNormalizer cannot denormalize string values for "value" properties of the "serialized_long" field (field item class: Drupal\Core\Field\Plugin\Field\FieldType\StringLongItem).'); + $this->serializer->denormalize($normalized, EntitySerializedField::class); + } + + /** + * Tests normalizing/denormalizing empty custom serialized fields. + */ + public function testDenormalizeEmptyCustomSerializedField() { + $entity = EntitySerializedField::create(['serialized_long' => serialize([])]); + $normalized = $this->serializer->normalize($entity); + $this->assertEquals([], $normalized['serialized_long'][0]['value']); + + $entity = $this->serializer->denormalize($normalized, EntitySerializedField::class); + $this->assertEquals(serialize([]), $entity->get('serialized_long')->value); + } + + /** + * Tests normalizing/denormalizing valid custom serialized fields. + */ + public function testDenormalizeValidCustomSerializedField() { + $entity = EntitySerializedField::create(['serialized_long' => serialize(['key' => 'value'])]); + $normalized = $this->serializer->normalize($entity); + $this->assertEquals(['key' => 'value'], $normalized['serialized_long'][0]['value']); + + $entity = $this->serializer->denormalize($normalized, EntitySerializedField::class); + + $this->assertEquals(serialize(['key' => 'value']), $entity->get('serialized_long')->value); + } + } diff --git a/core/modules/link/tests/src/Kernel/LinkItemSerializationTest.php b/core/modules/link/tests/src/Kernel/LinkItemSerializationTest.php index dde7dc117fbd..66209eff6f35 100644 --- a/core/modules/link/tests/src/Kernel/LinkItemSerializationTest.php +++ b/core/modules/link/tests/src/Kernel/LinkItemSerializationTest.php @@ -80,4 +80,25 @@ public function testLinkSerialization() { $this->assertSame($options_expected, $deserialized->field_test->options); } + /** + * Tests the deserialization. + */ + public function testLinkDeserialization() { + // Create entity. + $entity = EntityTest::create(); + $url = 'https://www.drupal.org?test_param=test_value'; + $parsed_url = UrlHelper::parse($url); + $title = $this->randomMachineName(); + $entity->field_test->uri = $parsed_url['path']; + $entity->field_test->title = $title; + $entity->field_test->first() + ->get('options') + ->set('query', $parsed_url['query']); + $json = json_decode($this->serializer->serialize($entity, 'json'), TRUE); + $json['field_test'][0]['options'] = 'string data'; + $serialized = json_encode($json, TRUE); + $this->setExpectedException(\LogicException::class, 'The generic FieldItemNormalizer cannot denormalize string values for "options" properties of the "field_test" field (field item class: Drupal\link\Plugin\Field\FieldType\LinkItem).'); + $this->serializer->deserialize($serialized, EntityTest::class, 'json'); + } + } diff --git a/core/modules/serialization/tests/src/Kernel/EntitySerializationTest.php b/core/modules/serialization/tests/src/Kernel/EntitySerializationTest.php index c95a9d96e60f..eff749805a51 100644 --- a/core/modules/serialization/tests/src/Kernel/EntitySerializationTest.php +++ b/core/modules/serialization/tests/src/Kernel/EntitySerializationTest.php @@ -4,6 +4,7 @@ use Drupal\Component\Serialization\Json; use Drupal\Component\Render\FormattableMarkup; +use Drupal\entity_test\Entity\EntitySerializedField; use Drupal\entity_test\Entity\EntityTestMulRev; use Drupal\filter\Entity\FilterFormat; use Drupal\Tests\rest\Functional\BcTimestampNormalizerUnixTestTrait; @@ -260,4 +261,92 @@ public function testDenormalize() { } } + /** + * Tests denormalizing serialized columns. + */ + public function testDenormalizeSerializedItem() { + $this->setExpectedException(\LogicException::class, 'The generic FieldItemNormalizer cannot denormalize string values for "value" properties of the "serialized" field (field item class: Drupal\entity_test\Plugin\Field\FieldType\SerializedItem).'); + $this->serializer->denormalize([ + 'serialized' => [ + [ + 'value' => 'boo', + ], + ], + 'type' => 'entity_test_serialized_field', + ], EntitySerializedField::class); + } + + /** + * Tests normalizing/denormalizing custom serialized columns. + */ + public function testDenormalizeCustomSerializedItem() { + $entity = EntitySerializedField::create(['serialized_text' => serialize(['Hello world!'])]); + $normalized = $this->serializer->normalize($entity); + $this->assertEquals($normalized['serialized_text'][0]['value'], ['Hello world!']); + $this->setExpectedException(\LogicException::class, 'The generic FieldItemNormalizer cannot denormalize string values for "value" properties of the "serialized_text" field (field item class: Drupal\entity_test\Plugin\Field\FieldType\SerializedPropertyItem).'); + $this->serializer->denormalize([ + 'serialized_text' => [ + [ + 'value' => 'boo', + ], + ], + 'type' => 'entity_test_serialized_field', + ], EntitySerializedField::class); + } + + /** + * Tests normalizing/denormalizing invalid custom serialized fields. + */ + public function testDenormalizeInvalidCustomSerializedField() { + $entity = EntitySerializedField::create(['serialized_long' => serialize(['Hello world!'])]); + $normalized = $this->serializer->normalize($entity); + $this->assertEquals($normalized['serialized_long'][0]['value'], ['Hello world!']); + $this->setExpectedException(\LogicException::class, 'The generic FieldItemNormalizer cannot denormalize string values for "value" properties of the "serialized_long" field (field item class: Drupal\Core\Field\Plugin\Field\FieldType\StringLongItem).'); + $this->serializer->denormalize([ + 'serialized_long' => [ + [ + 'value' => 'boo', + ], + ], + 'type' => 'entity_test_serialized_field', + ], EntitySerializedField::class); + } + + /** + * Tests normalizing/denormalizing empty custom serialized fields. + */ + public function testDenormalizeEmptyCustomSerializedField() { + $entity = EntitySerializedField::create(['serialized_long' => serialize([])]); + $normalized = $this->serializer->normalize($entity); + $this->assertEquals([], $normalized['serialized_long'][0]['value']); + + $entity = $this->serializer->denormalize($normalized, EntitySerializedField::class); + + $this->assertEquals(serialize([]), $entity->get('serialized_long')->value); + } + + /** + * Tests normalizing/denormalizing valid custom serialized fields. + */ + public function testDenormalizeValidCustomSerializedField() { + $entity = EntitySerializedField::create(['serialized_long' => serialize(['key' => 'value'])]); + $normalized = $this->serializer->normalize($entity); + $this->assertEquals(['key' => 'value'], $normalized['serialized_long'][0]['value']); + + $entity = $this->serializer->denormalize($normalized, EntitySerializedField::class); + + $this->assertEquals(serialize(['key' => 'value']), $entity->get('serialized_long')->value); + } + + /** + * Tests normalizing/denormalizing using string values. + */ + public function testDenormalizeStringValue() { + $this->setExpectedException(\LogicException::class, 'The generic FieldItemNormalizer cannot denormalize string values for "value" properties of the "serialized_long" field (field item class: Drupal\Core\Field\Plugin\Field\FieldType\StringLongItem).'); + $this->serializer->denormalize([ + 'serialized_long' => ['boo'], + 'type' => 'entity_test_serialized_field', + ], EntitySerializedField::class); + } + } diff --git a/core/modules/serialization/tests/src/Unit/Normalizer/EntityReferenceFieldItemNormalizerTest.php b/core/modules/serialization/tests/src/Unit/Normalizer/EntityReferenceFieldItemNormalizerTest.php index a783d0928c63..581f43181d59 100644 --- a/core/modules/serialization/tests/src/Unit/Normalizer/EntityReferenceFieldItemNormalizerTest.php +++ b/core/modules/serialization/tests/src/Unit/Normalizer/EntityReferenceFieldItemNormalizerTest.php @@ -436,7 +436,7 @@ protected function assertDenormalize(array $data) { ->shouldBeCalled(); } - // Avoid a static method call by returning dummy property data. + // Avoid a static method call by returning dummy serialized property data. $this->fieldDefinition ->getFieldStorageDefinition() ->willReturn() diff --git a/core/modules/serialization/tests/src/Unit/Normalizer/TimestampItemNormalizerTest.php b/core/modules/serialization/tests/src/Unit/Normalizer/TimestampItemNormalizerTest.php index af19ba339dc8..3d74c7cf771b 100644 --- a/core/modules/serialization/tests/src/Unit/Normalizer/TimestampItemNormalizerTest.php +++ b/core/modules/serialization/tests/src/Unit/Normalizer/TimestampItemNormalizerTest.php @@ -123,7 +123,7 @@ public function testDenormalize() { $timestamp_item->setValue(['value' => $timestamp_data_denormalization]) ->shouldBeCalled(); - // Avoid a static method call by returning dummy property data. + // Avoid a static method call by returning dummy serialized property data. $field_definition = $this->prophesize(FieldDefinitionInterface::class); $timestamp_item ->getFieldDefinition() diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntitySerializedField.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntitySerializedField.php new file mode 100644 index 000000000000..0d8bb8bd554a --- /dev/null +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntitySerializedField.php @@ -0,0 +1,49 @@ +<?php + +namespace Drupal\entity_test\Entity; + +use Drupal\Core\Entity\EntityTypeInterface; +use Drupal\Core\Field\BaseFieldDefinition; + +/** + * Defines a test class for testing fields with a serialized column. + * + * @ContentEntityType( + * id = "entity_test_serialized_field", + * label = @Translation("Test serialized fields"), + * entity_keys = { + * "id" = "id", + * "uuid" = "uuid", + * "bundle" = "type", + * "label" = "name" + * }, + * base_table = "entity_test_serialized_fields", + * persistent_cache = FALSE, + * serialized_field_property_names = { + * "serialized_long" = { + * "value" + * } + * } + * ) + */ +class EntitySerializedField extends EntityTest { + + /** + * {@inheritdoc} + */ + public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { + $fields = parent::baseFieldDefinitions($entity_type); + + $fields['serialized'] = BaseFieldDefinition::create('serialized_item_test') + ->setLabel(t('Serialized')); + + $fields['serialized_text'] = BaseFieldDefinition::create('serialized_property_item_test') + ->setLabel(t('Serialized text')); + + $fields['serialized_long'] = BaseFieldDefinition::create('string_long') + ->setLabel(t('Serialized long string')); + + return $fields; + } + +} diff --git a/core/modules/system/tests/modules/entity_test/src/Plugin/Field/FieldType/SerializedItem.php b/core/modules/system/tests/modules/entity_test/src/Plugin/Field/FieldType/SerializedItem.php new file mode 100644 index 000000000000..d460cb2026aa --- /dev/null +++ b/core/modules/system/tests/modules/entity_test/src/Plugin/Field/FieldType/SerializedItem.php @@ -0,0 +1,48 @@ +<?php + +namespace Drupal\entity_test\Plugin\Field\FieldType; + +use Drupal\Core\Field\FieldItemBase; +use Drupal\Core\Field\FieldStorageDefinitionInterface; +use Drupal\Core\StringTranslation\TranslatableMarkup; +use Drupal\Core\TypedData\DataDefinition; + +/** + * Defines the 'serialized_item' entity field type. + * + * @FieldType( + * id = "serialized_item_test", + * label = @Translation("Test serialized field item"), + * description = @Translation("A field containing a serialized string value."), + * category = @Translation("Field"), + * ) + */ +class SerializedItem extends FieldItemBase { + + /** + * {@inheritdoc} + */ + public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) { + $properties['value'] = DataDefinition::create('string') + ->setLabel(new TranslatableMarkup('Test serialized value')) + ->setRequired(TRUE); + + return $properties; + } + + /** + * {@inheritdoc} + */ + public static function schema(FieldStorageDefinitionInterface $field_definition) { + return [ + 'columns' => [ + 'value' => [ + 'type' => 'blob', + 'size' => 'big', + 'serialize' => TRUE, + ], + ], + ]; + } + +} diff --git a/core/modules/system/tests/modules/entity_test/src/Plugin/Field/FieldType/SerializedPropertyItem.php b/core/modules/system/tests/modules/entity_test/src/Plugin/Field/FieldType/SerializedPropertyItem.php new file mode 100644 index 000000000000..c9b067fe421f --- /dev/null +++ b/core/modules/system/tests/modules/entity_test/src/Plugin/Field/FieldType/SerializedPropertyItem.php @@ -0,0 +1,50 @@ +<?php + +namespace Drupal\entity_test\Plugin\Field\FieldType; + +use Drupal\Core\Field\FieldItemBase; +use Drupal\Core\Field\FieldStorageDefinitionInterface; +use Drupal\Core\StringTranslation\TranslatableMarkup; +use Drupal\Core\TypedData\DataDefinition; + +/** + * Defines the 'serialized_property_item_test' entity field type. + * + * @FieldType( + * id = "serialized_property_item_test", + * label = @Translation("Test serialized property field item"), + * description = @Translation("A field containing a string representing serialized data."), + * category = @Translation("Field"), + * serialized_property_names = { + * "value" + * } + * ) + */ +class SerializedPropertyItem extends FieldItemBase { + + /** + * {@inheritdoc} + */ + public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) { + $properties['value'] = DataDefinition::create('string') + ->setLabel(new TranslatableMarkup('Test serialized value')) + ->setRequired(TRUE); + + return $properties; + } + + /** + * {@inheritdoc} + */ + public static function schema(FieldStorageDefinitionInterface $field_definition) { + return [ + 'columns' => [ + 'value' => [ + 'type' => 'text', + 'size' => 'big', + ], + ], + ]; + } + +} -- GitLab