diff --git a/composer.json b/composer.json index b9536cfe35..97c131f9c4 100644 --- a/composer.json +++ b/composer.json @@ -8,7 +8,7 @@ "wikimedia/composer-merge-plugin": "^1.4" }, "replace": { - "drupal/core": "^8.4" + "drupal/core": "^8.5" }, "minimum-stability": "dev", "prefer-stable": true, diff --git a/composer.lock b/composer.lock index 6622a70a91..310254b2e0 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "bec46eaaa9fa07a4cbd8c84302f0d275", + "content-hash": "97de1708c79f6205a295cfc9808c0c72", "packages": [ { "name": "asm89/stack-cors", diff --git a/core/assets/vendor/jquery-once/jquery.once.js b/core/assets/vendor/jquery-once/jquery.once.js index 471dd535cd..f0ed4d0a4b 100644 --- a/core/assets/vendor/jquery-once/jquery.once.js +++ b/core/assets/vendor/jquery-once/jquery.once.js @@ -1,5 +1,5 @@ /*! - * jQuery Once v2.1.1 - http://github.com/robloach/jquery-once + * jQuery Once v2.2.0 - http://github.com/robloach/jquery-once * @license MIT, GPL-2.0 * http://opensource.org/licenses/MIT * http://opensource.org/licenses/GPL-2.0 @@ -29,7 +29,7 @@ /* globals jQuery */ factory(jQuery); } -}(function ($) { +})(function ($) { 'use strict'; /** @@ -40,13 +40,13 @@ * * @returns The valid ID name. * - * @throws Error when an ID is provided, but not a string. + * @throws TypeError when an ID is provided, but not a string. * @private */ var checkId = function (id) { id = id || 'once'; if (typeof id !== 'string') { - throw new Error('The jQuery Once id parameter must be a string'); + throw new TypeError('The jQuery Once id parameter must be a string'); } return id; }; @@ -173,4 +173,4 @@ return $(this).data(name) === true; }); }; -})); +}); diff --git a/core/assets/vendor/jquery-once/jquery.once.min.js b/core/assets/vendor/jquery-once/jquery.once.min.js index d15b280cfc..56c4ba17b5 100644 --- a/core/assets/vendor/jquery-once/jquery.once.min.js +++ b/core/assets/vendor/jquery-once/jquery.once.min.js @@ -1,8 +1,8 @@ /*! - * jQuery Once v2.1.1 - http://github.com/robloach/jquery-once + * jQuery Once v2.2.0 - http://github.com/robloach/jquery-once * @license MIT, GPL-2.0 * http://opensource.org/licenses/MIT * http://opensource.org/licenses/GPL-2.0 */ -(function(e){"use strict";if(typeof exports==="object"){e(require("jquery"))}else if(typeof define==="function"&&define.amd){define(["jquery"],e)}else{e(jQuery)}})(function(e){"use strict";var n=function(e){e=e||"once";if(typeof e!=="string"){throw new Error("The jQuery Once id parameter must be a string")}return e};e.fn.once=function(t){var r="jquery-once-"+n(t);return this.filter(function(){return e(this).data(r)!==true}).data(r,true)};e.fn.removeOnce=function(e){return this.findOnce(e).removeData("jquery-once-"+n(e))};e.fn.findOnce=function(t){var r="jquery-once-"+n(t);return this.filter(function(){return e(this).data(r)===true})}}); +(function(e){"use strict";if(typeof exports==="object"){e(require("jquery"))}else if(typeof define==="function"&&define.amd){define(["jquery"],e)}else{e(jQuery)}})(function(e){"use strict";var n=function(e){e=e||"once";if(typeof e!=="string"){throw new TypeError("The jQuery Once id parameter must be a string")}return e};e.fn.once=function(t){var r="jquery-once-"+n(t);return this.filter(function(){return e(this).data(r)!==true}).data(r,true)};e.fn.removeOnce=function(e){return this.findOnce(e).removeData("jquery-once-"+n(e))};e.fn.findOnce=function(t){var r="jquery-once-"+n(t);return this.filter(function(){return e(this).data(r)===true})}}); //# sourceMappingURL=jquery.once.min.js.map \ No newline at end of file diff --git a/core/assets/vendor/jquery-once/jquery.once.min.js.map b/core/assets/vendor/jquery-once/jquery.once.min.js.map index 1db77fa011..2d48c5cbed 100644 --- a/core/assets/vendor/jquery-once/jquery.once.min.js.map +++ b/core/assets/vendor/jquery-once/jquery.once.min.js.map @@ -1 +1 @@ -{"version":3,"file":"jquery.once.min.js","sources":["jquery.once.js"],"names":["factory","exports","require","define","amd","jQuery","$","checkId","id","Error","fn","once","name","this","filter","data","removeOnce","findOnce","removeData"],"mappings":";;;;;;CAgBC,SAAUA,GACT,YAEA,UAAWC,WAAY,SAAU,CAE/BD,EAAQE,QAAQ,eACX,UAAWC,UAAW,YAAcA,OAAOC,IAAK,CAGrDD,QAAQ,UAAWH,OACd,CAGLA,EAAQK,WAEV,SAAUC,GACV,YAaA,IAAIC,GAAU,SAAUC,GACtBA,EAAKA,GAAM,MACX,UAAWA,KAAO,SAAU,CAC1B,KAAM,IAAIC,OAAM,iDAElB,MAAOD,GAyCTF,GAAEI,GAAGC,KAAO,SAAUH,GAEpB,GAAII,GAAO,eAAiBL,EAAQC,EAGpC,OAAOK,MAAKC,OAAO,WACjB,MAAOR,GAAEO,MAAME,KAAKH,KAAU,OAC7BG,KAAKH,EAAM,MAiChBN,GAAEI,GAAGM,WAAa,SAAUR,GAE1B,MAAOK,MAAKI,SAAST,GAAIU,WAAW,eAAiBX,EAAQC,IAkC/DF,GAAEI,GAAGO,SAAW,SAAUT,GAExB,GAAII,GAAO,eAAiBL,EAAQC,EAEpC,OAAOK,MAAKC,OAAO,WACjB,MAAOR,GAAEO,MAAME,KAAKH,KAAU"} \ No newline at end of file +{"version":3,"sources":["jquery.once.js"],"names":["factory","exports","require","define","amd","jQuery","$","checkId","id","TypeError","fn","once","name","this","filter","data","removeOnce","findOnce","removeData"],"mappings":";;;;;;CAgBA,SAAWA,GACT,aAEA,UAAWC,UAAY,SAAU,CAE/BD,EAAQE,QAAQ,gBACX,UAAWC,SAAW,YAAcA,OAAOC,IAAK,CAGrDD,QAAQ,UAAWH,OACd,CAGLA,EAAQK,WAET,SAAUC,GACX,aAaA,IAAIC,EAAU,SAAUC,GACtBA,EAAKA,GAAM,OACX,UAAWA,IAAO,SAAU,CAC1B,MAAM,IAAIC,UAAU,iDAEtB,OAAOD,GAyCTF,EAAEI,GAAGC,KAAO,SAAUH,GAEpB,IAAII,EAAO,eAAiBL,EAAQC,GAGpC,OAAOK,KAAKC,OAAO,WACjB,OAAOR,EAAEO,MAAME,KAAKH,KAAU,OAC7BG,KAAKH,EAAM,OAiChBN,EAAEI,GAAGM,WAAa,SAAUR,GAE1B,OAAOK,KAAKI,SAAST,GAAIU,WAAW,eAAiBX,EAAQC,KAkC/DF,EAAEI,GAAGO,SAAW,SAAUT,GAExB,IAAII,EAAO,eAAiBL,EAAQC,GAEpC,OAAOK,KAAKC,OAAO,WACjB,OAAOR,EAAEO,MAAME,KAAKH,KAAU","file":"jquery.once.min.js"} \ No newline at end of file diff --git a/core/core.libraries.yml b/core/core.libraries.yml index e645035613..85c956228e 100644 --- a/core/core.libraries.yml +++ b/core/core.libraries.yml @@ -404,10 +404,10 @@ jquery.joyride: jquery.once: remote: https://github.com/RobLoach/jquery-once - version: "2.1.1" + version: "2.2.0" license: name: GNU-GPL-2.0-or-later - url: https://github.com/RobLoach/jquery-once/blob/2.1.1/LICENSE.md + url: https://raw.githubusercontent.com/RobLoach/jquery-once/2.2.0/LICENSE.md gpl-compatible: true js: assets/vendor/jquery-once/jquery.once.min.js: { weight: -19, minified: true } diff --git a/core/lib/Drupal.php b/core/lib/Drupal.php index 1acfc12e9a..07dc12f1f3 100644 --- a/core/lib/Drupal.php +++ b/core/lib/Drupal.php @@ -81,7 +81,7 @@ class Drupal { /** * The current system version. */ - const VERSION = '8.4.0-dev'; + const VERSION = '8.5.0-dev'; /** * Core API compatibility. diff --git a/core/lib/Drupal/Core/Entity/Plugin/DataType/EntityAdapter.php b/core/lib/Drupal/Core/Entity/Plugin/DataType/EntityAdapter.php index eafb6d88bf..4e81796dc3 100644 --- a/core/lib/Drupal/Core/Entity/Plugin/DataType/EntityAdapter.php +++ b/core/lib/Drupal/Core/Entity/Plugin/DataType/EntityAdapter.php @@ -27,7 +27,6 @@ * ) */ class EntityAdapter extends TypedData implements \IteratorAggregate, ComplexDataInterface { - /** * The wrapped entity object. * diff --git a/core/lib/Drupal/Core/Field/FieldConfigBase.php b/core/lib/Drupal/Core/Field/FieldConfigBase.php index e53e84b6c8..d1a0539dd5 100644 --- a/core/lib/Drupal/Core/Field/FieldConfigBase.php +++ b/core/lib/Drupal/Core/Field/FieldConfigBase.php @@ -6,12 +6,14 @@ use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\FieldableEntityInterface; use Drupal\Core\Field\TypedData\FieldItemDataDefinition; +use Drupal\Core\TypedData\ExposableDataDefinitionTrait; /** * Base class for configurable field definitions. */ abstract class FieldConfigBase extends ConfigEntityBase implements FieldConfigInterface { + use ExposableDataDefinitionTrait; /** * The field ID. * diff --git a/core/lib/Drupal/Core/TypedData/DataDefinition.php b/core/lib/Drupal/Core/TypedData/DataDefinition.php index 52a4394cd7..6e0f936a8f 100644 --- a/core/lib/Drupal/Core/TypedData/DataDefinition.php +++ b/core/lib/Drupal/Core/TypedData/DataDefinition.php @@ -9,6 +9,8 @@ class DataDefinition implements DataDefinitionInterface, \ArrayAccess { use TypedDataTrait; + use ExposableDataDefinitionTrait; + /** * The array holding values for all definition keys. * diff --git a/core/lib/Drupal/Core/TypedData/DataDefinitionInterface.php b/core/lib/Drupal/Core/TypedData/DataDefinitionInterface.php index 64d779c317..7ea68e47e6 100644 --- a/core/lib/Drupal/Core/TypedData/DataDefinitionInterface.php +++ b/core/lib/Drupal/Core/TypedData/DataDefinitionInterface.php @@ -218,4 +218,23 @@ public function getConstraint($constraint_name); */ public function addConstraint($constraint_name, $options = NULL); + /** + * Sets the whether the data value should be exposed. + * + * @param bool $exposed + * Whether the data value is exposed. + * + * @return static + * The object itself for chaining. + */ + public function setExposed($exposed = TRUE); + + /** + * Determines whether the data value is exposed. + * + * @return bool + * Whether the data value is exposed. + */ + public function isExposed(); + } diff --git a/core/lib/Drupal/Core/TypedData/ExposableDataDefinitionTrait.php b/core/lib/Drupal/Core/TypedData/ExposableDataDefinitionTrait.php new file mode 100644 index 0000000000..ba47fe35c7 --- /dev/null +++ b/core/lib/Drupal/Core/TypedData/ExposableDataDefinitionTrait.php @@ -0,0 +1,34 @@ +isComputed() && !isset($this->definition['exposed'])) { + return TRUE; + } + return !empty($this->definition['exposed']); + } + + /** + * {@inheritdoc} + * + * Implements setExposed() for \Drupal\Core\TypedData\DataDefinitionInterface. + */ + public function setExposed($exposed = TRUE) { + $this->definition['exposed'] = $exposed; + return $this; + } + +} diff --git a/core/lib/Drupal/Core/TypedData/TypedDataHelper.php b/core/lib/Drupal/Core/TypedData/TypedDataHelper.php new file mode 100644 index 0000000000..1df10eb252 --- /dev/null +++ b/core/lib/Drupal/Core/TypedData/TypedDataHelper.php @@ -0,0 +1,27 @@ +getProperties(TRUE), function (TypedDataInterface $property) { + return $property->getDataDefinition()->isExposed(); + }); + } + +} diff --git a/core/modules/hal/src/Normalizer/FieldItemNormalizer.php b/core/modules/hal/src/Normalizer/FieldItemNormalizer.php index 0c2ee9ed4d..5af3c94ced 100644 --- a/core/modules/hal/src/Normalizer/FieldItemNormalizer.php +++ b/core/modules/hal/src/Normalizer/FieldItemNormalizer.php @@ -3,6 +3,7 @@ namespace Drupal\hal\Normalizer; use Drupal\Core\Field\FieldItemInterface; +use Drupal\serialization\Normalizer\ComplexDataPropertiesNormalizerTrait; use Symfony\Component\Serializer\Exception\InvalidArgumentException; /** @@ -10,6 +11,8 @@ */ class FieldItemNormalizer extends NormalizerBase { + use ComplexDataPropertiesNormalizerTrait; + /** * The interface or class that this Normalizer supports. * @@ -87,13 +90,7 @@ protected function constructValue($data, $context) { * An array of field item values, keyed by property name. */ protected function normalizedFieldValues(FieldItemInterface $field_item, $format, array $context) { - $denormalized = []; - // 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) { - $denormalized[$property_name] = $this->serializer->normalize($property, $format, $context); - } + $denormalized = $this->normalizeProperties($field_item, $format, $context); if (isset($context['langcode'])) { $denormalized['lang'] = $context['langcode']; diff --git a/core/modules/path/src/Plugin/Field/FieldType/PathItem.php b/core/modules/path/src/Plugin/Field/FieldType/PathItem.php index 41063812dd..c77908bac0 100644 --- a/core/modules/path/src/Plugin/Field/FieldType/PathItem.php +++ b/core/modules/path/src/Plugin/Field/FieldType/PathItem.php @@ -186,4 +186,12 @@ protected function ensureLoaded() { } } + /** + * {@inheritdoc} + */ + public function get($property_name) { + $this->ensureLoaded(); + return parent::get($property_name); + } + } diff --git a/core/modules/path/tests/src/Kernel/PathItemTest.php b/core/modules/path/tests/src/Kernel/PathItemTest.php index 9a85a27958..1c7eb28ae0 100644 --- a/core/modules/path/tests/src/Kernel/PathItemTest.php +++ b/core/modules/path/tests/src/Kernel/PathItemTest.php @@ -66,10 +66,18 @@ public function testPathItem() { $node_storage->resetCache(); + // Ensure that isEmpty(), ->alias, and [0]->get('alias')->getValue() can all + // be called individually without the other methods being called. + // @see \Drupal\path\Plugin\Field\FieldType\PathItem::ensureLoaded() /** @var \Drupal\node\NodeInterface $loaded_node */ $loaded_node = $node_storage->load($node->id()); $this->assertFalse($loaded_node->get('path')->isEmpty()); + $node_storage->resetCache(); + $loaded_node = $node_storage->load($node->id()); $this->assertEquals('/foo', $loaded_node->get('path')->alias); + $node_storage->resetCache(); + $loaded_node = $node_storage->load($node->id()); + $this->assertEquals('/foo', $loaded_node->get('path')[0]->get('alias')->getValue()); $node_storage->resetCache(); $loaded_node = $node_storage->load($node->id()); diff --git a/core/modules/rest/src/EventSubscriber/ResourceResponseSubscriber.php b/core/modules/rest/src/EventSubscriber/ResourceResponseSubscriber.php index df77281f37..7dc6585283 100644 --- a/core/modules/rest/src/EventSubscriber/ResourceResponseSubscriber.php +++ b/core/modules/rest/src/EventSubscriber/ResourceResponseSubscriber.php @@ -2,6 +2,7 @@ namespace Drupal\rest\EventSubscriber; +use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Cache\CacheableResponse; use Drupal\Core\Cache\CacheableResponseInterface; use Drupal\Core\Render\RenderContext; @@ -126,11 +127,18 @@ public function getResponseFormat(RouteMatchInterface $route_match, Request $req /** * Renders a resource response body. * - * Serialization can invoke rendering (e.g., generating URLs), but the - * serialization API does not provide a mechanism to collect the - * bubbleable metadata associated with that (e.g., language and other - * contexts), so instead, allow those to "leak" and collect them here in - * a render context. + * During serialization, encoders and normalizers are able to explicitly + * bubble cacheability metadata via the 'cacheability' key-value pair in the + * received context. This bubbled cacheability metadata will be applied to the + * the response. + * + * In prior versions of Drupal 8, we allowed implicit bubbling of cacheability + * metadata because there was no explicit cacheability metadata bubbling API. + * To maintain backwards compatibility, we continue to support this, but + * support for this will be dropped in Drupal 9.0.0. This is especially useful + * when interacting with APIs that implicitly invoke rendering (for example: + * generating URLs): this allows those to "leak", and we collect their bubbled + * cacheability metadata automatically in a render context. * * @param \Symfony\Component\HttpFoundation\Request $request * The request object. @@ -150,14 +158,25 @@ protected function renderResponseBody(Request $request, ResourceResponseInterfac // If there is data to send, serialize and set it as the response body. if ($data !== NULL) { + $serialization_context = [ + 'request' => $request, + 'cacheability' => new CacheableMetadata(), + ]; + + // @deprecated In Drupal 8.4.0, will be removed before Drupal 9.0.0. Use + // explicit cacheability metadata bubbling instead. (The wrapping call to + // executeInRenderContext() will be removed before Drupal 9.0.0.) $context = new RenderContext(); $output = $this->renderer - ->executeInRenderContext($context, function () use ($serializer, $data, $format) { - return $serializer->serialize($data, $format); + ->executeInRenderContext($context, function() use ($serializer, $data, $format, $serialization_context) { + return $serializer->serialize($data, $format, $serialization_context); }); - - if ($response instanceof CacheableResponseInterface && !$context->isEmpty()) { - $response->addCacheableDependency($context->pop()); + if ($response instanceof CacheableResponseInterface) { + if (!$context->isEmpty()) { + @trigger_error('Implicit cacheability metadata bubbling (onto the global render context) in normalizers is deprecated since Drupal 8.4.0 and will be removed in Drupal 9.0.0. Use the "cacheability" serialization context instead, for explicit cacheability metadata bubbling.', E_USER_DEPRECATED); + $response->addCacheableDependency($context->pop()); + } + $response->addCacheableDependency($serialization_context['cacheability']); } $response->setContent($output); diff --git a/core/modules/rest/tests/src/Functional/EntityResource/EntityTest/EntityTestExposedPropertyNormalizerTest.php b/core/modules/rest/tests/src/Functional/EntityResource/EntityTest/EntityTestExposedPropertyNormalizerTest.php new file mode 100644 index 0000000000..5a7df055e7 --- /dev/null +++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityTest/EntityTestExposedPropertyNormalizerTest.php @@ -0,0 +1,99 @@ + 'value to expose', + 'exposed_value' => 'Exposed! value to expose', + ], + ]; + return $expected; + } + + /** + * {@inheritdoc} + */ + protected function createEntity() { + if (!FieldStorageConfig::loadByName('entity_test', 'field_test_exposed')) { + // Auto-create a field for testing. + FieldStorageConfig::create([ + 'entity_type' => 'entity_test', + 'field_name' => 'field_test_exposed', + 'type' => 'exposed_string_test', + 'cardinality' => 1, + 'translatable' => FALSE, + ])->save(); + FieldConfig::create([ + 'entity_type' => 'entity_test', + 'field_name' => 'field_test_exposed', + 'bundle' => 'entity_test', + 'label' => 'Test exposed-field', + ])->save(); + } + + $entity = parent::createEntity(); + $entity->field_test_exposed = [ + 'value' => 'value to expose', + ]; + $entity->save(); + return $entity; + } + + /** + * {@inheritdoc} + */ + protected function getNormalizedPostEntity() { + $post_entity = parent::getNormalizedPostEntity(); + $post_entity['field_test_exposed'] = [ + [ + 'value' => 'value to expose', + ], + ]; + return $post_entity; + } + + /** + * {@inheritdoc} + */ + protected function getExpectedCacheContexts() { + return Cache::mergeContexts(parent::getExpectedCacheContexts(), ['request_format']); + } + + /** + * {@inheritdoc} + */ + protected function getExpectedCacheTags() { + return Cache::mergeTags(parent::getExpectedCacheTags(), ['you_are_it', 'no_tag_backs']); + } + +} diff --git a/core/modules/serialization/src/Normalizer/ComplexDataNormalizer.php b/core/modules/serialization/src/Normalizer/ComplexDataNormalizer.php index 3d2031218f..b3b761c706 100644 --- a/core/modules/serialization/src/Normalizer/ComplexDataNormalizer.php +++ b/core/modules/serialization/src/Normalizer/ComplexDataNormalizer.php @@ -2,6 +2,8 @@ namespace Drupal\serialization\Normalizer; +use Drupal\Core\TypedData\ComplexDataInterface; + /** * Converts the Drupal entity object structures to a normalized array. * @@ -14,6 +16,7 @@ */ class ComplexDataNormalizer extends NormalizerBase { + use ComplexDataPropertiesNormalizerTrait; /** * The interface or class that this Normalizer supports. * @@ -25,10 +28,19 @@ class ComplexDataNormalizer extends NormalizerBase { * {@inheritdoc} */ public function normalize($object, $format = NULL, array $context = []) { - $attributes = []; - /** @var \Drupal\Core\TypedData\TypedDataInterface $field */ - foreach ($object as $name => $field) { - $attributes[$name] = $this->serializer->normalize($field, $format, $context); + // $object will not always match $supportedInterfaceOrClass. + // @see \Drupal\serialization\Normalizer\EntityNormalizer + // Other normalizer that extend this class may only provide $object that + // implements \Traversable. + if ($object instanceof ComplexDataInterface) { + $attributes = $this->normalizeProperties($object, $format, $context); + } + else { + $attributes = []; + /** @var \Drupal\Core\TypedData\TypedDataInterface $field */ + foreach ($object as $name => $property) { + $attributes[$name] = $this->serializer->normalize($property, $format, $context); + } } return $attributes; } diff --git a/core/modules/serialization/src/Normalizer/ComplexDataPropertiesNormalizerTrait.php b/core/modules/serialization/src/Normalizer/ComplexDataPropertiesNormalizerTrait.php new file mode 100644 index 0000000000..8697d1ff29 --- /dev/null +++ b/core/modules/serialization/src/Normalizer/ComplexDataPropertiesNormalizerTrait.php @@ -0,0 +1,42 @@ + $property) { + $attribute = $this->serializer->normalize($property, $format, $context); + if ($attribute instanceof CacheableDependencyInterface && isset($context['cacheability'])) { + $context['cacheability']->addCacheableDependency($attribute); + } + if (is_object($attribute) && method_exists($attribute, '__toString')) { + $attribute = (string) $attribute; + } + $attributes[$name] = $attribute; + } + return $attributes; + } + +} diff --git a/core/modules/serialization/tests/src/Unit/Normalizer/ComplexDataNormalizerTest.php b/core/modules/serialization/tests/src/Unit/Normalizer/ComplexDataNormalizerTest.php index dc14d6f428..de936d7acf 100644 --- a/core/modules/serialization/tests/src/Unit/Normalizer/ComplexDataNormalizerTest.php +++ b/core/modules/serialization/tests/src/Unit/Normalizer/ComplexDataNormalizerTest.php @@ -7,8 +7,10 @@ namespace Drupal\Tests\serialization\Unit\Normalizer; +use Drupal\Core\Cache\CacheableDependencyInterface; +use Drupal\Core\Cache\RefinableCacheableDependencyInterface; use Drupal\Core\TypedData\ComplexDataInterface; -use Drupal\Core\TypedData\TraversableTypedDataInterface; +use Drupal\Core\TypedData\TypedDataInterface; use Drupal\serialization\Normalizer\ComplexDataNormalizer; use Drupal\Tests\UnitTestCase; use Symfony\Component\Serializer\Serializer; @@ -44,103 +46,97 @@ protected function setUp() { * @covers ::supportsNormalization */ public function testSupportsNormalization() { - $this->assertTrue($this->normalizer->supportsNormalization(new TestComplexData())); + $complexData = $this->prophesize(ComplexDataInterface::class)->reveal(); + $this->assertTrue($this->normalizer->supportsNormalization($complexData)); // Also test that an object not implementing ComplexDataInterface fails. $this->assertFalse($this->normalizer->supportsNormalization(new \stdClass())); } /** + * Test normalizing complex data. + * * @covers ::normalize */ - public function testNormalize() { - $context = ['test' => 'test']; - + public function testNormalizeComplexData() { $serializer_prophecy = $this->prophesize(Serializer::class); - $serializer_prophecy->normalize('A', static::TEST_FORMAT, $context) + $cacheable = $this->prophesize(TestCacheableDependencyInterface::class); + $cacheable->__toString() + ->willReturn('prop-as-string') ->shouldBeCalled(); - $serializer_prophecy->normalize('B', static::TEST_FORMAT, $context) - ->shouldBeCalled(); - - $this->normalizer->setSerializer($serializer_prophecy->reveal()); - - $complex_data = new TestComplexData(['a' => 'A', 'b' => 'B']); - $this->normalizer->normalize($complex_data, static::TEST_FORMAT, $context); - - } - -} - -/** - * Test class implementing ComplexDataInterface and IteratorAggregate. - */ -class TestComplexData implements \IteratorAggregate, ComplexDataInterface { - - private $values; - - public function __construct(array $values = []) { - $this->values = $values; - } - - public function getIterator() { - return new \ArrayIterator($this->values); - } - - public function applyDefaultValue($notify = TRUE) { - } - - public static function createInstance($definition, $name = NULL, TraversableTypedDataInterface $parent = NULL) { - } + $cacheable = $cacheable->reveal(); - public function get($property_name) { - } + $property = $this->prophesize(TypedDataInterface::class); + $property = $property->reveal(); - public function getConstraints() { - } - - public function getDataDefinition() { - } - - public function getName() { - } - - public function getParent() { - } + $cacheableMetaData = $this->prophesize(RefinableCacheableDependencyInterface::class); + $cacheableMetaData->addCacheableDependency($cacheable) + ->shouldBeCalled(); + $serialization_context = [ + 'cacheability' => $cacheableMetaData->reveal(), + ]; - public function getProperties($include_computed = FALSE) { - } + $serializer_prophecy->normalize('A', static::TEST_FORMAT, $serialization_context) + ->willReturn('A-normalized') + ->shouldBeCalled(); + $serializer_prophecy->normalize($property, static::TEST_FORMAT, $serialization_context) + ->willReturn($cacheable) + ->shouldBeCalled(); - public function getPropertyPath() { - } + $this->normalizer->setSerializer($serializer_prophecy->reveal()); - public function getRoot() { - } + $complex_data = $this->prophesize(ComplexDataInterface::class); + $complex_data->getExposedProperties() + ->willReturn(['prop:a' => 'A', 'prop:cacheable' => $property]) + ->shouldBeCalled(); - public function getString() { + $normalized = $this->normalizer->normalize($complex_data->reveal(), static::TEST_FORMAT, $serialization_context); + $this->assertEquals(['prop:a' => 'A-normalized', 'prop:cacheable' => 'prop-as-string'], $normalized); } - public function getValue() { - } + /** + * Test normalize() where $object does not implement ComplexDataInterface. + * + * Normalizers extending ComplexDataNormalizer may have a different supported + * class. + * + * @covers ::normalize + */ + public function testNormalizeNonComplex() { + $normalizer = new TestExtendedNormalizer(); + $serialization_context = ['test' => 'test']; - public function isEmpty() { - } + $serializer_prophecy = $this->prophesize(Serializer::class); + $serializer_prophecy->normalize('A', static::TEST_FORMAT, $serialization_context) + ->willReturn('A-normalized') + ->shouldBeCalled(); + $serializer_prophecy->normalize('B', static::TEST_FORMAT, $serialization_context) + ->willReturn('B-normalized') + ->shouldBeCalled(); - public function onChange($name) { - } + $normalizer->setSerializer($serializer_prophecy->reveal()); - public function set($property_name, $value, $notify = TRUE) { - } + $stdClass = new \stdClass(); + $stdClass->a = 'A'; + $stdClass->b = 'B'; - public function setContext($name = NULL, TraversableTypedDataInterface $parent = NULL) { - } + $normalized = $normalizer->normalize($stdClass, static::TEST_FORMAT, $serialization_context); + $this->assertEquals(['a' => 'A-normalized', 'b' => 'B-normalized'], $normalized); - public function setValue($value, $notify = TRUE) { } - public function toArray() { - } +} - public function validate() { - } +/** + * Test interface used for mocking. + */ +interface TestCacheableDependencyInterface extends CacheableDependencyInterface { + public function __toString(); +} +/** + * Test normalizer with a different supported class. + */ +class TestExtendedNormalizer extends ComplexDataNormalizer { + protected $supportedInterfaceOrClass = \stdClass::class; } diff --git a/core/modules/serialization/tests/src/Unit/Normalizer/EntityReferenceFieldItemNormalizerTest.php b/core/modules/serialization/tests/src/Unit/Normalizer/EntityReferenceFieldItemNormalizerTest.php index e0561a1003..a107250865 100644 --- a/core/modules/serialization/tests/src/Unit/Normalizer/EntityReferenceFieldItemNormalizerTest.php +++ b/core/modules/serialization/tests/src/Unit/Normalizer/EntityReferenceFieldItemNormalizerTest.php @@ -4,6 +4,8 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Field\FieldDefinitionInterface; +use Drupal\Core\TypedData\Plugin\DataType\StringData; +use Drupal\Core\TypedData\Type\StringInterface; use Drupal\Core\TypedData\TypedDataInterface; use Drupal\Core\Entity\EntityRepositoryInterface; use Drupal\Core\Entity\FieldableEntityInterface; @@ -122,6 +124,10 @@ public function testNormalize() { ->willReturn($entity_reference) ->shouldBeCalled(); + $this->fieldItem->getExposedProperties() + ->willReturn(['target_id' => NULL]) + ->shouldBeCalled(); + $normalized = $this->normalizer->normalize($this->fieldItem->reveal()); $expected = [ @@ -146,6 +152,10 @@ public function testNormalizeWithNoEntity() { ->willReturn($entity_reference->reveal()) ->shouldBeCalled(); + $this->fieldItem->getExposedProperties() + ->willReturn(['target_id' => NULL]) + ->shouldBeCalled(); + $normalized = $this->normalizer->normalize($this->fieldItem->reveal()); $expected = [ diff --git a/core/modules/serialization/tests/src/Unit/Normalizer/TimestampItemNormalizerTest.php b/core/modules/serialization/tests/src/Unit/Normalizer/TimestampItemNormalizerTest.php index fd1fc9ce9f..0db0ca71a5 100644 --- a/core/modules/serialization/tests/src/Unit/Normalizer/TimestampItemNormalizerTest.php +++ b/core/modules/serialization/tests/src/Unit/Normalizer/TimestampItemNormalizerTest.php @@ -77,6 +77,10 @@ public function testNormalize() { $timestamp_item->getIterator() ->willReturn(new \ArrayIterator(['value' => 1478422920])); + $timestamp_item->getExposedProperties() + ->willReturn(['value' => 1478422920]) + ->shouldBeCalled(); + $serializer = new Serializer(); $this->normalizer->setSerializer($serializer); diff --git a/core/modules/system/tests/modules/entity_test/config/schema/entity_test.data_types.schema.yml b/core/modules/system/tests/modules/entity_test/config/schema/entity_test.data_types.schema.yml new file mode 100644 index 0000000000..bda7a93205 --- /dev/null +++ b/core/modules/system/tests/modules/entity_test/config/schema/entity_test.data_types.schema.yml @@ -0,0 +1,15 @@ +# Schema for the configuration of the exposed string field type. + +field.storage_settings.exposed_string_test: + type: mapping + label: 'String settings' + mapping: + max_length: + type: integer + label: 'Maximum length' + case_sensitive: + type: boolean + label: 'Case sensitive' + is_ascii: + type: boolean + label: 'Contains US ASCII characters only' diff --git a/core/modules/system/tests/modules/entity_test/src/ComputedString.php b/core/modules/system/tests/modules/entity_test/src/ComputedString.php new file mode 100644 index 0000000000..0c78bdc02d --- /dev/null +++ b/core/modules/system/tests/modules/entity_test/src/ComputedString.php @@ -0,0 +1,41 @@ +notComputedValue = $not_computed_value; + $this->cacheContexts = ['request_format']; + $this->cacheTags = ['you_are_it', 'no_tag_backs']; + } + + /** + * {@inheritdoc} + */ + public function __toString() { + // Computation is simple concatenation for test. + return "Exposed! " . $this->notComputedValue; + } + +} diff --git a/core/modules/system/tests/modules/entity_test/src/Plugin/DataType/ExposedStringData.php b/core/modules/system/tests/modules/entity_test/src/Plugin/DataType/ExposedStringData.php new file mode 100644 index 0000000000..650121754f --- /dev/null +++ b/core/modules/system/tests/modules/entity_test/src/Plugin/DataType/ExposedStringData.php @@ -0,0 +1,37 @@ +getParent(); + $computed = new ComputedString($item->get('value')->getString()); + return $computed; + } + + /** + * {@inheritdoc} + */ + public function getCastedValue() { + return $this->getValue(); + } + +} diff --git a/core/modules/system/tests/modules/entity_test/src/Plugin/Field/FieldType/ExposedPropertyTestFieldItem.php b/core/modules/system/tests/modules/entity_test/src/Plugin/Field/FieldType/ExposedPropertyTestFieldItem.php new file mode 100644 index 0000000000..fb930826b2 --- /dev/null +++ b/core/modules/system/tests/modules/entity_test/src/Plugin/Field/FieldType/ExposedPropertyTestFieldItem.php @@ -0,0 +1,39 @@ +setLabel(new TranslatableMarkup('Text value exposed')) + ->setComputed(TRUE) + ->setClass(ExposedStringData::class) + ->setExposed(TRUE); + return $properties; + } + +}