summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorxjm2017-02-28 10:13:35 -0600
committerxjm2017-02-28 10:13:35 -0600
commit6cca8a6da0b3ec90523422ed6a30424d04303319 (patch)
tree4302050c1c00c029cba7eacfc9a76c982f1b004f
parentb1242b2928e7830412fa8fde0d2e916bc09e0d1b (diff)
Issue #2751325 by damiankloip, Grayside, dawehner, Wim Leers, catch, tedbow, alexpott, himanshu-dixit, Jo Fitzgerald, xjm, andrewbelcher, skyredwang, effulgentsia, hampercm, eelkeblok: All serialized values are strings, should be integers/booleans when appropriate
-rw-r--r--core/modules/hal/src/Normalizer/FieldItemNormalizer.php9
-rw-r--r--core/modules/hal/tests/src/Kernel/FileNormalizeTest.php23
-rw-r--r--core/modules/hal/tests/src/Kernel/NormalizerTestBase.php31
-rw-r--r--core/modules/rest/src/Plugin/rest/resource/EntityResource.php39
-rw-r--r--core/modules/rest/tests/src/Functional/EntityResource/Comment/CommentResourceTestBase.php10
-rw-r--r--core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php66
-rw-r--r--core/modules/rest/tests/src/Functional/EntityResource/EntityTest/EntityTestResourceTestBase.php6
-rw-r--r--core/modules/rest/tests/src/Functional/EntityResource/Node/NodeResourceTestBase.php19
-rw-r--r--core/modules/rest/tests/src/Functional/EntityResource/Term/TermResourceTestBase.php2
-rw-r--r--core/modules/rest/tests/src/Functional/EntityResource/User/UserResourceTestBase.php6
-rw-r--r--core/modules/serialization/config/install/serialization.settings.yml4
-rw-r--r--core/modules/serialization/config/schema/serialization.schema.yml7
-rw-r--r--core/modules/serialization/serialization.install37
-rw-r--r--core/modules/serialization/serialization.services.yml9
-rw-r--r--core/modules/serialization/src/EventSubscriber/BcConfigSubscriber.php55
-rw-r--r--core/modules/serialization/src/Normalizer/ComplexDataNormalizer.php1
-rw-r--r--core/modules/serialization/src/Normalizer/PrimitiveDataNormalizer.php32
-rw-r--r--core/modules/serialization/src/RegisterSerializationClassesCompilerPass.php19
-rw-r--r--core/modules/serialization/tests/src/Kernel/EntitySerializationTest.php5
-rw-r--r--core/modules/serialization/tests/src/Unit/Normalizer/PrimitiveDataNormalizerTest.php101
20 files changed, 397 insertions, 84 deletions
diff --git a/core/modules/hal/src/Normalizer/FieldItemNormalizer.php b/core/modules/hal/src/Normalizer/FieldItemNormalizer.php
index 376a444..238557a 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 abd7cd3..d6978e6 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 d53f313..9dac94c 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 @@ abstract class NormalizerTestBase extends KernelTestBase {
'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 3e35f83..a66d5ab 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\Entity\FieldableEntityInterface;
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;
@@ -196,6 +198,41 @@ class EntityResource extends ResourceBase implements DependentPluginInterface {
}
/**
+ * 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.
*
* @param \Drupal\Core\Entity\EntityInterface $original_entity
@@ -239,7 +276,7 @@ class EntityResource extends ResourceBase implements DependentPluginInterface {
}
// 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 b04da54..8b3ea64 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 @@ abstract class CommentResourceTestBase extends EntityResourceTestBase {
],
'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 @@ abstract class CommentResourceTestBase extends EntityResourceTestBase {
],
'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 @@ abstract class CommentResourceTestBase extends EntityResourceTestBase {
],
'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 c7bcb97..ebb03e2 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 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
$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 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
}
$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();
@@ -512,6 +544,30 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
}
/**
+ * 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.
*/
public function testPost() {
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 2d3c998..2935a00 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 @@ abstract class EntityTestResourceTestBase extends EntityResourceTestBase {
],
'id' => [
[
- 'value' => '1',
+ 'value' => 1,
],
],
'langcode' => [
@@ -93,12 +93,12 @@ abstract class EntityTestResourceTestBase extends EntityResourceTestBase {
],
'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 20a9095..cfbd971 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 @@ abstract class NodeResourceTestBase extends EntityResourceTestBase {
],
'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 @@ abstract class NodeResourceTestBase extends EntityResourceTestBase {
],
'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 @@ abstract class NodeResourceTestBase extends EntityResourceTestBase {
],
'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 94a0a6e..698451b 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 @@ abstract class TermResourceTestBase extends EntityResourceTestBase {
],
'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 1e76734..04fe435 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 @@ abstract class UserResourceTestBase extends EntityResourceTestBase {
protected function getExpectedNormalizedEntity() {
return [
'uid' => [
- ['value' => '3'],
+ ['value' => 3],
],
'uuid' => [
['value' => $this->entity->uuid()],
@@ -99,12 +99,12 @@ abstract class UserResourceTestBase extends EntityResourceTestBase {
],
'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 0000000..39c583a
--- /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 0000000..0fdd2da
--- /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 4a4a57c..9f7c71e 100644
--- a/core/modules/serialization/serialization.install
+++ b/core/modules/serialization/serialization.install
@@ -6,6 +6,31 @@
*/
/**
+ * 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). <a href="https://www.drupal.org/node/2837696">Disabling this backwards compatibility mode</a> 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 <a href="https://www.drupal.org/node/2837696">enable backwards compatibility mode</a>.');
+ }
+ }
+
+ return $requirements;
+}
+
+/**
* @defgroup updates-8.2.x-to-8.3.x Updates from 8.2.x to 8.3.x
* @{
* Update functions from 8.2.x to 8.3.x.
@@ -17,5 +42,17 @@
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, <a href="https://www.drupal.org/node/2837696">read the change record to learn how to enable the BC mode.</a>');
+}
+
+/**
* @} 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 c8fb376..3e47b00 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 0000000..79d3a90
--- /dev/null
+++ b/core/modules/serialization/src/EventSubscriber/BcConfigSubscriber.php
@@ -0,0 +1,55 @@
+<?php
+
+namespace Drupal\serialization\EventSubscriber;
+
+use Drupal\Core\Config\ConfigCrudEvent;
+use Drupal\Core\Config\ConfigEvents;
+use Drupal\Core\DrupalKernelInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Config event subscriber to rebuild the container when BC config is saved.
+ */
+class BcConfigSubscriber implements EventSubscriberInterface {
+
+ /**
+ * The Drupal Kernel.
+ *
+ * @var \Drupal\Core\DrupalKernelInterface
+ */
+ protected $kernel;
+
+ /**
+ * BcConfigSubscriber constructor.
+ *
+ * @param \Drupal\Core\DrupalKernelInterface $kernel
+ * The Drupal Kernel.
+ */
+ public function __construct(DrupalKernelInterface $kernel) {
+ $this->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 5062738..ef07d11 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 0000000..cce108c
--- /dev/null
+++ b/core/modules/serialization/src/Normalizer/PrimitiveDataNormalizer.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace Drupal\serialization\Normalizer;
+
+use Drupal\Core\TypedData\PrimitiveInterface;
+
+/**
+ * Converts primitive data objects to their casted values.
+ */
+class PrimitiveDataNormalizer extends NormalizerBase {
+
+ /**
+ * The interface or class that this Normalizer supports.
+ *
+ * @var string
+ */
+ protected $supportedInterfaceOrClass = PrimitiveInterface::class;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function normalize($object, $format = NULL, array $context = []) {
+ // Typed data casts NULL objects to their empty variants, so for example
+ // the empty string ('') for string type data, or 0 for integer typed data.
+ // In a better world with typed data implementing algebraic data types,
+ // getCastedValue would return NULL, but as typed data is not aware of real
+ // optional values on the primitive level, we implement our own optional
+ // value normalization here.
+ return $object->getValue() === NULL ? NULL : $object->getCastedValue();
+ }
+
+}
diff --git a/core/modules/serialization/src/RegisterSerializationClassesCompilerPass.php b/core/modules/serialization/src/RegisterSerializationClassesCompilerPass.php
index f4611f3..7110056 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 @@ class RegisterSerializationClassesCompilerPass implements CompilerPassInterface
// 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);
}
@@ -54,6 +61,18 @@ class RegisterSerializationClassesCompilerPass implements CompilerPassInterface
}
/**
+ * 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.
*
* Order services from highest priority number to lowest (reverse sorting).
diff --git a/core/modules/serialization/tests/src/Kernel/EntitySerializationTest.php b/core/modules/serialization/tests/src/Kernel/EntitySerializationTest.php
index 8d7fdab..d29f493 100644
--- a/core/modules/serialization/tests/src/Kernel/EntitySerializationTest.php
+++ b/core/modules/serialization/tests/src/Kernel/EntitySerializationTest.php
@@ -110,7 +110,8 @@ class EntitySerializationTest extends NormalizerTestBase {
),
'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 @@ class EntitySerializationTest extends NormalizerTestBase {
$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 0000000..acdc6da
--- /dev/null
+++ b/core/modules/serialization/tests/src/Unit/Normalizer/PrimitiveDataNormalizerTest.php
@@ -0,0 +1,101 @@
+<?php
+
+namespace Drupal\Tests\serialization\Unit\Normalizer;
+
+use Drupal\Core\TypedData\DataDefinition;
+use Drupal\Core\TypedData\Plugin\DataType\BooleanData;
+use Drupal\Core\TypedData\Plugin\DataType\IntegerData;
+use Drupal\Core\TypedData\Plugin\DataType\StringData;
+use Drupal\Tests\UnitTestCase;
+use Drupal\serialization\Normalizer\PrimitiveDataNormalizer;
+
+/**
+ * @coversDefaultClass \Drupal\serialization\Normalizer\PrimitiveDataNormalizer
+ * @group serialization
+ */
+class PrimitiveDataNormalizerTest extends UnitTestCase {
+
+ /**
+ * The TypedDataNormalizer instance.
+ *
+ * @var \Drupal\serialization\Normalizer\TypedDataNormalizer
+ */
+ protected $normalizer;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp() {
+ $this->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;
+ }
+
+}