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 = '
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: