diff --git a/core/modules/hal/src/Normalizer/FieldItemNormalizer.php b/core/modules/hal/src/Normalizer/FieldItemNormalizer.php index 376a444b65cd44dedf88a2fd0044c42fe960949d..238557af2dc14093bf933cdabea0401e17096920 100644 --- a/core/modules/hal/src/Normalizer/FieldItemNormalizer.php +++ b/core/modules/hal/src/Normalizer/FieldItemNormalizer.php @@ -21,7 +21,14 @@ class FieldItemNormalizer extends NormalizerBase { * {@inheritdoc} */ public function normalize($field_item, $format = NULL, array $context = array()) { - $values = $field_item->toArray(); + $values = []; + // We normalize each individual property, so each can do their own casting, + // if needed. + /** @var \Drupal\Core\TypedData\TypedDataInterface $property */ + foreach ($field_item as $property_name => $property) { + $values[$property_name] = $this->serializer->normalize($property, $format, $context); + } + if (isset($context['langcode'])) { $values['lang'] = $context['langcode']; } diff --git a/core/modules/hal/tests/src/Kernel/FileNormalizeTest.php b/core/modules/hal/tests/src/Kernel/FileNormalizeTest.php index abd7cd3b90ca1d246ca282cd975f4f50ef743183..d6978e656f4198296e0f12b4d7924f2791e82874 100644 --- a/core/modules/hal/tests/src/Kernel/FileNormalizeTest.php +++ b/core/modules/hal/tests/src/Kernel/FileNormalizeTest.php @@ -2,16 +2,7 @@ namespace Drupal\Tests\hal\Kernel; -use Drupal\Core\Cache\MemoryBackend; use Drupal\file\Entity\File; -use Drupal\hal\Encoder\JsonEncoder; -use Drupal\hal\LinkManager\LinkManager; -use Drupal\hal\LinkManager\RelationLinkManager; -use Drupal\hal\LinkManager\TypeLinkManager; -use Drupal\hal\Normalizer\FieldItemNormalizer; -use Drupal\hal\Normalizer\FileEntityNormalizer; -use Symfony\Component\Serializer\Serializer; - /** * Tests that file entities can be normalized in HAL. @@ -33,20 +24,6 @@ class FileNormalizeTest extends NormalizerTestBase { protected function setUp() { parent::setUp(); $this->installEntitySchema('file'); - - $entity_manager = \Drupal::entityManager(); - $link_manager = new LinkManager(new TypeLinkManager(new MemoryBackend(), \Drupal::moduleHandler(), \Drupal::service('config.factory'), \Drupal::service('request_stack'), \Drupal::service('entity_type.bundle.info')), new RelationLinkManager(new MemoryBackend(), $entity_manager, \Drupal::moduleHandler(), \Drupal::service('config.factory'), \Drupal::service('request_stack'))); - - // Set up the mock serializer. - $normalizers = array( - new FieldItemNormalizer(), - new FileEntityNormalizer($entity_manager, \Drupal::httpClient(), $link_manager, \Drupal::moduleHandler()), - ); - - $encoders = array( - new JsonEncoder(), - ); - $this->serializer = new Serializer($normalizers, $encoders); } diff --git a/core/modules/hal/tests/src/Kernel/NormalizerTestBase.php b/core/modules/hal/tests/src/Kernel/NormalizerTestBase.php index d53f313b1f813cf00ef7edebd28057c04c19253e..9dac94c81f3236773490443d4c3af2023c536623 100644 --- a/core/modules/hal/tests/src/Kernel/NormalizerTestBase.php +++ b/core/modules/hal/tests/src/Kernel/NormalizerTestBase.php @@ -2,22 +2,9 @@ namespace Drupal\Tests\hal\Kernel; -use Drupal\Core\Cache\MemoryBackend; use Drupal\field\Entity\FieldConfig; -use Drupal\hal\Encoder\JsonEncoder; -use Drupal\hal\LinkManager\LinkManager; -use Drupal\hal\LinkManager\RelationLinkManager; -use Drupal\hal\LinkManager\TypeLinkManager; -use Drupal\hal\Normalizer\ContentEntityNormalizer; -use Drupal\hal\Normalizer\EntityReferenceItemNormalizer; -use Drupal\hal\Normalizer\FieldItemNormalizer; -use Drupal\hal\Normalizer\FieldNormalizer; use Drupal\language\Entity\ConfigurableLanguage; -use Drupal\serialization\EntityResolver\ChainEntityResolver; -use Drupal\serialization\EntityResolver\TargetIdResolver; -use Drupal\serialization\EntityResolver\UuidResolver; use Drupal\KernelTests\KernelTestBase; -use Symfony\Component\Serializer\Serializer; use Drupal\field\Entity\FieldStorageConfig; /** @@ -130,23 +117,7 @@ protected function setUp() { 'translatable' => TRUE, ])->save(); - $entity_manager = \Drupal::entityManager(); - $link_manager = new LinkManager(new TypeLinkManager(new MemoryBackend(), \Drupal::moduleHandler(), \Drupal::service('config.factory'), \Drupal::service('request_stack'), \Drupal::service('entity_type.bundle.info')), new RelationLinkManager(new MemoryBackend(), $entity_manager, \Drupal::moduleHandler(), \Drupal::service('config.factory'), \Drupal::service('request_stack'))); - - $chain_resolver = new ChainEntityResolver(array(new UuidResolver($entity_manager), new TargetIdResolver())); - - // Set up the mock serializer. - $normalizers = array( - new ContentEntityNormalizer($link_manager, $entity_manager, \Drupal::moduleHandler()), - new EntityReferenceItemNormalizer($link_manager, $chain_resolver), - new FieldItemNormalizer(), - new FieldNormalizer(), - ); - - $encoders = array( - new JsonEncoder(), - ); - $this->serializer = new Serializer($normalizers, $encoders); + $this->serializer = $this->container->get('serializer'); } } diff --git a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php index 3e35f83171932dc5e477ec728785c1a2c6e5e127..a66d5ab9948f553c9e62a9df189c4252f7876a98 100644 --- a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php +++ b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php @@ -11,6 +11,8 @@ use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityStorageException; +use Drupal\Core\Field\FieldItemListInterface; +use Drupal\Core\TypedData\PrimitiveInterface; use Drupal\rest\Plugin\ResourceBase; use Drupal\rest\ResourceResponse; use Psr\Log\LoggerInterface; @@ -195,6 +197,41 @@ public function post(EntityInterface $entity = NULL) { } } + /** + * Gets the values from the field item list casted to the correct type. + * + * Values are casted to the correct type so we can determine whether or not + * something has changed. REST formats such as JSON support typed data but + * Drupal's database API will return values as strings. Currently, only + * primitive data types know how to cast their values to the correct type. + * + * @param \Drupal\Core\Field\FieldItemListInterface $field_item_list + * The field item list to retrieve its data from. + * + * @return mixed[][] + * The values from the field item list casted to the correct type. The array + * of values returned is a multidimensional array keyed by delta and the + * property name. + */ + protected function getCastedValueFromFieldItemList(FieldItemListInterface $field_item_list) { + $value = $field_item_list->getValue(); + + foreach ($value as $delta => $field_item_value) { + /** @var \Drupal\Core\Field\FieldItemInterface $field_item */ + $field_item = $field_item_list->get($delta); + $properties = $field_item->getProperties(TRUE); + // Foreach field value we check whether we know the underlying property. + // If we exists we try to cast the value. + foreach ($field_item_value as $property_name => $property_value) { + if (isset($properties[$property_name]) && ($property = $field_item->get($property_name)) && $property instanceof PrimitiveInterface) { + $value[$delta][$property_name] = $property->getCastedValue(); + } + } + } + + return $value; + } + /** * Responds to entity PATCH requests. * @@ -239,7 +276,7 @@ public function patch(EntityInterface $original_entity, EntityInterface $entity } // Unchanged values for entity keys don't need access checking. - if ($original_entity->get($field_name)->getValue() === $entity->get($field_name)->getValue()) { + if ($this->getCastedValueFromFieldItemList($original_entity->get($field_name)) === $this->getCastedValueFromFieldItemList($entity->get($field_name))) { continue; } // It is not possible to set the language to NULL as it is automatically diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Comment/CommentResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/Comment/CommentResourceTestBase.php index b04da54373e8e6ebbe1cbf6ba2ce894a19bd6579..8b3ea643f30801a81df5cdfdc0c9d2eb9713a7f4 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/Comment/CommentResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/Comment/CommentResourceTestBase.php @@ -144,17 +144,17 @@ protected function getExpectedNormalizedEntity() { ], 'status' => [ [ - 'value' => 1, + 'value' => TRUE, ], ], 'created' => [ [ - 'value' => '123456789', + 'value' => 123456789, ], ], 'changed' => [ [ - 'value' => (string) $this->entity->getChangedTime(), + 'value' => $this->entity->getChangedTime(), ], ], 'default_langcode' => [ @@ -164,7 +164,7 @@ protected function getExpectedNormalizedEntity() { ], 'uid' => [ [ - 'target_id' => $author->id(), + 'target_id' => (int) $author->id(), 'target_type' => 'user', 'target_uuid' => $author->uuid(), 'url' => base_path() . 'user/' . $author->id(), @@ -178,7 +178,7 @@ protected function getExpectedNormalizedEntity() { ], 'entity_id' => [ [ - 'target_id' => '1', + 'target_id' => 1, 'target_type' => 'entity_test', 'target_uuid' => EntityTest::load(1)->uuid(), 'url' => base_path() . 'entity_test/1', diff --git a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php index c7bcb97167074f49971464a25d36074a29308dc8..ebb03e2c6f4d1ac099517076d9ab9285aceab220 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php @@ -406,11 +406,15 @@ public function testGet() { $this->assertEquals($this->getExpectedCacheTags(), empty($cache_tags_header_value) ? [] : explode(' ', $cache_tags_header_value)); $cache_contexts_header_value = $response->getHeader('X-Drupal-Cache-Contexts')[0]; $this->assertEquals($this->getExpectedCacheContexts(), empty($cache_contexts_header_value) ? [] : explode(' ', $cache_contexts_header_value)); - // Comparing the exact serialization is pointless, because the order of - // fields does not matter (at least not yet). That's why we only compare the - // normalized entity with the decoded response: it's comparing PHP arrays - // instead of strings. - $this->assertEquals($this->getExpectedNormalizedEntity(), $this->serializer->decode((string) $response->getBody(), static::$format)); + // Sort the serialization data first so we can do an identical comparison + // for the keys with the array order the same (it needs to match with + // identical comparison). + $expected = $this->getExpectedNormalizedEntity(); + ksort($expected); + $actual = $this->serializer->decode((string) $response->getBody(), static::$format); + ksort($actual); + $this->assertSame($expected, $actual); + // Not only assert the normalization, also assert deserialization of the // response results in the expected object. $unserialized = $this->serializer->deserialize((string) $response->getBody(), get_class($this->entity), static::$format); @@ -448,7 +452,35 @@ public function testGet() { } $this->assertSame($get_headers, $head_headers); + // Only run this for fieldable entities. It doesn't make sense for config + // entities as config values are already casted. They also run through the + // ConfigEntityNormalizer, which doesn't deal with fields individually. + if ($this->entity instanceof FieldableEntityInterface) { + $this->config('serialization.settings')->set('bc_primitives_as_strings', TRUE)->save(TRUE); + // Rebuild the container so new config is reflected in the removal of the + // PrimitiveDataNormalizer. + $this->rebuildAll(); + + + $response = $this->request('GET', $url, $request_options); + $this->assertResourceResponse(200, FALSE, $response); + + + // Again do an identical comparison, but this time transform the expected + // normalized entity's values to strings. This ensures the BC layer for + // bc_primitives_as_strings works as expected. + $expected = $this->getExpectedNormalizedEntity(); + // Config entities are not affected. + // @see \Drupal\serialization\Normalizer\ConfigEntityNormalizer::normalize() + $expected = static::castToString($expected); + ksort($expected); + $actual = $this->serializer->decode((string) $response->getBody(), static::$format); + ksort($actual); + $this->assertSame($expected, $actual); + } + + // BC: rest_update_8203(). $this->config('rest.settings')->set('bc_entity_resource_permissions', TRUE)->save(TRUE); $this->refreshTestStateAfterRestConfigChange(); @@ -511,6 +543,30 @@ public function testGet() { $this->assertResourceErrorResponse(404, $message, $response); } + /** + * Transforms a normalization: casts all non-string types to strings. + * + * @param array $normalization + * A normalization to transform. + * + * @return array + * The transformed normalization. + */ + protected static function castToString(array $normalization) { + foreach ($normalization as $key => $value) { + if (is_bool($value)) { + $normalization[$key] = (string) (int) $value; + } + elseif (is_int($value) || is_float($value)) { + $normalization[$key] = (string) $value; + } + elseif (is_array($value)) { + $normalization[$key] = static::castToString($value); + } + } + return $normalization; + } + /** * Tests a POST request for an entity, plus edge cases to ensure good DX. */ diff --git a/core/modules/rest/tests/src/Functional/EntityResource/EntityTest/EntityTestResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/EntityTest/EntityTestResourceTestBase.php index 2d3c9980ef44129fbd3e800bf97077ad6cc67f1c..2935a00fa72faac6010c9fe07ced155a54559e8a 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/EntityTest/EntityTestResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityTest/EntityTestResourceTestBase.php @@ -73,7 +73,7 @@ protected function getExpectedNormalizedEntity() { ], 'id' => [ [ - 'value' => '1', + 'value' => 1, ], ], 'langcode' => [ @@ -93,12 +93,12 @@ protected function getExpectedNormalizedEntity() { ], 'created' => [ [ - 'value' => $this->entity->get('created')->value, + 'value' => (int) $this->entity->get('created')->value, ] ], 'user_id' => [ [ - 'target_id' => $author->id(), + 'target_id' => (int) $author->id(), 'target_type' => 'user', 'target_uuid' => $author->uuid(), 'url' => $author->toUrl()->toString(), diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Node/NodeResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/Node/NodeResourceTestBase.php index 20a90955833eb1b40168c0e44daebec8022c17a3..cfbd971611da5eec6979957377df169f670ead2c 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/Node/NodeResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/Node/NodeResourceTestBase.php @@ -116,32 +116,32 @@ protected function getExpectedNormalizedEntity() { ], 'status' => [ [ - 'value' => 1, + 'value' => TRUE, ], ], 'created' => [ [ - 'value' => '123456789', + 'value' => 123456789, ], ], 'changed' => [ [ - 'value' => (string) $this->entity->getChangedTime(), + 'value' => $this->entity->getChangedTime(), ], ], 'promote' => [ [ - 'value' => 1, + 'value' => TRUE, ], ], 'sticky' => [ [ - 'value' => '0', + 'value' => FALSE, ], ], 'revision_timestamp' => [ [ - 'value' => '123456789', + 'value' => 123456789, ], ], 'revision_translation_affected' => [ @@ -156,7 +156,7 @@ protected function getExpectedNormalizedEntity() { ], 'uid' => [ [ - 'target_id' => $author->id(), + 'target_id' => (int) $author->id(), 'target_type' => 'user', 'target_uuid' => $author->uuid(), 'url' => base_path() . 'user/' . $author->id(), @@ -164,14 +164,13 @@ protected function getExpectedNormalizedEntity() { ], 'revision_uid' => [ [ - 'target_id' => $author->id(), + 'target_id' => (int) $author->id(), 'target_type' => 'user', 'target_uuid' => $author->uuid(), 'url' => base_path() . 'user/' . $author->id(), ], ], - 'revision_log' => [ - ], + 'revision_log' => [], ]; } diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Term/TermResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/Term/TermResourceTestBase.php index 94a0a6e265ca4306e005e592b43625518fa45333..698451b58cf353948c7c7f0ed9dd628a11e2bad7 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/Term/TermResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/Term/TermResourceTestBase.php @@ -108,7 +108,7 @@ protected function getExpectedNormalizedEntity() { ], 'changed' => [ [ - 'value' => (string) $this->entity->getChangedTime(), + 'value' => $this->entity->getChangedTime(), ], ], 'default_langcode' => [ diff --git a/core/modules/rest/tests/src/Functional/EntityResource/User/UserResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/User/UserResourceTestBase.php index 1e7673478db5400b43228204dfa9133578dbdf21..04fe435a426511ac504952b5018e2ae0f1a0a7d0 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/User/UserResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/User/UserResourceTestBase.php @@ -82,7 +82,7 @@ protected function createEntity() { protected function getExpectedNormalizedEntity() { return [ 'uid' => [ - ['value' => '3'], + ['value' => 3], ], 'uuid' => [ ['value' => $this->entity->uuid()], @@ -99,12 +99,12 @@ protected function getExpectedNormalizedEntity() { ], 'created' => [ [ - 'value' => '123456789', + 'value' => 123456789, ], ], 'changed' => [ [ - 'value' => (string) $this->entity->getChangedTime(), + 'value' => $this->entity->getChangedTime(), ], ], 'default_langcode' => [ diff --git a/core/modules/serialization/config/install/serialization.settings.yml b/core/modules/serialization/config/install/serialization.settings.yml new file mode 100644 index 0000000000000000000000000000000000000000..39c583aaa63141944928e33c0b22feeed7f5f531 --- /dev/null +++ b/core/modules/serialization/config/install/serialization.settings.yml @@ -0,0 +1,4 @@ +# Before Drupal 8.3, typed data primitive values were normalized as strings, as +# this was usually returned from database storage. A primitive data normalizer +# has been introduced to get the casted value instead. +bc_primitives_as_strings: false diff --git a/core/modules/serialization/config/schema/serialization.schema.yml b/core/modules/serialization/config/schema/serialization.schema.yml new file mode 100644 index 0000000000000000000000000000000000000000..0fdd2da14dba6bf955f80ae7112c6b58b816005c --- /dev/null +++ b/core/modules/serialization/config/schema/serialization.schema.yml @@ -0,0 +1,7 @@ +serialization.settings: + type: config_object + label: 'Serialization settings' + mapping: + bc_primitives_as_strings: + type: boolean + label: 'Whether to retain pre Drupal 8.3 behavior of serializing all primitive items as strings.' diff --git a/core/modules/serialization/serialization.install b/core/modules/serialization/serialization.install index 4a4a57cd9403eafd382b59f3ddadca77e68a8fe6..9f7c71eb135057d447a9385069ad1a191eff2996 100644 --- a/core/modules/serialization/serialization.install +++ b/core/modules/serialization/serialization.install @@ -5,6 +5,31 @@ * Update functions for the Serialization module. */ +/** + * Implements hook_requirements(). + */ +function serialization_requirements($phase) { + $requirements = []; + + if ($phase == 'runtime') { + $requirements['serialization_as_strings'] = array( + 'title' => t('Serialized data types'), + 'severity' => REQUIREMENT_INFO, + ); + + if (\Drupal::config('serialization.settings')->get('bc_primitives_as_strings')) { + $requirements['serialization_as_strings']['value'] = t('Enabled'); + $requirements['serialization_as_strings']['description'] = t('The Serialization API is configured to output only string values for REST and other applications (instead of integers or Booleans when appropriate). Disabling this backwards compatibility mode is recommended unless your sites or applications require string output.'); + } + else { + $requirements['serialization_as_strings']['value'] = t('Not enabled'); + $requirements['serialization_as_strings']['description'] = t('The Serialization API is configured with the recommended default and outputs typed values (integers, Booleans, or strings as appropriate) for REST and other applications. If your site or applications require string output, you can enable backwards compatibility mode.'); + } + } + + return $requirements; +} + /** * @defgroup updates-8.2.x-to-8.3.x Updates from 8.2.x to 8.3.x * @{ @@ -16,6 +41,18 @@ */ function serialization_update_8301() {} +/** + * Add serialization.settings::bc_primitives_as_strings configuration. + */ +function serialization_update_8302() { + $config_factory = \Drupal::configFactory(); + $config_factory->getEditable('serialization.settings') + ->set('bc_primitives_as_strings', FALSE) + ->save(FALSE); + + return t('The REST API will no longer output all values as strings. Integers/booleans will be used where appropriate. If your site depends on these value being strings, read the change record to learn how to enable the BC mode.'); +} + /** * @} End of "defgroup updates-8.2.x-to-8.3.x". */ diff --git a/core/modules/serialization/serialization.services.yml b/core/modules/serialization/serialization.services.yml index c8fb3767f7e2ae54f1b13ca8ff2b0c9ddb8a9386..3e47b005cb9017fa8d8378954bf8de35945dd3e2 100644 --- a/core/modules/serialization/serialization.services.yml +++ b/core/modules/serialization/serialization.services.yml @@ -17,6 +17,10 @@ services: tags: - { name: normalizer } arguments: ['@entity.manager'] + serializer.normalizer.primitive_data: + class: Drupal\serialization\Normalizer\PrimitiveDataNormalizer + tags: + - { name: normalizer, priority: 5, bc: bc_primitives_as_strings, bc_config_name: 'serialization.settings' } serializer.normalizer.complex_data: class: Drupal\serialization\Normalizer\ComplexDataNormalizer tags: @@ -89,3 +93,8 @@ services: tags: - { name: event_subscriber } arguments: ['%serializer.formats%'] + serialization.bc_config_subscriber: + class: Drupal\serialization\EventSubscriber\BcConfigSubscriber + tags: + - { name: event_subscriber } + arguments: ['@kernel'] diff --git a/core/modules/serialization/src/EventSubscriber/BcConfigSubscriber.php b/core/modules/serialization/src/EventSubscriber/BcConfigSubscriber.php new file mode 100644 index 0000000000000000000000000000000000000000..79d3a905b07b9c09e719701b28f9389cf1aff3fc --- /dev/null +++ b/core/modules/serialization/src/EventSubscriber/BcConfigSubscriber.php @@ -0,0 +1,55 @@ +kernel = $kernel; + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + $events[ConfigEvents::SAVE][] = 'onConfigSave'; + return $events; + } + + /** + * Invalidates the service container if serialization BC config gets updated. + * + * @param \Drupal\Core\Config\ConfigCrudEvent $event + */ + public function onConfigSave(ConfigCrudEvent $event) { + $saved_config = $event->getConfig(); + + if ($saved_config->getName() === 'serialization.settings') { + if ($event->isChanged('bc_primitives_as_strings')) { + $this->kernel->invalidateContainer(); + } + } + } + +} diff --git a/core/modules/serialization/src/Normalizer/ComplexDataNormalizer.php b/core/modules/serialization/src/Normalizer/ComplexDataNormalizer.php index 5062738e9d50d35b227db07554ab46df993796e2..ef07d1185cbf057d9c5cc36f08144a758ef05a75 100644 --- a/core/modules/serialization/src/Normalizer/ComplexDataNormalizer.php +++ b/core/modules/serialization/src/Normalizer/ComplexDataNormalizer.php @@ -26,6 +26,7 @@ class ComplexDataNormalizer extends NormalizerBase { */ public function normalize($object, $format = NULL, array $context = array()) { $attributes = array(); + /** @var \Drupal\Core\TypedData\TypedDataInterface $field */ foreach ($object as $name => $field) { $attributes[$name] = $this->serializer->normalize($field, $format, $context); } diff --git a/core/modules/serialization/src/Normalizer/PrimitiveDataNormalizer.php b/core/modules/serialization/src/Normalizer/PrimitiveDataNormalizer.php new file mode 100644 index 0000000000000000000000000000000000000000..cce108cacf3d3a9fa4fd4d1573eaa7c2800e9d04 --- /dev/null +++ b/core/modules/serialization/src/Normalizer/PrimitiveDataNormalizer.php @@ -0,0 +1,32 @@ +getValue() === NULL ? NULL : $object->getCastedValue(); + } + +} diff --git a/core/modules/serialization/src/RegisterSerializationClassesCompilerPass.php b/core/modules/serialization/src/RegisterSerializationClassesCompilerPass.php index f4611f3fc1a529cfa1476cb0ffc66902db6ea59c..7110056f60f70843f84bdeaaf019cafca692ebe2 100644 --- a/core/modules/serialization/src/RegisterSerializationClassesCompilerPass.php +++ b/core/modules/serialization/src/RegisterSerializationClassesCompilerPass.php @@ -2,6 +2,7 @@ namespace Drupal\serialization; +use Drupal\Core\Config\BootstrapConfigStorageFactory; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; @@ -22,6 +23,12 @@ public function process(ContainerBuilder $container) { // Retrieve registered Normalizers and Encoders from the container. foreach ($container->findTaggedServiceIds('normalizer') as $id => $attributes) { + // If there is a BC key present, pass this to determine if the normalizer + // should be skipped. + if (isset($attributes[0]['bc']) && $this->normalizerBcSettingIsEnabled($attributes[0]['bc'], $attributes[0]['bc_config_name'])) { + continue; + } + $priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0; $normalizers[$priority][] = new Reference($id); } @@ -53,6 +60,18 @@ public function process(ContainerBuilder $container) { $container->setParameter('serializer.format_providers', $format_providers); } + /** + * Returns whether a normalizer BC setting is disabled or not. + * + * @param string $key + * + * @return bool + */ + protected function normalizerBcSettingIsEnabled($key, $config_name) { + $settings = BootstrapConfigStorageFactory::get()->read($config_name); + return !empty($settings[$key]); + } + /** * Sorts by priority. * diff --git a/core/modules/serialization/tests/src/Kernel/EntitySerializationTest.php b/core/modules/serialization/tests/src/Kernel/EntitySerializationTest.php index 8d7fdab6ef5be32c3e2af2c056f1f49dc5e7b22b..d29f4937f0259821b7be7bf4fe6b345e09265e6e 100644 --- a/core/modules/serialization/tests/src/Kernel/EntitySerializationTest.php +++ b/core/modules/serialization/tests/src/Kernel/EntitySerializationTest.php @@ -110,7 +110,8 @@ public function testNormalize() { ), 'user_id' => array( array( - 'target_id' => $this->user->id(), + // id() will return the string value as it comes from the database. + 'target_id' => (int) $this->user->id(), 'target_type' => $this->user->getEntityTypeId(), 'target_uuid' => $this->user->uuid(), 'url' => $this->user->url(), @@ -134,7 +135,7 @@ public function testNormalize() { $normalized = $this->serializer->normalize($this->entity); foreach (array_keys($expected) as $fieldName) { - $this->assertEqual($expected[$fieldName], $normalized[$fieldName], "ComplexDataNormalizer produces expected array for $fieldName."); + $this->assertSame($expected[$fieldName], $normalized[$fieldName], "Normalization produces expected array for $fieldName."); } $this->assertEqual(array_diff_key($normalized, $expected), array(), 'No unexpected data is added to the normalized array.'); } diff --git a/core/modules/serialization/tests/src/Unit/Normalizer/PrimitiveDataNormalizerTest.php b/core/modules/serialization/tests/src/Unit/Normalizer/PrimitiveDataNormalizerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..acdc6daa3ac26a2bcde198a3dce708d7137bee30 --- /dev/null +++ b/core/modules/serialization/tests/src/Unit/Normalizer/PrimitiveDataNormalizerTest.php @@ -0,0 +1,101 @@ +normalizer = new PrimitiveDataNormalizer(); + } + + /** + * @covers ::supportsNormalization + * @dataProvider dataProviderPrimitiveData + */ + public function testSupportsNormalization($primitive_data, $expected) { + $this->assertTrue($this->normalizer->supportsNormalization($primitive_data)); + } + + /** + * @covers ::supportsNormalization + */ + public function testSupportsNormalizationFail() { + // Test that an object not implementing PrimitiveInterface fails. + $this->assertFalse($this->normalizer->supportsNormalization(new \stdClass())); + } + + /** + * @covers ::normalize + * @dataProvider dataProviderPrimitiveData + */ + public function testNormalize($primitive_data, $expected) { + $this->assertSame($expected, $this->normalizer->normalize($primitive_data)); + } + + /** + * Data provider for testNormalize(). + */ + public function dataProviderPrimitiveData() { + $data = []; + + $definition = DataDefinition::createFromDataType('string'); + $string = new StringData($definition, 'string'); + $string->setValue('test'); + + $data['string'] = [$string, 'test']; + + $definition = DataDefinition::createFromDataType('string'); + $string = new StringData($definition, 'string'); + $string->setValue(NULL); + + $data['string-null'] = [$string, NULL]; + + $definition = DataDefinition::createFromDataType('integer'); + $integer = new IntegerData($definition, 'integer'); + $integer->setValue(5); + + $data['integer'] = [$integer, 5]; + + $definition = DataDefinition::createFromDataType('integer'); + $integer = new IntegerData($definition, 'integer'); + $integer->setValue(NULL); + + $data['integer-null'] = [$integer, NULL]; + + $definition = DataDefinition::createFromDataType('boolean'); + $boolean = new BooleanData($definition, 'boolean'); + $boolean->setValue(TRUE); + + $data['boolean'] = [$boolean, TRUE]; + + $definition = DataDefinition::createFromDataType('boolean'); + $boolean = new BooleanData($definition, 'boolean'); + $boolean->setValue(NULL); + + $data['boolean-null'] = [$boolean, NULL]; + + return $data; + } + +}