From 52b186a210015f931513dccecc74edc7f385a987 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Wed, 3 Oct 2018 17:09:15 +0200 Subject: [PATCH] [Serializer] Refactor and uniformize the config by introducing a default context --- src/Symfony/Component/Serializer/CHANGELOG.md | 10 ++ .../Serializer/Encoder/CsvEncoder.php | 50 +++--- .../Serializer/Encoder/JsonDecode.php | 64 ++++---- .../Serializer/Encoder/JsonEncode.php | 36 +++-- .../Serializer/Encoder/JsonEncoder.php | 2 +- .../Serializer/Encoder/XmlEncoder.php | 134 +++++++++------ .../Normalizer/AbstractNormalizer.php | 117 +++++++++----- .../Normalizer/AbstractObjectNormalizer.php | 45 ++++-- .../ConstraintViolationListNormalizer.php | 24 ++- .../Normalizer/DateIntervalNormalizer.php | 23 ++- .../Normalizer/DateTimeNormalizer.php | 32 ++-- .../Normalizer/ObjectNormalizer.php | 4 +- .../Tests/Encoder/CsvEncoderTest.php | 58 ++++++- .../Tests/Encoder/XmlEncoderTest.php | 78 ++++++++- .../Normalizer/DateIntervalNormalizerTest.php | 32 +++- .../Normalizer/DateTimeNormalizerTest.php | 41 ++++- .../Normalizer/GetSetMethodNormalizerTest.php | 92 +++++++++-- .../JsonSerializableNormalizerTest.php | 25 ++- .../Tests/Normalizer/ObjectNormalizerTest.php | 152 +++++++++++++++--- .../Normalizer/PropertyNormalizerTest.php | 93 +++++++++-- 20 files changed, 854 insertions(+), 258 deletions(-) diff --git a/src/Symfony/Component/Serializer/CHANGELOG.md b/src/Symfony/Component/Serializer/CHANGELOG.md index fc441ba95acd7..a82a7c6b2d9ef 100644 --- a/src/Symfony/Component/Serializer/CHANGELOG.md +++ b/src/Symfony/Component/Serializer/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 4.2.0 ----- + * using the default context is the new recommended way to configure normalizers and encoders * added a `skip_null_values` context option to not serialize properties with a `null` values * `AbstractNormalizer::handleCircularReference` is now final and receives two optional extra arguments: the format and the context @@ -24,6 +25,15 @@ CHANGELOG and `ObjectNormalizer` constructor * added `MetadataAwareNameConverter` to configure the serialized name of properties through metadata * `YamlEncoder` now handles the `.yml` extension too + * `AbstractNormalizer::$circularReferenceLimit`, `AbstractNormalizer::$circularReferenceHandler`, + `AbstractNormalizer::$callbacks`, `AbstractNormalizer::$ignoredAttributes`, + `AbstractNormalizer::$camelizedAttributes`, `AbstractNormalizer::setCircularReferenceLimit()`, + `AbstractNormalizer::setCircularReferenceHandler()`, `AbstractNormalizer::setCallbacks()` and + `AbstractNormalizer::setIgnoredAttributes()` are deprecated, use the default context instead. + * `AbstractObjectNormalizer::$maxDepthHandler` and `AbstractObjectNormalizer::setMaxDepthHandler()` + are deprecated, use the default context instead. + * passing configuration options directly to the constructor of `CsvEncoder`, `JsonDecode` and + `XmlEncoder` is deprecated since Symfony 4.2, use the default context instead. 4.1.0 ----- diff --git a/src/Symfony/Component/Serializer/Encoder/CsvEncoder.php b/src/Symfony/Component/Serializer/Encoder/CsvEncoder.php index ba1a62526c424..c453120fc6a0d 100644 --- a/src/Symfony/Component/Serializer/Encoder/CsvEncoder.php +++ b/src/Symfony/Component/Serializer/Encoder/CsvEncoder.php @@ -30,20 +30,34 @@ class CsvEncoder implements EncoderInterface, DecoderInterface const ESCAPE_FORMULAS_KEY = 'csv_escape_formulas'; const AS_COLLECTION_KEY = 'as_collection'; - private $delimiter; - private $enclosure; - private $escapeChar; - private $keySeparator; - private $escapeFormulas; private $formulasStartCharacters = array('=', '-', '+', '@'); + private $defaultContext = array( + self::DELIMITER_KEY => ',', + self::ENCLOSURE_KEY => '"', + self::ESCAPE_CHAR_KEY => '\\', + self::ESCAPE_FORMULAS_KEY => false, + self::HEADERS_KEY => array(), + self::KEY_SEPARATOR_KEY => '.', + ); - public function __construct(string $delimiter = ',', string $enclosure = '"', string $escapeChar = '\\', string $keySeparator = '.', bool $escapeFormulas = false) + /** + * @param array $defaultContext + */ + public function __construct($defaultContext = array(), string $enclosure = '"', string $escapeChar = '\\', string $keySeparator = '.', bool $escapeFormulas = false) { - $this->delimiter = $delimiter; - $this->enclosure = $enclosure; - $this->escapeChar = $escapeChar; - $this->keySeparator = $keySeparator; - $this->escapeFormulas = $escapeFormulas; + if (!\is_array($defaultContext)) { + @trigger_error('Passing configuration options directly to the constructor is deprecated since Symfony 4.2, use the default context instead.', E_USER_DEPRECATED); + + $defaultContext = array( + self::DELIMITER_KEY => (string) $defaultContext, + self::ENCLOSURE_KEY => $enclosure, + self::ESCAPE_CHAR_KEY => $escapeChar, + self::KEY_SEPARATOR_KEY => $keySeparator, + self::ESCAPE_FORMULAS_KEY => $escapeFormulas, + ); + } + + $this->defaultContext = array_merge($this->defaultContext, $defaultContext); } /** @@ -200,14 +214,14 @@ private function flatten(array $array, array &$result, string $keySeparator, str } } - private function getCsvOptions(array $context) + private function getCsvOptions(array $context): array { - $delimiter = isset($context[self::DELIMITER_KEY]) ? $context[self::DELIMITER_KEY] : $this->delimiter; - $enclosure = isset($context[self::ENCLOSURE_KEY]) ? $context[self::ENCLOSURE_KEY] : $this->enclosure; - $escapeChar = isset($context[self::ESCAPE_CHAR_KEY]) ? $context[self::ESCAPE_CHAR_KEY] : $this->escapeChar; - $keySeparator = isset($context[self::KEY_SEPARATOR_KEY]) ? $context[self::KEY_SEPARATOR_KEY] : $this->keySeparator; - $headers = isset($context[self::HEADERS_KEY]) ? $context[self::HEADERS_KEY] : array(); - $escapeFormulas = isset($context[self::ESCAPE_FORMULAS_KEY]) ? $context[self::ESCAPE_FORMULAS_KEY] : $this->escapeFormulas; + $delimiter = $context[self::DELIMITER_KEY] ?? $this->defaultContext[self::DELIMITER_KEY]; + $enclosure = $context[self::ENCLOSURE_KEY] ?? $this->defaultContext[self::ENCLOSURE_KEY]; + $escapeChar = $context[self::ESCAPE_CHAR_KEY] ?? $this->defaultContext[self::ESCAPE_CHAR_KEY]; + $keySeparator = $context[self::KEY_SEPARATOR_KEY] ?? $this->defaultContext[self::KEY_SEPARATOR_KEY]; + $headers = $context[self::HEADERS_KEY] ?? $this->defaultContext[self::HEADERS_KEY]; + $escapeFormulas = $context[self::ESCAPE_FORMULAS_KEY] ?? $this->defaultContext[self::ESCAPE_FORMULAS_KEY]; if (!\is_array($headers)) { throw new InvalidArgumentException(sprintf('The "%s" context variable must be an array or null, given "%s".', self::HEADERS_KEY, \gettype($headers))); diff --git a/src/Symfony/Component/Serializer/Encoder/JsonDecode.php b/src/Symfony/Component/Serializer/Encoder/JsonDecode.php index 5b0a432f39202..2722974d586a7 100644 --- a/src/Symfony/Component/Serializer/Encoder/JsonDecode.php +++ b/src/Symfony/Component/Serializer/Encoder/JsonDecode.php @@ -22,19 +22,41 @@ class JsonDecode implements DecoderInterface { protected $serializer; - private $associative; - private $recursionDepth; + /** + * True to return the result as an associative array, false for a nested stdClass hierarchy. + */ + const ASSOCIATIVE = 'json_decode_associative'; + + const OPTIONS = 'json_decode_options'; + + /** + * Specifies the recursion depth. + */ + const RECURSION_DEPTH = 'json_decode_recursion_depth'; + + private $defaultContext = array( + self::ASSOCIATIVE => false, + self::OPTIONS => 0, + self::RECURSION_DEPTH => 512, + ); /** * Constructs a new JsonDecode instance. * - * @param bool $associative True to return the result associative array, false for a nested stdClass hierarchy - * @param int $depth Specifies the recursion depth + * @param array $defaultContext */ - public function __construct(bool $associative = false, int $depth = 512) + public function __construct($defaultContext = array(), int $depth = 512) { - $this->associative = $associative; - $this->recursionDepth = $depth; + if (!\is_array($defaultContext)) { + @trigger_error(sprintf('Using constructor parameters that are not a default context is deprecated since Symfony 4.2, use the "%s" and "%s" keys of the context instead.', self::ASSOCIATIVE, self::RECURSION_DEPTH), E_USER_DEPRECATED); + + $defaultContext = array( + self::ASSOCIATIVE => (bool) $defaultContext, + self::RECURSION_DEPTH => $depth, + ); + } + + $this->defaultContext = array_merge($this->defaultContext, $defaultContext); } /** @@ -47,7 +69,7 @@ public function __construct(bool $associative = false, int $depth = 512) * The $context array is a simple key=>value array, with the following supported keys: * * json_decode_associative: boolean - * If true, returns the object as associative array. + * If true, returns the object as an associative array. * If false, returns the object as nested stdClass * If not specified, this method will use the default set in JsonDecode::__construct * @@ -56,7 +78,7 @@ public function __construct(bool $associative = false, int $depth = 512) * If not specified, this method will use the default set in JsonDecode::__construct * * json_decode_options: integer - * Specifies additional options as per documentation for json_decode. + * Specifies additional options as per documentation for json_decode * * @return mixed * @@ -66,11 +88,9 @@ public function __construct(bool $associative = false, int $depth = 512) */ public function decode($data, $format, array $context = array()) { - $context = $this->resolveContext($context); - - $associative = $context['json_decode_associative']; - $recursionDepth = $context['json_decode_recursion_depth']; - $options = $context['json_decode_options']; + $associative = $context[self::ASSOCIATIVE] ?? $this->defaultContext[self::ASSOCIATIVE]; + $recursionDepth = $context[self::RECURSION_DEPTH] ?? $this->defaultContext[self::RECURSION_DEPTH]; + $options = $context[self::OPTIONS] ?? $this->defaultContext[self::OPTIONS]; $decodedData = json_decode($data, $associative, $recursionDepth, $options); @@ -88,20 +108,4 @@ public function supportsDecoding($format) { return JsonEncoder::FORMAT === $format; } - - /** - * Merges the default options of the Json Decoder with the passed context. - * - * @return array - */ - private function resolveContext(array $context) - { - $defaultOptions = array( - 'json_decode_associative' => $this->associative, - 'json_decode_recursion_depth' => $this->recursionDepth, - 'json_decode_options' => 0, - ); - - return array_merge($defaultOptions, $context); - } } diff --git a/src/Symfony/Component/Serializer/Encoder/JsonEncode.php b/src/Symfony/Component/Serializer/Encoder/JsonEncode.php index 5cc30b75026f4..63669d4ca696c 100644 --- a/src/Symfony/Component/Serializer/Encoder/JsonEncode.php +++ b/src/Symfony/Component/Serializer/Encoder/JsonEncode.php @@ -20,11 +20,24 @@ */ class JsonEncode implements EncoderInterface { - private $options; + const OPTIONS = 'json_encode_options'; - public function __construct(int $bitmask = 0) + private $defaultContext = array( + self::OPTIONS => 0, + ); + + /** + * @param array $defaultContext + */ + public function __construct($defaultContext = array()) { - $this->options = $bitmask; + if (!\is_array($defaultContext)) { + @trigger_error(sprintf('Passing an integer as first parameter of the "%s()" method is deprecated since Symfony 4.2, use the "json_encode_options" key of the context instead.', __METHOD__), E_USER_DEPRECATED); + + $this->defaultContext[self::OPTIONS] = (int) $defaultContext; + } else { + $this->defaultContext = array_merge($this->defaultContext, $defaultContext); + } } /** @@ -34,11 +47,10 @@ public function __construct(int $bitmask = 0) */ public function encode($data, $format, array $context = array()) { - $context = $this->resolveContext($context); - - $encodedJson = json_encode($data, $context['json_encode_options']); + $jsonEncodeOptions = $context[self::OPTIONS] ?? $this->defaultContext[self::OPTIONS]; + $encodedJson = json_encode($data, $jsonEncodeOptions); - if (JSON_ERROR_NONE !== json_last_error() && (false === $encodedJson || !($context['json_encode_options'] & JSON_PARTIAL_OUTPUT_ON_ERROR))) { + if (JSON_ERROR_NONE !== json_last_error() && (false === $encodedJson || !($jsonEncodeOptions & JSON_PARTIAL_OUTPUT_ON_ERROR))) { throw new NotEncodableValueException(json_last_error_msg()); } @@ -52,14 +64,4 @@ public function supportsEncoding($format) { return JsonEncoder::FORMAT === $format; } - - /** - * Merge default json encode options with context. - * - * @return array - */ - private function resolveContext(array $context = array()) - { - return array_merge(array('json_encode_options' => $this->options), $context); - } } diff --git a/src/Symfony/Component/Serializer/Encoder/JsonEncoder.php b/src/Symfony/Component/Serializer/Encoder/JsonEncoder.php index f4950cb3b3f47..4690c9863c71b 100644 --- a/src/Symfony/Component/Serializer/Encoder/JsonEncoder.php +++ b/src/Symfony/Component/Serializer/Encoder/JsonEncoder.php @@ -26,7 +26,7 @@ class JsonEncoder implements EncoderInterface, DecoderInterface public function __construct(JsonEncode $encodingImpl = null, JsonDecode $decodingImpl = null) { $this->encodingImpl = $encodingImpl ?: new JsonEncode(); - $this->decodingImpl = $decodingImpl ?: new JsonDecode(true); + $this->decodingImpl = $decodingImpl ?: new JsonDecode(array(JsonDecode::ASSOCIATIVE => true)); } /** diff --git a/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php b/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php index c04b7d845b52c..d92266cfa83d5 100644 --- a/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php +++ b/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php @@ -30,30 +30,64 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa const FORMAT = 'xml'; + const AS_COLLECTION = 'as_collection'; + + /** + * An array of ignored XML node types while decoding, each one of the DOM Predefined XML_* constants. + */ + const DECODER_IGNORED_NODE_TYPES = 'decoder_ignored_node_types'; + + /** + * An array of ignored XML node types while encoding, each one of the DOM Predefined XML_* constants. + */ + const ENCODER_IGNORED_NODE_TYPES = 'encoder_ignored_node_types'; + const ENCODING = 'xml_encoding'; + const FORMAT_OUTPUT = 'xml_format_output'; + + /** + * A bit field of LIBXML_* constants. + */ + const LOAD_OPTIONS = 'load_options'; + const REMOVE_EMPTY_TAGS = 'remove_empty_tags'; + const ROOT_NODE_NAME = 'xml_root_node_name'; + const STANDALONE = 'xml_standalone'; + const TYPE_CASE_ATTRIBUTES = 'xml_type_cast_attributes'; + const VERSION = 'xml_version'; + + private $defaultContext = array( + self::AS_COLLECTION => false, + self::DECODER_IGNORED_NODE_TYPES => array(XML_PI_NODE, XML_COMMENT_NODE), + self::ENCODER_IGNORED_NODE_TYPES => array(), + self::LOAD_OPTIONS => LIBXML_NONET | LIBXML_NOBLANKS, + self::REMOVE_EMPTY_TAGS => false, + self::ROOT_NODE_NAME => 'response', + self::TYPE_CASE_ATTRIBUTES => true, + ); + /** * @var \DOMDocument */ private $dom; private $format; private $context; - private $rootNodeName = 'response'; - private $loadOptions; - private $decoderIgnoredNodeTypes; - private $encoderIgnoredNodeTypes; /** - * Construct new XmlEncoder and allow to change the root node element name. - * - * @param int|null $loadOptions A bit field of LIBXML_* constants - * @param int[] $decoderIgnoredNodeTypes an array of ignored XML node types while decoding, each one of the DOM Predefined XML_* Constants - * @param int[] $encoderIgnoredNodeTypes an array of ignored XML node types while encoding, each one of the DOM Predefined XML_* Constants + * @param array $defaultContext */ - public function __construct(string $rootNodeName = 'response', int $loadOptions = null, array $decoderIgnoredNodeTypes = array(XML_PI_NODE, XML_COMMENT_NODE), array $encoderIgnoredNodeTypes = array()) + public function __construct($defaultContext = array(), int $loadOptions = null, array $decoderIgnoredNodeTypes = array(XML_PI_NODE, XML_COMMENT_NODE), array $encoderIgnoredNodeTypes = array()) { - $this->rootNodeName = $rootNodeName; - $this->loadOptions = null !== $loadOptions ? $loadOptions : LIBXML_NONET | LIBXML_NOBLANKS; - $this->decoderIgnoredNodeTypes = $decoderIgnoredNodeTypes; - $this->encoderIgnoredNodeTypes = $encoderIgnoredNodeTypes; + if (!\is_array($defaultContext)) { + @trigger_error('Passing configuration options directly to the constructor is deprecated since Symfony 4.2, use the default context instead.', E_USER_DEPRECATED); + + $defaultContext = array( + self::DECODER_IGNORED_NODE_TYPES => $decoderIgnoredNodeTypes, + self::ENCODER_IGNORED_NODE_TYPES => $encoderIgnoredNodeTypes, + self::LOAD_OPTIONS => $loadOptions ?? LIBXML_NONET | LIBXML_NOBLANKS, + self::ROOT_NODE_NAME => (string) $defaultContext, + ); + } + + $this->defaultContext = array_merge($this->defaultContext, $defaultContext); } /** @@ -61,11 +95,13 @@ public function __construct(string $rootNodeName = 'response', int $loadOptions */ public function encode($data, $format, array $context = array()) { + $encoderIgnoredNodeTypes = $context[self::ENCODER_IGNORED_NODE_TYPES] ?? $this->defaultContext[self::ENCODER_IGNORED_NODE_TYPES]; + $ignorePiNode = \in_array(XML_PI_NODE, $encoderIgnoredNodeTypes, true); if ($data instanceof \DOMDocument) { - return $data->saveXML(\in_array(XML_PI_NODE, $this->encoderIgnoredNodeTypes, true) ? $data->documentElement : null); + return $data->saveXML($ignorePiNode ? $data->documentElement : null); } - $xmlRootNodeName = $this->resolveXmlRootName($context); + $xmlRootNodeName = $context[self::ROOT_NODE_NAME] ?? $this->defaultContext[self::ROOT_NODE_NAME]; $this->dom = $this->createDomDocument($context); $this->format = $format; @@ -79,7 +115,7 @@ public function encode($data, $format, array $context = array()) $this->appendNode($this->dom, $data, $xmlRootNodeName); } - return $this->dom->saveXML(\in_array(XML_PI_NODE, $this->encoderIgnoredNodeTypes, true) ? $this->dom->documentElement : null); + return $this->dom->saveXML($ignorePiNode ? $this->dom->documentElement : null); } /** @@ -96,7 +132,7 @@ public function decode($data, $format, array $context = array()) libxml_clear_errors(); $dom = new \DOMDocument(); - $dom->loadXML($data, $this->loadOptions); + $dom->loadXML($data, $context[self::LOAD_OPTIONS] ?? $this->defaultContext[self::LOAD_OPTIONS]); libxml_use_internal_errors($internalErrors); libxml_disable_entity_loader($disableEntities); @@ -108,11 +144,12 @@ public function decode($data, $format, array $context = array()) } $rootNode = null; + $decoderIgnoredNodeTypes = $context[self::DECODER_IGNORED_NODE_TYPES] ?? $this->defaultContext[self::DECODER_IGNORED_NODE_TYPES]; foreach ($dom->childNodes as $child) { if (XML_DOCUMENT_TYPE_NODE === $child->nodeType) { throw new NotEncodableValueException('Document types are not allowed.'); } - if (!$rootNode && !\in_array($child->nodeType, $this->decoderIgnoredNodeTypes, true)) { + if (!$rootNode && !\in_array($child->nodeType, $decoderIgnoredNodeTypes, true)) { $rootNode = $child; } } @@ -169,21 +206,29 @@ public function supportsDecoding($format) /** * Sets the root node name. * + * @deprecated since Symfony 4.2 + * * @param string $name Root node name */ public function setRootNodeName($name) { - $this->rootNodeName = $name; + @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the context instead.', __METHOD__), E_USER_DEPRECATED); + + $this->defaultContext[self::ROOT_NODE_NAME] = $name; } /** * Returns the root node name. * + * @deprecated since Symfony 4.2 + * * @return string */ public function getRootNodeName() { - return $this->rootNodeName; + @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the context instead.', __METHOD__), E_USER_DEPRECATED); + + return $this->defaultContext[self::ROOT_NODE_NAME]; } final protected function appendXMLString(\DOMNode $node, string $val): bool @@ -291,7 +336,7 @@ private function parseXmlAttributes(\DOMNode $node, array $context = array()): a } $data = array(); - $typeCastAttributes = $this->resolveXmlTypeCastAttributes($context); + $typeCastAttributes = (bool) ($context[self::TYPE_CASE_ATTRIBUTES] ?? $this->defaultContext[self::TYPE_CASE_ATTRIBUTES]); foreach ($node->attributes as $attr) { if (!is_numeric($attr->nodeValue) || !$typeCastAttributes) { @@ -328,9 +373,9 @@ private function parseXmlValue(\DOMNode $node, array $context = array()) } $value = array(); - + $decoderIgnoredNodeTypes = $context[self::DECODER_IGNORED_NODE_TYPES] ?? $this->defaultContext[self::DECODER_IGNORED_NODE_TYPES]; foreach ($node->childNodes as $subnode) { - if (\in_array($subnode->nodeType, $this->decoderIgnoredNodeTypes, true)) { + if (\in_array($subnode->nodeType, $decoderIgnoredNodeTypes, true)) { continue; } @@ -347,8 +392,9 @@ private function parseXmlValue(\DOMNode $node, array $context = array()) } } + $asCollection = $context[self::AS_COLLECTION] ?? $this->defaultContext[self::AS_COLLECTION]; foreach ($value as $key => $val) { - if (empty($context['as_collection']) && \is_array($val) && 1 === \count($val)) { + if (!$asCollection && \is_array($val) && 1 === \count($val)) { $value[$key] = current($val); } } @@ -366,6 +412,8 @@ private function parseXmlValue(\DOMNode $node, array $context = array()) private function buildXml(\DOMNode $parentNode, $data, string $xmlRootNodeName = null): bool { $append = true; + $removeEmptyTags = $this->context[self::REMOVE_EMPTY_TAGS] ?? $this->defaultContext[self::REMOVE_EMPTY_TAGS] ?? false; + $encoderIgnoredNodeTypes = $this->context[self::ENCODER_IGNORED_NODE_TYPES] ?? $this->defaultContext[self::ENCODER_IGNORED_NODE_TYPES]; if (\is_array($data) || ($data instanceof \Traversable && !$this->serializer->supportsNormalization($data, $this->format))) { foreach ($data as $key => $data) { @@ -378,7 +426,7 @@ private function buildXml(\DOMNode $parentNode, $data, string $xmlRootNodeName = } elseif ('#' === $key) { $append = $this->selectNodeType($parentNode, $data); } elseif ('#comment' === $key) { - if (!\in_array(XML_COMMENT_NODE, $this->encoderIgnoredNodeTypes, true)) { + if (!\in_array(XML_COMMENT_NODE, $encoderIgnoredNodeTypes, true)) { $append = $this->appendComment($parentNode, $data); } } elseif (\is_array($data) && false === is_numeric($key)) { @@ -397,7 +445,7 @@ private function buildXml(\DOMNode $parentNode, $data, string $xmlRootNodeName = } } elseif (is_numeric($key) || !$this->isElementNameValid($key)) { $append = $this->appendNode($parentNode, $data, 'item', $key); - } elseif (null !== $data || !isset($this->context['remove_empty_tags']) || false === $this->context['remove_empty_tags']) { + } elseif (null !== $data || !$removeEmptyTags) { $append = $this->appendNode($parentNode, $data, $key); } } @@ -487,26 +535,6 @@ private function selectNodeType(\DOMNode $node, $val): bool return true; } - /** - * Get real XML root node name, taking serializer options into account. - */ - private function resolveXmlRootName(array $context = array()): string - { - return isset($context['xml_root_node_name']) - ? $context['xml_root_node_name'] - : $this->rootNodeName; - } - - /** - * Get XML option for type casting attributes Defaults to true. - */ - private function resolveXmlTypeCastAttributes(array $context = array()): bool - { - return isset($context['xml_type_cast_attributes']) - ? (bool) $context['xml_type_cast_attributes'] - : true; - } - /** * Create a DOM document, taking serializer options into account. */ @@ -517,17 +545,17 @@ private function createDomDocument(array $context): \DOMDocument // Set an attribute on the DOM document specifying, as part of the XML declaration, $xmlOptions = array( // nicely formats output with indentation and extra space - 'xml_format_output' => 'formatOutput', + self::FORMAT_OUTPUT => 'formatOutput', // the version number of the document - 'xml_version' => 'xmlVersion', + self::VERSION => 'xmlVersion', // the encoding of the document - 'xml_encoding' => 'encoding', + self::ENCODING => 'encoding', // whether the document is standalone - 'xml_standalone' => 'xmlStandalone', + self::STANDALONE => 'xmlStandalone', ); foreach ($xmlOptions as $xmlOption => $documentProperty) { - if (isset($context[$xmlOption])) { - $document->$documentProperty = $context[$xmlOption]; + if ($contextOption = $context[$xmlOption] ?? $this->defaultContext[$xmlOption] ?? false) { + $document->$documentProperty = $contextOption; } } diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php index 78102cb6a9616..3eb9e57002f20 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php @@ -38,14 +38,30 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn const ATTRIBUTES = 'attributes'; const ALLOW_EXTRA_ATTRIBUTES = 'allow_extra_attributes'; const DEFAULT_CONSTRUCTOR_ARGUMENTS = 'default_constructor_arguments'; + const CALLBACKS = 'callbacks'; + const CIRCULAR_REFERENCE_HANDLER = 'circular_reference_handler'; + const IGNORED_ATTRIBUTES = 'ignored_attributes'; /** - * @var int + * @internal + */ + const CIRCULAR_REFERENCE_LIMIT_COUNTERS = 'circular_reference_limit_counters'; + + protected $defaultContext = array( + self::ALLOW_EXTRA_ATTRIBUTES => true, + self::CIRCULAR_REFERENCE_LIMIT => 1, + self::IGNORED_ATTRIBUTES => array(), + ); + + /** + * @deprecated since Symfony 4.2 */ protected $circularReferenceLimit = 1; /** - * @var callable + * @deprecated since Symfony 4.2 + * + * @var callable|null */ protected $circularReferenceHandler; @@ -60,31 +76,42 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn protected $nameConverter; /** - * @var array + * @deprecated since Symfony 4.2 */ protected $callbacks = array(); /** - * @var array + * @deprecated since Symfony 4.2 */ protected $ignoredAttributes = array(); /** - * @var array + * @deprecated since Symfony 4.2 */ protected $camelizedAttributes = array(); /** * Sets the {@link ClassMetadataFactoryInterface} to use. */ - public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null) + public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, array $defaultContext = array()) { $this->classMetadataFactory = $classMetadataFactory; $this->nameConverter = $nameConverter; + $this->defaultContext = array_merge($this->defaultContext, $defaultContext); + + if (\is_array($this->defaultContext[self::CALLBACKS] ?? null)) { + foreach ($this->defaultContext[self::CALLBACKS] as $attribute => $callback) { + if (!\is_callable($callback)) { + throw new InvalidArgumentException(sprintf('The given callback for attribute "%s" is not callable.', $attribute)); + } + } + } } /** - * Set circular reference limit. + * Sets circular reference limit. + * + * @deprecated since Symfony 4.2 * * @param int $circularReferenceLimit Limit of iterations for the same object * @@ -92,13 +119,17 @@ public function __construct(ClassMetadataFactoryInterface $classMetadataFactory */ public function setCircularReferenceLimit($circularReferenceLimit) { - $this->circularReferenceLimit = $circularReferenceLimit; + @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the "circular_reference_limit" key of the context instead.', __METHOD__), E_USER_DEPRECATED); + + $this->defaultContext[self::CIRCULAR_REFERENCE_LIMIT] = $this->circularReferenceLimit = $circularReferenceLimit; return $this; } /** - * Set circular reference handler. + * Sets circular reference handler. + * + * @deprecated since Symfony 4.2 * * @param callable $circularReferenceHandler * @@ -106,13 +137,17 @@ public function setCircularReferenceLimit($circularReferenceLimit) */ public function setCircularReferenceHandler(callable $circularReferenceHandler) { - $this->circularReferenceHandler = $circularReferenceHandler; + @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the "circular_reference_handler" key of the context instead.', __METHOD__), E_USER_DEPRECATED); + + $this->defaultContext[self::CIRCULAR_REFERENCE_HANDLER] = $this->circularReferenceHandler = $circularReferenceHandler; return $this; } /** - * Set normalization callbacks. + * Sets normalization callbacks. + * + * @deprecated since Symfony 4.2 * * @param callable[] $callbacks Help normalize the result * @@ -122,24 +157,30 @@ public function setCircularReferenceHandler(callable $circularReferenceHandler) */ public function setCallbacks(array $callbacks) { + @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the "callbacks" key of the context instead.', __METHOD__), E_USER_DEPRECATED); + foreach ($callbacks as $attribute => $callback) { if (!\is_callable($callback)) { throw new InvalidArgumentException(sprintf('The given callback for attribute "%s" is not callable.', $attribute)); } } - $this->callbacks = $callbacks; + $this->defaultContext[self::CALLBACKS] = $this->callbacks = $callbacks; return $this; } /** - * Set ignored attributes for normalization and denormalization. + * Sets ignored attributes for normalization and denormalization. + * + * @deprecated since Symfony 4.2 * * @return self */ public function setIgnoredAttributes(array $ignoredAttributes) { - $this->ignoredAttributes = $ignoredAttributes; + @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the "ignored_attributes" key of the context instead.', __METHOD__), E_USER_DEPRECATED); + + $this->defaultContext[self::IGNORED_ATTRIBUTES] = $this->ignoredAttributes = $ignoredAttributes; return $this; } @@ -166,16 +207,17 @@ protected function isCircularReference($object, &$context) { $objectHash = spl_object_hash($object); - if (isset($context[static::CIRCULAR_REFERENCE_LIMIT][$objectHash])) { - if ($context[static::CIRCULAR_REFERENCE_LIMIT][$objectHash] >= $this->circularReferenceLimit) { - unset($context[static::CIRCULAR_REFERENCE_LIMIT][$objectHash]); + $circularReferenceLimit = $context[self::CIRCULAR_REFERENCE_LIMIT] ?? $this->defaultContext[self::CIRCULAR_REFERENCE_LIMIT] ?? $this->circularReferenceLimit; + if (isset($context[self::CIRCULAR_REFERENCE_LIMIT_COUNTERS][$objectHash])) { + if ($context[self::CIRCULAR_REFERENCE_LIMIT_COUNTERS][$objectHash] >= $circularReferenceLimit) { + unset($context[self::CIRCULAR_REFERENCE_LIMIT_COUNTERS][$objectHash]); return true; } - ++$context[static::CIRCULAR_REFERENCE_LIMIT][$objectHash]; + ++$context[self::CIRCULAR_REFERENCE_LIMIT_COUNTERS][$objectHash]; } else { - $context[static::CIRCULAR_REFERENCE_LIMIT][$objectHash] = 1; + $context[self::CIRCULAR_REFERENCE_LIMIT_COUNTERS][$objectHash] = 1; } return false; @@ -205,8 +247,9 @@ protected function handleCircularReference($object/*, string $format = null, arr $format = \func_num_args() > 1 ? func_get_arg(1) : null; $context = \func_num_args() > 2 ? func_get_arg(2) : array(); - if ($this->circularReferenceHandler) { - return \call_user_func($this->circularReferenceHandler, $object, $format, $context); + $circularReferenceHandler = $context[self::CIRCULAR_REFERENCE_HANDLER] ?? $this->defaultContext[self::CIRCULAR_REFERENCE_HANDLER] ?? $this->circularReferenceHandler; + if ($circularReferenceHandler) { + return $circularReferenceHandler($object, $format, $context); } throw new CircularReferenceException(sprintf('A circular reference has been detected when serializing the object of class "%s" (configured limit: %d)', \get_class($object), $this->circularReferenceLimit)); @@ -225,18 +268,18 @@ protected function handleCircularReference($object/*, string $format = null, arr */ protected function getAllowedAttributes($classOrObject, array $context, $attributesAsString = false) { + $allowExtraAttributes = $context[self::ALLOW_EXTRA_ATTRIBUTES] ?? $this->defaultContext[self::ALLOW_EXTRA_ATTRIBUTES]; if (!$this->classMetadataFactory) { - if (isset($context[static::ALLOW_EXTRA_ATTRIBUTES]) && !$context[static::ALLOW_EXTRA_ATTRIBUTES]) { - throw new LogicException(sprintf('A class metadata factory must be provided in the constructor when setting "%s" to false.', static::ALLOW_EXTRA_ATTRIBUTES)); + if (!$allowExtraAttributes) { + throw new LogicException(sprintf('A class metadata factory must be provided in the constructor when setting "%s" to false.', self::ALLOW_EXTRA_ATTRIBUTES)); } return false; } - $groups = false; - if (isset($context[static::GROUPS]) && (\is_array($context[static::GROUPS]) || is_scalar($context[static::GROUPS]))) { - $groups = (array) $context[static::GROUPS]; - } elseif (!isset($context[static::ALLOW_EXTRA_ATTRIBUTES]) || $context[static::ALLOW_EXTRA_ATTRIBUTES]) { + $tmpGroups = $context[self::GROUPS] ?? $this->defaultContext[self::GROUPS] ?? null; + $groups = (\is_array($tmpGroups) || is_scalar($tmpGroups)) ? (array) $tmpGroups : false; + if (false === $groups && $allowExtraAttributes) { return false; } @@ -267,17 +310,19 @@ protected function getAllowedAttributes($classOrObject, array $context, $attribu */ protected function isAllowedAttribute($classOrObject, $attribute, $format = null, array $context = array()) { - if (\in_array($attribute, $this->ignoredAttributes)) { + $ignoredAttributes = $context[self::IGNORED_ATTRIBUTES] ?? $this->defaultContext[self::IGNORED_ATTRIBUTES] ?? $this->ignoredAttributes; + if (\in_array($attribute, $ignoredAttributes)) { return false; } - if (isset($context[self::ATTRIBUTES][$attribute])) { + $attributes = $context[self::ATTRIBUTES] ?? $this->defaultContext[self::ATTRIBUTES] ?? null; + if (isset($attributes[$attribute])) { // Nested attributes return true; } - if (isset($context[self::ATTRIBUTES]) && \is_array($context[self::ATTRIBUTES])) { - return \in_array($attribute, $context[self::ATTRIBUTES], true); + if (\is_array($attributes)) { + return \in_array($attribute, $attributes, true); } return true; @@ -335,8 +380,8 @@ protected function getConstructor(array &$data, $class, array &$context, \Reflec */ protected function instantiateObject(array &$data, $class, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes, string $format = null) { - if (null !== $object = $this->extractObjectToPopulate($class, $context, static::OBJECT_TO_POPULATE)) { - unset($context[static::OBJECT_TO_POPULATE]); + if (null !== $object = $this->extractObjectToPopulate($class, $context, self::OBJECT_TO_POPULATE)) { + unset($context[self::OBJECT_TO_POPULATE]); return $object; } @@ -371,7 +416,7 @@ protected function instantiateObject(array &$data, $class, array &$context, \Ref try { if (null !== $constructorParameter->getClass()) { if (!$this->serializer instanceof DenormalizerInterface) { - throw new LogicException(sprintf('Cannot create an instance of %s from serialized data because the serializer inject in "%s" is not a denormalizer', $constructorParameter->getClass(), static::class)); + throw new LogicException(sprintf('Cannot create an instance of %s from serialized data because the serializer inject in "%s" is not a denormalizer', $constructorParameter->getClass(), self::class)); } $parameterClass = $constructorParameter->getClass()->getName(); $parameterData = $this->serializer->denormalize($parameterData, $parameterClass, $format, $this->createChildContext($context, $paramName)); @@ -388,8 +433,8 @@ protected function instantiateObject(array &$data, $class, array &$context, \Ref // Don't run set for a parameter passed to the constructor $params[] = $parameterData; unset($data[$key]); - } elseif (isset($context[static::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class][$key])) { - $params[] = $context[static::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class][$key]; + } elseif (null !== $param = $context[self::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class][$key] ?? $this->defaultContext[self::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class][$key] ?? null) { + $params[] = $param; } elseif ($constructorParameter->isDefaultValueAvailable()) { $params[] = $constructorParameter->getDefaultValue(); } else { diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index 6864f8e145d17..649df9c3a838e 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -36,12 +36,16 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer const DEPTH_KEY_PATTERN = 'depth_%s::%s'; const DISABLE_TYPE_ENFORCEMENT = 'disable_type_enforcement'; const SKIP_NULL_VALUES = 'skip_null_values'; + const MAX_DEPTH_HANDLER = 'max_depth_handler'; + const EXCLUDE_FROM_CACHE_KEY = 'exclude_from_cache_key'; private $propertyTypeExtractor; private $typesCache = array(); private $attributesCache = array(); /** + * @deprecated since Symfony 4.2 + * * @var callable|null */ private $maxDepthHandler; @@ -52,9 +56,10 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer */ protected $classDiscriminatorResolver; - public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, PropertyTypeExtractorInterface $propertyTypeExtractor = null, ClassDiscriminatorResolverInterface $classDiscriminatorResolver = null, callable $objectClassResolver = null) + public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, PropertyTypeExtractorInterface $propertyTypeExtractor = null, ClassDiscriminatorResolverInterface $classDiscriminatorResolver = null, callable $objectClassResolver = null, array $defaultContext = array()) { - parent::__construct($classMetadataFactory, $nameConverter); + parent::__construct($classMetadataFactory, $nameConverter, $defaultContext); + $this->defaultContext[self::EXCLUDE_FROM_CACHE_KEY] = array(self::CIRCULAR_REFERENCE_LIMIT_COUNTERS); $this->propertyTypeExtractor = $propertyTypeExtractor; @@ -91,20 +96,25 @@ public function normalize($object, $format = null, array $context = array()) $attributes = $this->getAttributes($object, $format, $context); $class = $this->objectClassResolver ? \call_user_func($this->objectClassResolver, $object) : \get_class($object); $attributesMetadata = $this->classMetadataFactory ? $this->classMetadataFactory->getMetadataFor($class)->getAttributesMetadata() : null; + $maxDepthHandler = $context[self::MAX_DEPTH_HANDLER] ?? $this->defaultContext[self::MAX_DEPTH_HANDLER] ?? $this->maxDepthHandler; foreach ($attributes as $attribute) { $maxDepthReached = false; - if (null !== $attributesMetadata && ($maxDepthReached = $this->isMaxDepthReached($attributesMetadata, $class, $attribute, $context)) && !$this->maxDepthHandler) { + if (null !== $attributesMetadata && ($maxDepthReached = $this->isMaxDepthReached($attributesMetadata, $class, $attribute, $context)) && !$maxDepthHandler) { continue; } $attributeValue = $this->getAttributeValue($object, $attribute, $format, $context); if ($maxDepthReached) { - $attributeValue = \call_user_func($this->maxDepthHandler, $attributeValue, $object, $attribute, $format, $context); + $attributeValue = \call_user_func($maxDepthHandler, $attributeValue, $object, $attribute, $format, $context); } - if (isset($this->callbacks[$attribute])) { - $attributeValue = \call_user_func($this->callbacks[$attribute], $attributeValue, $object, $attribute, $format, $context); + /** + * @var $callback callable|null + */ + $callback = $context[self::CALLBACKS][$attribute] ?? $this->defaultContext[self::CALLBACKS][$attribute] ?? $this->callbacks[$attribute] ?? null; + if ($callback) { + $attributeValue = $callback($attributeValue, $object, $attribute, $format, $context); } if (null !== $attributeValue && !is_scalar($attributeValue)) { @@ -175,7 +185,7 @@ protected function getAttributes($object, $format = null, array $context) return $allowedAttributes; } - if (isset($context['attributes'])) { + if ($context[self::ATTRIBUTES] ?? $this->defaultContext[self::ATTRIBUTES] ?? false) { return $this->extractAttributes($object, $format, $context); } @@ -217,9 +227,13 @@ abstract protected function getAttributeValue($object, $attribute, $format = nul /** * Sets a handler function that will be called when the max depth is reached. + * + * @deprecated since Symfony 4.2 */ public function setMaxDepthHandler(?callable $handler): void { + @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the "max_depth_handler" key of the context instead.', __METHOD__), E_USER_DEPRECATED); + $this->maxDepthHandler = $handler; } @@ -253,7 +267,7 @@ public function denormalize($data, $class, $format = null, array $context = arra } if ((false !== $allowedAttributes && !\in_array($attribute, $allowedAttributes)) || !$this->isAllowedAttribute($class, $attribute, $format, $context)) { - if (isset($context[self::ALLOW_EXTRA_ATTRIBUTES]) && !$context[self::ALLOW_EXTRA_ATTRIBUTES]) { + if (!($context[self::ALLOW_EXTRA_ATTRIBUTES] ?? $this->defaultContext[self::ALLOW_EXTRA_ATTRIBUTES])) { $extraAttributes[] = $attribute; } @@ -354,7 +368,7 @@ private function validateAndDenormalize(string $currentClass, string $attribute, } } - if (!empty($context[self::DISABLE_TYPE_ENFORCEMENT])) { + if ($context[self::DISABLE_TYPE_ENFORCEMENT] ?? $this->defaultContext[self::DISABLE_TYPE_ENFORCEMENT] ?? false) { return $data; } @@ -405,7 +419,7 @@ private function getTypes(string $currentClass, string $attribute) */ private function updateData(array $data, string $attribute, $attributeValue, string $class, ?string $format, array $context): array { - if (null === $attributeValue && ($context[self::SKIP_NULL_VALUES] ?? false)) { + if (null === $attributeValue && ($context[self::SKIP_NULL_VALUES] ?? $this->defaultContext[self::SKIP_NULL_VALUES] ?? false)) { return $data; } @@ -425,16 +439,16 @@ private function updateData(array $data, string $attribute, $attributeValue, str */ private function isMaxDepthReached(array $attributesMetadata, string $class, string $attribute, array &$context): bool { + $enableMaxDepth = $context[self::ENABLE_MAX_DEPTH] ?? $this->defaultContext[self::ENABLE_MAX_DEPTH] ?? false; if ( - !isset($context[static::ENABLE_MAX_DEPTH]) || - !$context[static::ENABLE_MAX_DEPTH] || + !$enableMaxDepth || !isset($attributesMetadata[$attribute]) || null === $maxDepth = $attributesMetadata[$attribute]->getMaxDepth() ) { return false; } - $key = sprintf(static::DEPTH_KEY_PATTERN, $class, $attribute); + $key = sprintf(self::DEPTH_KEY_PATTERN, $class, $attribute); if (!isset($context[$key])) { $context[$key] = 1; @@ -457,6 +471,11 @@ private function isMaxDepthReached(array $attributesMetadata, string $class, str */ private function getCacheKey(?string $format, array $context) { + foreach ($context[self::EXCLUDE_FROM_CACHE_KEY] ?? $this->defaultContext[self::EXCLUDE_FROM_CACHE_KEY] as $key) { + unset($context[$key]); + } + unset($context[self::EXCLUDE_FROM_CACHE_KEY]); + try { return md5($format.serialize($context)); } catch (\Exception $exception) { diff --git a/src/Symfony/Component/Serializer/Normalizer/ConstraintViolationListNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/ConstraintViolationListNormalizer.php index bc9aa60bd3a08..dc24f6ec16bde 100644 --- a/src/Symfony/Component/Serializer/Normalizer/ConstraintViolationListNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/ConstraintViolationListNormalizer.php @@ -24,6 +24,18 @@ */ class ConstraintViolationListNormalizer implements NormalizerInterface, CacheableSupportsMethodInterface { + const INSTANCE = 'instance'; + const STATUS = 'status'; + const TITLE = 'title'; + const TYPE = 'type'; + + private $defaultContext; + + public function __construct($defaultContext = array()) + { + $this->defaultContext = $defaultContext; + } + /** * {@inheritdoc} */ @@ -49,17 +61,17 @@ public function normalize($object, $format = null, array $context = array()) } $result = array( - 'type' => $context['type'] ?? 'https://symfony.com/errors/validation', - 'title' => $context['title'] ?? 'Validation Failed', + 'type' => $context[self::TYPE] ?? $this->defaultContext[self::TYPE] ?? 'https://symfony.com/errors/validation', + 'title' => $context[self::TITLE] ?? $this->defaultContext[self::TITLE] ?? 'Validation Failed', ); - if (isset($context['status'])) { - $result['status'] = $context['status']; + if (null !== $status = ($context[self::STATUS] ?? $this->defaultContext[self::STATUS] ?? null)) { + $result['status'] = $status; } if ($messages) { $result['detail'] = implode("\n", $messages); } - if (isset($context['instance'])) { - $result['instance'] = $context['instance']; + if (null !== $instance = ($context[self::INSTANCE] ?? $this->defaultContext[self::INSTANCE] ?? null)) { + $result['instance'] = $instance; } return $result + array('violations' => $violations); diff --git a/src/Symfony/Component/Serializer/Normalizer/DateIntervalNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/DateIntervalNormalizer.php index 2dce4d5b54167..9a050ae003ed8 100644 --- a/src/Symfony/Component/Serializer/Normalizer/DateIntervalNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/DateIntervalNormalizer.php @@ -24,11 +24,22 @@ class DateIntervalNormalizer implements NormalizerInterface, DenormalizerInterfa { const FORMAT_KEY = 'dateinterval_format'; - private $format; + private $defaultContext = array( + self::FORMAT_KEY => 'P%yY%mM%dDT%hH%iM%sS', + ); - public function __construct(string $format = 'P%yY%mM%dDT%hH%iM%sS') + /** + * @param array $defaultContext + */ + public function __construct($defaultContext = array()) { - $this->format = $format; + if (!\is_array($defaultContext)) { + @trigger_error(sprintf('The "format" parameter is deprecated since Symfony 4.2, use the "%s" key of the context instead.', self::FORMAT_KEY), E_USER_DEPRECATED); + + $defaultContext = array(self::FORMAT_KEY => (string) $defaultContext); + } + + $this->defaultContext = array_merge($this->defaultContext, $defaultContext); } /** @@ -42,9 +53,7 @@ public function normalize($object, $format = null, array $context = array()) throw new InvalidArgumentException('The object must be an instance of "\DateInterval".'); } - $dateIntervalFormat = isset($context[self::FORMAT_KEY]) ? $context[self::FORMAT_KEY] : $this->format; - - return $object->format($dateIntervalFormat); + return $object->format($context[self::FORMAT_KEY] ?? $this->defaultContext[self::FORMAT_KEY]); } /** @@ -79,7 +88,7 @@ public function denormalize($data, $class, $format = null, array $context = arra throw new UnexpectedValueException('Expected a valid ISO 8601 interval string.'); } - $dateIntervalFormat = isset($context[self::FORMAT_KEY]) ? $context[self::FORMAT_KEY] : $this->format; + $dateIntervalFormat = $context[self::FORMAT_KEY] ?? $this->defaultContext[self::FORMAT_KEY]; $valuePattern = '/^'.preg_replace('/%([yYmMdDhHiIsSwW])(\w)/', '(?P<$1>\d+)$2', $dateIntervalFormat).'$/'; if (!preg_match($valuePattern, $data)) { diff --git a/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php index b2ebe97bd57e9..26c48e5fd49fd 100644 --- a/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php @@ -25,8 +25,7 @@ class DateTimeNormalizer implements NormalizerInterface, DenormalizerInterface, const FORMAT_KEY = 'datetime_format'; const TIMEZONE_KEY = 'datetime_timezone'; - private $format; - private $timezone; + private $defaultContext; private static $supportedTypes = array( \DateTimeInterface::class => true, @@ -34,10 +33,24 @@ class DateTimeNormalizer implements NormalizerInterface, DenormalizerInterface, \DateTime::class => true, ); - public function __construct(?string $format = \DateTime::RFC3339, \DateTimeZone $timezone = null) + /** + * @param array $defaultContext + */ + public function __construct($defaultContext = array(), \DateTimeZone $timezone = null) { - $this->format = $format; - $this->timezone = $timezone; + $this->defaultContext = array( + self::FORMAT_KEY => \DateTime::RFC3339, + self::TIMEZONE_KEY => null, + ); + + if (!\is_array($defaultContext)) { + @trigger_error('Passing configuration options directly to the constructor is deprecated since Symfony 4.2, use the default context instead.', E_USER_DEPRECATED); + + $defaultContext = array(self::FORMAT_KEY => (string) $defaultContext); + $defaultContext[self::TIMEZONE_KEY] = $timezone; + } + + $this->defaultContext = array_merge($this->defaultContext, $defaultContext); } /** @@ -51,14 +64,14 @@ public function normalize($object, $format = null, array $context = array()) throw new InvalidArgumentException('The object must implement the "\DateTimeInterface".'); } - $format = isset($context[self::FORMAT_KEY]) ? $context[self::FORMAT_KEY] : $this->format; + $dateTimeFormat = $context[self::FORMAT_KEY] ?? $this->defaultContext[self::FORMAT_KEY]; $timezone = $this->getTimezone($context); if (null !== $timezone) { $object = (new \DateTimeImmutable('@'.$object->getTimestamp()))->setTimezone($timezone); } - return $object->format($format); + return $object->format($dateTimeFormat); } /** @@ -76,7 +89,7 @@ public function supportsNormalization($data, $format = null) */ public function denormalize($data, $class, $format = null, array $context = array()) { - $dateTimeFormat = isset($context[self::FORMAT_KEY]) ? $context[self::FORMAT_KEY] : null; + $dateTimeFormat = $context[self::FORMAT_KEY] ?? null; $timezone = $this->getTimezone($context); if ('' === $data || null === $data) { @@ -142,8 +155,7 @@ private function formatDateTimeErrors(array $errors) private function getTimezone(array $context) { - $dateTimeZone = array_key_exists(self::TIMEZONE_KEY, $context) ? $context[self::TIMEZONE_KEY] : $this->timezone; - + $dateTimeZone = $context[self::TIMEZONE_KEY] ?? $this->defaultContext[self::TIMEZONE_KEY]; if (null === $dateTimeZone) { return null; } diff --git a/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php index a6ea398e6265d..f0f749ad74fca 100644 --- a/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php @@ -30,13 +30,13 @@ class ObjectNormalizer extends AbstractObjectNormalizer { protected $propertyAccessor; - public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, PropertyAccessorInterface $propertyAccessor = null, PropertyTypeExtractorInterface $propertyTypeExtractor = null, ClassDiscriminatorResolverInterface $classDiscriminatorResolver = null, callable $objectClassResolver = null) + public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, PropertyAccessorInterface $propertyAccessor = null, PropertyTypeExtractorInterface $propertyTypeExtractor = null, ClassDiscriminatorResolverInterface $classDiscriminatorResolver = null, callable $objectClassResolver = null, array $defaultContext = array()) { if (!\class_exists(PropertyAccess::class)) { throw new LogicException('The ObjectNormalizer class requires the "PropertyAccess" component. Install "symfony/property-access" to use it.'); } - parent::__construct($classMetadataFactory, $nameConverter, $propertyTypeExtractor, $classDiscriminatorResolver, $objectClassResolver); + parent::__construct($classMetadataFactory, $nameConverter, $propertyTypeExtractor, $classDiscriminatorResolver, $objectClassResolver, $defaultContext); $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); } diff --git a/src/Symfony/Component/Serializer/Tests/Encoder/CsvEncoderTest.php b/src/Symfony/Component/Serializer/Tests/Encoder/CsvEncoderTest.php index 9a71034496147..65ced222a9760 100644 --- a/src/Symfony/Component/Serializer/Tests/Encoder/CsvEncoderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Encoder/CsvEncoderTest.php @@ -100,7 +100,26 @@ public function testEncodeNestedArrays() public function testEncodeCustomSettings() { - $this->encoder = new CsvEncoder(';', "'", '|', '-'); + $this->doTestEncodeCustomSettings(); + } + + public function testLegacyEncodeCustomSettings() + { + $this->doTestEncodeCustomSettings(true); + } + + private function doTestEncodeCustomSettings(bool $legacy = false) + { + if ($legacy) { + $this->encoder = new CsvEncoder(';', "'", '|', '-'); + } else { + $this->encoder = new CsvEncoder(array( + CsvEncoder::DELIMITER_KEY => ';', + CsvEncoder::ENCLOSURE_KEY => "'", + CsvEncoder::ESCAPE_CHAR_KEY => '|', + CsvEncoder::KEY_SEPARATOR_KEY => '-', + )); + } $value = array('a' => 'he\'llo', 'c' => array('d' => 'foo')); @@ -175,7 +194,21 @@ public function testEncodeCustomHeaders() public function testEncodeFormulas() { - $this->encoder = new CsvEncoder(',', '"', '\\', '.', true); + $this->doTestEncodeFormulas(); + } + + public function testLegacyEncodeFormulas() + { + $this->doTestEncodeFormulas(true); + } + + private function doTestEncodeFormulas(bool $legacy = false) + { + if ($legacy) { + $this->encoder = new CsvEncoder(',', '"', '\\', '.', true); + } else { + $this->encoder = new CsvEncoder(array(CsvEncoder::ESCAPE_FORMULAS_KEY => true)); + } $this->assertSame(<<<'CSV' 0 @@ -378,7 +411,26 @@ public function testDecodeNestedArrays() public function testDecodeCustomSettings() { - $this->encoder = new CsvEncoder(';', "'", '|', '-'); + $this->doTestDecodeCustomSettings(); + } + + public function testLegacyDecodeCustomSettings() + { + $this->doTestDecodeCustomSettings(true); + } + + private function doTestDecodeCustomSettings(bool $legacy = false) + { + if ($legacy) { + $this->encoder = new CsvEncoder(';', "'", '|', '-'); + } else { + $this->encoder = new CsvEncoder(array( + CsvEncoder::DELIMITER_KEY => ';', + CsvEncoder::ENCLOSURE_KEY => "'", + CsvEncoder::ESCAPE_CHAR_KEY => '|', + CsvEncoder::KEY_SEPARATOR_KEY => '-', + )); + } $expected = array(array('a' => 'hell\'o', 'bar' => array('baz' => 'b'))); $this->assertEquals($expected, $this->encoder->decode(<<<'CSV' diff --git a/src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php b/src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php index 3fa60122e1b08..6ef68c13f9260 100644 --- a/src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php @@ -47,6 +47,9 @@ public function testEncodeScalar() $this->assertEquals($expected, $this->encoder->encode($obj, 'xml')); } + /** + * @group legacy + */ public function testSetRootNodeName() { $obj = new ScalarDummy(); @@ -543,6 +546,16 @@ public function testDecodeIgnoreComments() } public function testDecodePreserveComments() + { + $this->doTestDecodePreserveComments(); + } + + public function testLegacyDecodePreserveComments() + { + $this->doTestDecodePreserveComments(true); + } + + private function doTestDecodePreserveComments(bool $legacy = false) { $source = <<<'XML' @@ -559,7 +572,14 @@ public function testDecodePreserveComments() XML; - $this->encoder = new XmlEncoder('people', null, array(XML_PI_NODE)); + if ($legacy) { + $this->encoder = new XmlEncoder('people', null, array(XML_PI_NODE)); + } else { + $this->encoder = new XmlEncoder(array( + XmlEncoder::ROOT_NODE_NAME => 'people', + XmlEncoder::DECODER_IGNORED_NODE_TYPES => array(XML_PI_NODE), + )); + } $serializer = new Serializer(array(new CustomNormalizer()), array('xml' => new XmlEncoder())); $this->encoder->setSerializer($serializer); @@ -573,7 +593,21 @@ public function testDecodePreserveComments() public function testDecodeAlwaysAsCollection() { - $this->encoder = new XmlEncoder('response', null); + $this->doTestDecodeAlwaysAsCollection(); + } + + public function testLegacyDecodeAlwaysAsCollection() + { + $this->doTestDecodeAlwaysAsCollection(true); + } + + private function doTestDecodeAlwaysAsCollection(bool $legacy = false) + { + if ($legacy) { + $this->encoder = new XmlEncoder('response', null); + } else { + $this->encoder = new XmlEncoder(array(XmlEncoder::ROOT_NODE_NAME => 'response')); + } $serializer = new Serializer(array(new CustomNormalizer()), array('xml' => new XmlEncoder())); $this->encoder->setSerializer($serializer); @@ -773,9 +807,26 @@ public function testEncodeComment() $this->assertEquals($expected, $this->encoder->encode($data, 'xml')); } - public function testEncodeWithoutPI() + public function testEncodeWithoutPi() { - $encoder = new XmlEncoder('response', null, array(), array(XML_PI_NODE)); + $this->doTestEncodeWithoutPi(); + } + + public function testLegacyEncodeWithoutPi() + { + $this->doTestEncodeWithoutPi(true); + } + + private function doTestEncodeWithoutPi(bool $legacy = false) + { + if ($legacy) { + $encoder = new XmlEncoder('response', null, array(), array(XML_PI_NODE)); + } else { + $encoder = new XmlEncoder(array( + XmlEncoder::ROOT_NODE_NAME => 'response', + XmlEncoder::ENCODER_IGNORED_NODE_TYPES => array(XML_PI_NODE), + )); + } $expected = ''; @@ -784,7 +835,24 @@ public function testEncodeWithoutPI() public function testEncodeWithoutComment() { - $encoder = new XmlEncoder('response', null, array(), array(XML_COMMENT_NODE)); + $this->doTestEncodeWithoutComment(); + } + + public function testLegacyEncodeWithoutComment() + { + $this->doTestEncodeWithoutComment(true); + } + + private function doTestEncodeWithoutComment(bool $legacy = false) + { + if ($legacy) { + $encoder = new XmlEncoder('response', null, array(), array(XML_COMMENT_NODE)); + } else { + $encoder = new XmlEncoder(array( + XmlEncoder::ROOT_NODE_NAME => 'response', + XmlEncoder::ENCODER_IGNORED_NODE_TYPES => array(XML_COMMENT_NODE), + )); + } $expected = <<<'XML' diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/DateIntervalNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/DateIntervalNormalizerTest.php index f6dc6c2475e53..86478bb413e6f 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/DateIntervalNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/DateIntervalNormalizerTest.php @@ -58,7 +58,21 @@ public function testNormalizeUsingFormatPassedInContext($format, $output, $input */ public function testNormalizeUsingFormatPassedInConstructor($format, $output, $input) { - $this->assertEquals($output, (new DateIntervalNormalizer($format))->normalize(new \DateInterval($input))); + $this->doTestNormalizeUsingFormatPassedInConstructor($format, $output, $input); + } + + /** + * @dataProvider dataProviderISO + */ + public function testLegacyNormalizeUsingFormatPassedInConstructor($format, $output, $input) + { + $this->doTestNormalizeUsingFormatPassedInConstructor($format, $output, $input, true); + } + + private function doTestNormalizeUsingFormatPassedInConstructor($format, $output, $input, bool $legacy = false) + { + $normalizer = $legacy ? new DateIntervalNormalizer($format) : new DateIntervalNormalizer(array(DateIntervalNormalizer::FORMAT_KEY => $format)); + $this->assertEquals($output, $normalizer->normalize(new \DateInterval($input))); } /** @@ -94,7 +108,21 @@ public function testDenormalizeUsingFormatPassedInContext($format, $input, $outp */ public function testDenormalizeUsingFormatPassedInConstructor($format, $input, $output) { - $this->assertDateIntervalEquals(new \DateInterval($output), (new DateIntervalNormalizer($format))->denormalize($input, \DateInterval::class)); + $this->doTestDenormalizeUsingFormatPassedInConstructor($format, $input, $output); + } + + /** + * @dataProvider dataProviderISO + */ + public function testLegacyDenormalizeUsingFormatPassedInConstructor($format, $input, $output) + { + $this->doTestDenormalizeUsingFormatPassedInConstructor($format, $input, $output, true); + } + + private function doTestDenormalizeUsingFormatPassedInConstructor($format, $input, $output, bool $legacy = false) + { + $normalizer = $legacy ? new DateIntervalNormalizer($format) : new DateIntervalNormalizer(array(DateIntervalNormalizer::FORMAT_KEY => $format)); + $this->assertDateIntervalEquals(new \DateInterval($output), $normalizer->denormalize($input, \DateInterval::class)); } /** diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/DateTimeNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/DateTimeNormalizerTest.php index 178519b30e687..87edb8df4c993 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/DateTimeNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/DateTimeNormalizerTest.php @@ -49,12 +49,37 @@ public function testNormalizeUsingFormatPassedInContext() public function testNormalizeUsingFormatPassedInConstructor() { - $this->assertEquals('16', (new DateTimeNormalizer('y'))->normalize(new \DateTime('2016/01/01', new \DateTimeZone('UTC')))); + $this->doTestNormalizeUsingFormatPassedInConstructor(); + } + + public function testLegacyNormalizeUsingFormatPassedInConstructor() + { + $this->doTestNormalizeUsingFormatPassedInConstructor(true); + } + + private function doTestNormalizeUsingFormatPassedInConstructor(bool $legacy = false) + { + $normalizer = $legacy ? new DateTimeNormalizer('y') : new DateTimeNormalizer(array(DateTimeNormalizer::FORMAT_KEY => 'y')); + $this->assertEquals('16', $normalizer->normalize(new \DateTime('2016/01/01', new \DateTimeZone('UTC')))); } public function testNormalizeUsingTimeZonePassedInConstructor() { - $normalizer = new DateTimeNormalizer(\DateTime::RFC3339, new \DateTimeZone('Japan')); + $this->doTestNormalizeUsingTimeZonePassedInConstructor(); + } + + public function testLegacyNormalizeUsingTimeZonePassedInConstructor() + { + $this->doTestNormalizeUsingTimeZonePassedInConstructor(true); + } + + private function doTestNormalizeUsingTimeZonePassedInConstructor(bool $legacy = false) + { + if ($legacy) { + $normalizer = new DateTimeNormalizer(\DateTime::RFC3339, new \DateTimeZone('Japan')); + } else { + $normalizer = new DateTimeNormalizer(array(DateTimeNormalizer::TIMEZONE_KEY => new \DateTimeZone('Japan'))); + } $this->assertSame('2016-12-01T00:00:00+09:00', $normalizer->normalize(new \DateTime('2016/12/01', new \DateTimeZone('Japan')))); $this->assertSame('2016-12-01T09:00:00+09:00', $normalizer->normalize(new \DateTime('2016/12/01', new \DateTimeZone('UTC')))); @@ -103,10 +128,20 @@ public function testDenormalize() } public function testDenormalizeUsingTimezonePassedInConstructor() + { + $this->doTestDenormalizeUsingTimezonePassedInConstructor(); + } + + public function testLegacyDenormalizeUsingTimezonePassedInConstructor() + { + $this->doTestDenormalizeUsingTimezonePassedInConstructor(true); + } + + private function doTestDenormalizeUsingTimezonePassedInConstructor(bool $legacy = false) { $timezone = new \DateTimeZone('Japan'); $expected = new \DateTime('2016/12/01 17:35:00', $timezone); - $normalizer = new DateTimeNormalizer(null, $timezone); + $normalizer = $legacy ? new DateTimeNormalizer(null, $timezone) : new DateTimeNormalizer(array(DateTimeNormalizer::TIMEZONE_KEY => $timezone)); $this->assertEquals($expected, $normalizer->denormalize('2016.12.01 17:35:00', \DateTime::class, null, array( DateTimeNormalizer::FORMAT_KEY => 'Y.m.d H:i:s', diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php index 8940f70a4f4f2..07277579b3b5d 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/GetSetMethodNormalizerTest.php @@ -37,9 +37,14 @@ class GetSetMethodNormalizerTest extends TestCase private $serializer; protected function setUp() + { + $this->createNormalizer(); + } + + private function createNormalizer(array $defaultContext = array()) { $this->serializer = $this->getMockBuilder(__NAMESPACE__.'\SerializerNormalizer')->getMock(); - $this->normalizer = new GetSetMethodNormalizer(); + $this->normalizer = new GetSetMethodNormalizer(null, null, null, null, null, $defaultContext); $this->normalizer->setSerializer($this->serializer); } @@ -285,10 +290,22 @@ public function testGroupsDenormalizeWithNameConverter() */ public function testCallbacks($callbacks, $value, $result, $message) { - $this->normalizer->setCallbacks($callbacks); + $this->doTestCallbacks($callbacks, $value, $result, $message); + } - $obj = new GetConstructorDummy('', $value, true); + /** + * @dataProvider provideCallbacks + */ + public function testLegacyCallbacks($callbacks, $value, $result, $message) + { + $this->doTestCallbacks($callbacks, $value, $result, $message, true); + } + private function doTestCallbacks($callbacks, $value, $result, $message, bool $legacy = false) + { + $legacy ? $this->normalizer->setCallbacks($callbacks) : $this->createNormalizer(array(GetSetMethodNormalizer::CALLBACKS => $callbacks)); + + $obj = new GetConstructorDummy('', $value, true); $this->assertEquals( $result, $this->normalizer->normalize($obj, 'any'), @@ -301,7 +318,21 @@ public function testCallbacks($callbacks, $value, $result, $message) */ public function testUncallableCallbacks() { - $this->normalizer->setCallbacks(array('bar' => null)); + $this->doTestUncallableCallbacks(); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testLegacyUncallableCallbacks() + { + $this->doTestUncallableCallbacks(true); + } + + private function doTestUncallableCallbacks(bool $legacy = false) + { + $callbacks = array('bar' => null); + $legacy ? $this->normalizer->setCallbacks($callbacks) : $this->createNormalizer(array(GetSetMethodNormalizer::CALLBACKS => $callbacks)); $obj = new GetConstructorDummy('baz', 'quux', true); @@ -310,7 +341,18 @@ public function testUncallableCallbacks() public function testIgnoredAttributes() { - $this->normalizer->setIgnoredAttributes(array('foo', 'bar', 'baz', 'camelCase', 'object')); + $this->doTestIgnoredAttributes(); + } + + public function testLegacyIgnoredAttributes() + { + $this->doTestIgnoredAttributes(true); + } + + private function doTestIgnoredAttributes(bool $legacy = false) + { + $ignoredAttributes = array('foo', 'bar', 'baz', 'camelCase', 'object'); + $legacy ? $this->normalizer->setIgnoredAttributes($ignoredAttributes) : $this->createNormalizer(array(GetSetMethodNormalizer::IGNORED_ATTRIBUTES => $ignoredAttributes)); $obj = new GetSetDummy(); $obj->setFoo('foo'); @@ -404,12 +446,24 @@ public function testUnableToNormalizeObjectAttribute() */ public function testUnableToNormalizeCircularReference() { - $serializer = new Serializer(array($this->normalizer)); - $this->normalizer->setSerializer($serializer); - $this->normalizer->setCircularReferenceLimit(2); + $this->doTestUnableToNormalizeCircularReference(); + } - $obj = new CircularReferenceDummy(); + /** + * @expectedException \Symfony\Component\Serializer\Exception\CircularReferenceException + */ + public function testLegacyUnableToNormalizeCircularReference() + { + $this->doTestUnableToNormalizeCircularReference(true); + } + private function doTestUnableToNormalizeCircularReference(bool $legacy = false) + { + $legacy ? $this->normalizer->setCircularReferenceLimit(2) : $this->createNormalizer(array(GetSetMethodNormalizer::CIRCULAR_REFERENCE_LIMIT => 2)); + $this->serializer = new Serializer(array($this->normalizer)); + $this->normalizer->setSerializer($this->serializer); + + $obj = new CircularReferenceDummy(); $this->normalizer->normalize($obj); } @@ -430,11 +484,23 @@ public function testSiblingReference() public function testCircularReferenceHandler() { - $serializer = new Serializer(array($this->normalizer)); - $this->normalizer->setSerializer($serializer); - $this->normalizer->setCircularReferenceHandler(function ($obj) { + $this->doTestCircularReferenceHandler(); + } + + public function testLegacyCircularReferenceHandler() + { + $this->doTestCircularReferenceHandler(true); + } + + private function doTestCircularReferenceHandler(bool $legacy = false) + { + $handler = function ($obj) { return \get_class($obj); - }); + }; + + $legacy ? $this->normalizer->setCircularReferenceHandler($handler) : $this->createNormalizer(array(GetSetMethodNormalizer::CIRCULAR_REFERENCE_HANDLER => $handler)); + $this->serializer = new Serializer(array($this->normalizer)); + $this->normalizer->setSerializer($this->serializer); $obj = new CircularReferenceDummy(); diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/JsonSerializableNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/JsonSerializableNormalizerTest.php index 065d92b99f05e..d1628be2b539a 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/JsonSerializableNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/JsonSerializableNormalizerTest.php @@ -33,9 +33,14 @@ class JsonSerializableNormalizerTest extends TestCase private $serializer; protected function setUp() + { + $this->createNormalizer(); + } + + private function createNormalizer(array $defaultContext = array()) { $this->serializer = $this->getMockBuilder(JsonSerializerNormalizer::class)->getMock(); - $this->normalizer = new JsonSerializableNormalizer(); + $this->normalizer = new JsonSerializableNormalizer(null, null, $defaultContext); $this->normalizer->setSerializer($this->serializer); } @@ -65,7 +70,23 @@ public function testNormalize() */ public function testCircularNormalize() { - $this->normalizer->setCircularReferenceLimit(1); + $this->doTestCircularNormalize(); + } + + /** + * @expectedException \Symfony\Component\Serializer\Exception\CircularReferenceException + */ + public function testLegacyCircularNormalize() + { + $this->doTestCircularNormalize(true); + } + + /** + * @expectedException \Symfony\Component\Serializer\Exception\CircularReferenceException + */ + private function doTestCircularNormalize(bool $legacy = false) + { + $legacy ? $this->normalizer->setCircularReferenceLimit(1) : $this->createNormalizer(array(JsonSerializableNormalizer::CIRCULAR_REFERENCE_LIMIT => 1)); $this->serializer ->expects($this->once()) diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php index 9f1c590a0950a..9eff7914d1c89 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php @@ -17,6 +17,7 @@ use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; use Symfony\Component\PropertyInfo\PropertyInfoExtractor; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; +use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface; use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; @@ -48,9 +49,14 @@ class ObjectNormalizerTest extends TestCase private $serializer; protected function setUp() + { + $this->createNormalizer(); + } + + private function createNormalizer(array $defaultContext = array(), ClassMetadataFactoryInterface $classMetadataFactory = null) { $this->serializer = $this->getMockBuilder(__NAMESPACE__.'\ObjectSerializerNormalizer')->getMock(); - $this->normalizer = new ObjectNormalizer(); + $this->normalizer = new ObjectNormalizer($classMetadataFactory, null, null, null, null, null, $defaultContext); $this->normalizer->setSerializer($this->serializer); } @@ -393,8 +399,20 @@ public function testMetadataAwareNameConvertorWithNotSerializedConstructorParame */ public function testCallbacks($callbacks, $value, $result, $message) { - $this->normalizer->setCallbacks($callbacks); + $this->doTestCallbacks($callbacks, $value, $result, $message); + } + + /** + * @dataProvider provideCallbacks + */ + public function testLegacyCallbacks($callbacks, $value, $result, $message) + { + $this->doTestCallbacks($callbacks, $value, $result, $message); + } + private function doTestCallbacks($callbacks, $value, $result, $message, bool $legacy = false) + { + $legacy ? $this->normalizer->setCallbacks($callbacks) : $this->createNormalizer(array(ObjectNormalizer::CALLBACKS => $callbacks)); $obj = new ObjectConstructorDummy('', $value, true); $this->assertEquals( @@ -409,7 +427,21 @@ public function testCallbacks($callbacks, $value, $result, $message) */ public function testUncallableCallbacks() { - $this->normalizer->setCallbacks(array('bar' => null)); + $this->doTestUncallableCallbacks(); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testLegacyUncallableCallbacks() + { + $this->doTestUncallableCallbacks(true); + } + + private function doTestUncallableCallbacks(bool $legacy = false) + { + $callbacks = array('bar' => null); + $legacy ? $this->normalizer->setCallbacks($callbacks) : $this->createNormalizer(array(ObjectNormalizer::CALLBACKS => $callbacks)); $obj = new ObjectConstructorDummy('baz', 'quux', true); @@ -418,7 +450,18 @@ public function testUncallableCallbacks() public function testIgnoredAttributes() { - $this->normalizer->setIgnoredAttributes(array('foo', 'bar', 'baz', 'camelCase', 'object')); + $this->doTestIgnoredAttributes(); + } + + public function testLegacyIgnoredAttributes() + { + $this->doTestIgnoredAttributes(true); + } + + private function doTestIgnoredAttributes(bool $legacy = false) + { + $ignoredAttributes = array('foo', 'bar', 'baz', 'camelCase', 'object'); + $legacy ? $this->normalizer->setIgnoredAttributes($ignoredAttributes) : $this->createNormalizer(array(ObjectNormalizer::IGNORED_ATTRIBUTES => $ignoredAttributes)); $obj = new ObjectDummy(); $obj->setFoo('foo'); @@ -433,7 +476,18 @@ public function testIgnoredAttributes() public function testIgnoredAttributesDenormalize() { - $this->normalizer->setIgnoredAttributes(array('fooBar', 'bar', 'baz')); + $this->doTestIgnoredAttributesDenormalize(); + } + + public function testLegacyIgnoredAttributesDenormalize() + { + $this->doTestIgnoredAttributesDenormalize(true); + } + + private function doTestIgnoredAttributesDenormalize(bool $legacy = false) + { + $ignoredAttributes = array('fooBar', 'bar', 'baz'); + $legacy ? $this->normalizer->setIgnoredAttributes($ignoredAttributes) : $this->createNormalizer(array(ObjectNormalizer::IGNORED_ATTRIBUTES => $ignoredAttributes)); $obj = new ObjectDummy(); $obj->setFoo('foo'); @@ -466,7 +520,7 @@ public function provideCallbacks() $this->assertInstanceOf(ObjectConstructorDummy::class, $object); $this->assertSame('bar', $attributeName); $this->assertSame('any', $format); - $this->assertArrayHasKey('circular_reference_limit', $context); + $this->assertArrayHasKey('circular_reference_limit_counters', $context); }, ), 'baz', @@ -532,9 +586,22 @@ public function testUnableToNormalizeObjectAttribute() */ public function testUnableToNormalizeCircularReference() { + $this->doTestUnableToNormalizeCircularReference(); + } + + /** + * @expectedException \Symfony\Component\Serializer\Exception\CircularReferenceException + */ + public function testLegacyUnableToNormalizeCircularReference() + { + $this->doTestUnableToNormalizeCircularReference(true); + } + + private function doTestUnableToNormalizeCircularReference(bool $legacy = false) + { + $legacy ? $this->normalizer->setCircularReferenceLimit(2) : $this->createNormalizer(array(ObjectNormalizer::CIRCULAR_REFERENCE_LIMIT => 2)); $serializer = new Serializer(array($this->normalizer)); $this->normalizer->setSerializer($serializer); - $this->normalizer->setCircularReferenceLimit(2); $obj = new CircularReferenceDummy(); @@ -558,27 +625,41 @@ public function testSiblingReference() public function testCircularReferenceHandler() { - $serializer = new Serializer(array($this->normalizer)); - $this->normalizer->setSerializer($serializer); - $this->normalizer->setCircularReferenceHandler(function ($obj) { + $this->doTestCircularReferenceHandler(); + } + + public function testLegacyCircularReferenceHandler() + { + $this->doTestCircularReferenceHandler(true); + } + + private function doTestCircularReferenceHandler(bool $legacy = false) + { + $this->createNormalizerWithCircularReferenceHandler(function ($obj) { return \get_class($obj); - }); + }, $legacy); $obj = new CircularReferenceDummy(); - $expected = array('me' => 'Symfony\Component\Serializer\Tests\Fixtures\CircularReferenceDummy'); $this->assertEquals($expected, $this->normalizer->normalize($obj)); - $this->normalizer->setCircularReferenceHandler(function ($obj, string $format, array $context) { + $this->createNormalizerWithCircularReferenceHandler(function ($obj, string $format, array $context) { $this->assertInstanceOf(CircularReferenceDummy::class, $obj); $this->assertSame('test', $format); $this->arrayHasKey('foo', $context); return \get_class($obj); - }); + }, $legacy); $this->assertEquals($expected, $this->normalizer->normalize($obj, 'test', array('foo' => 'bar'))); } + private function createNormalizerWithCircularReferenceHandler(callable $handler, bool $legacy) + { + $legacy ? $this->normalizer->setCircularReferenceHandler($handler) : $this->createNormalizer(array(ObjectNormalizer::CIRCULAR_REFERENCE_HANDLER => $handler)); + $this->serializer = new Serializer(array($this->normalizer)); + $this->normalizer->setSerializer($this->serializer); + } + public function testDenormalizeNonExistingAttribute() { $this->assertEquals( @@ -620,11 +701,16 @@ public function testNormalizeNotSerializableContext() public function testMaxDepth() { - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); - $this->normalizer = new ObjectNormalizer($classMetadataFactory); - $serializer = new Serializer(array($this->normalizer)); - $this->normalizer->setSerializer($serializer); + $this->doTestMaxDepth(); + } + + public function testLegacyMaxDepth() + { + $this->doTestMaxDepth(true); + } + private function doTestMaxDepth(bool $legacy = false) + { $level1 = new MaxDepthDummy(); $level1->foo = 'level1'; @@ -636,7 +722,8 @@ public function testMaxDepth() $level3->foo = 'level3'; $level2->child = $level3; - $result = $serializer->normalize($level1, null, array(ObjectNormalizer::ENABLE_MAX_DEPTH => true)); + $this->createNormalizerWithMaxDepthHandler(null, $legacy); + $result = $this->serializer->normalize($level1, null, array(ObjectNormalizer::ENABLE_MAX_DEPTH => true)); $expected = array( 'bar' => null, @@ -667,14 +754,13 @@ public function testMaxDepth() ), ); - $this->normalizer->setMaxDepthHandler(function ($obj) { + $this->createNormalizerWithMaxDepthHandler(function () { return 'handler'; - }); - - $result = $serializer->normalize($level1, null, array(ObjectNormalizer::ENABLE_MAX_DEPTH => true)); + }, $legacy); + $result = $this->serializer->normalize($level1, null, array(ObjectNormalizer::ENABLE_MAX_DEPTH => true)); $this->assertEquals($expected, $result); - $this->normalizer->setMaxDepthHandler(function ($object, $parentObject, $attributeName, $format, $context) { + $this->createNormalizerWithMaxDepthHandler(function ($object, $parentObject, $attributeName, $format, $context) { $this->assertSame('level3', $object); $this->assertInstanceOf(MaxDepthDummy::class, $parentObject); $this->assertSame('foo', $attributeName); @@ -682,9 +768,23 @@ public function testMaxDepth() $this->assertArrayHasKey(ObjectNormalizer::ENABLE_MAX_DEPTH, $context); return 'handler'; - }); + }, $legacy); + $this->serializer->normalize($level1, 'test', array(ObjectNormalizer::ENABLE_MAX_DEPTH => true)); + } - $serializer->normalize($level1, 'test', array(ObjectNormalizer::ENABLE_MAX_DEPTH => true)); + private function createNormalizerWithMaxDepthHandler(callable $handler = null, bool $legacy = false) + { + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + if ($legacy) { + $this->createNormalizer(array(), $classMetadataFactory); + if (null !== $handler) { + $this->normalizer->setMaxDepthHandler($handler); + } + } else { + $this->createNormalizer(array(ObjectNormalizer::MAX_DEPTH_HANDLER => $handler), $classMetadataFactory); + } + $this->serializer = new Serializer(array($this->normalizer)); + $this->normalizer->setSerializer($this->serializer); } /** diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/PropertyNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/PropertyNormalizerTest.php index 444fdabc1b20a..091421f07271b 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/PropertyNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/PropertyNormalizerTest.php @@ -37,9 +37,14 @@ class PropertyNormalizerTest extends TestCase private $serializer; protected function setUp() + { + $this->createNormalizer(); + } + + private function createNormalizer(array $defaultContext = array()) { $this->serializer = $this->getMockBuilder('Symfony\Component\Serializer\SerializerInterface')->getMock(); - $this->normalizer = new PropertyNormalizer(); + $this->normalizer = new PropertyNormalizer(null, null, null, null, null, $defaultContext); $this->normalizer->setSerializer($this->serializer); } @@ -121,7 +126,20 @@ public function testConstructorDenormalizeWithNullArgument() */ public function testCallbacks($callbacks, $value, $result, $message) { - $this->normalizer->setCallbacks($callbacks); + $this->doTestCallbacks($callbacks, $value, $result, $message); + } + + /** + * @dataProvider provideCallbacks + */ + public function testLegacyCallbacks($callbacks, $value, $result, $message) + { + $this->doTestCallbacks($callbacks, $value, $result, $message, true); + } + + private function doTestCallbacks($callbacks, $value, $result, $message, bool $legacy = false) + { + $legacy ? $this->normalizer->setCallbacks($callbacks) : $this->createNormalizer(array(PropertyNormalizer::CALLBACKS => $callbacks)); $obj = new PropertyConstructorDummy('', $value); @@ -137,7 +155,24 @@ public function testCallbacks($callbacks, $value, $result, $message) */ public function testUncallableCallbacks() { - $this->normalizer->setCallbacks(array('bar' => null)); + $this->doTestUncallableCallbacks(); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testLegacyUncallableCallbacks() + { + $this->doTestUncallableCallbacks(true); + } + + /** + * @expectedException \InvalidArgumentException + */ + private function doTestUncallableCallbacks(bool $legacy = false) + { + $callbacks = array('bar' => null); + $legacy ? $this->normalizer->setCallbacks($callbacks) : $this->createNormalizer(array(PropertyNormalizer::CALLBACKS => $callbacks)); $obj = new PropertyConstructorDummy('baz', 'quux'); @@ -146,7 +181,18 @@ public function testUncallableCallbacks() public function testIgnoredAttributes() { - $this->normalizer->setIgnoredAttributes(array('foo', 'bar', 'camelCase')); + $this->doTestIgnoredAttributes(); + } + + public function testLegacyIgnoredAttributes() + { + $this->doTestIgnoredAttributes(true); + } + + private function doTestIgnoredAttributes(bool $legacy = false) + { + $ignoredAttributes = array('foo', 'bar', 'camelCase'); + $legacy ? $this->normalizer->setIgnoredAttributes($ignoredAttributes) : $this->createNormalizer(array(PropertyNormalizer::IGNORED_ATTRIBUTES => $ignoredAttributes)); $obj = new PropertyDummy(); $obj->foo = 'foo'; @@ -324,9 +370,22 @@ public function provideCallbacks() */ public function testUnableToNormalizeCircularReference() { - $serializer = new Serializer(array($this->normalizer)); - $this->normalizer->setSerializer($serializer); - $this->normalizer->setCircularReferenceLimit(2); + $this->doTestUnableToNormalizeCircularReference(); + } + + /** + * @expectedException \Symfony\Component\Serializer\Exception\CircularReferenceException + */ + public function testLegacyUnableToNormalizeCircularReference() + { + $this->doTestUnableToNormalizeCircularReference(true); + } + + private function doTestUnableToNormalizeCircularReference(bool $legacy = false) + { + $legacy ? $this->normalizer->setCircularReferenceLimit(2) : $this->createNormalizer(array(PropertyNormalizer::CIRCULAR_REFERENCE_LIMIT => 2)); + $this->serializer = new Serializer(array($this->normalizer)); + $this->normalizer->setSerializer($this->serializer); $obj = new PropertyCircularReferenceDummy(); @@ -350,11 +409,23 @@ public function testSiblingReference() public function testCircularReferenceHandler() { - $serializer = new Serializer(array($this->normalizer)); - $this->normalizer->setSerializer($serializer); - $this->normalizer->setCircularReferenceHandler(function ($obj) { + $this->doTestCircularReferenceHandler(); + } + + public function testLegacyCircularReferenceHandler() + { + $this->doTestCircularReferenceHandler(true); + } + + private function doTestCircularReferenceHandler(bool $legacy = false) + { + $handler = function ($obj) { return \get_class($obj); - }); + }; + $legacy ? $this->normalizer->setCircularReferenceHandler($handler) : $this->createNormalizer(array(PropertyNormalizer::CIRCULAR_REFERENCE_HANDLER => $handler)); + + $this->serializer = new Serializer(array($this->normalizer)); + $this->normalizer->setSerializer($this->serializer); $obj = new PropertyCircularReferenceDummy(); pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy