feature #28709 [Serializer] Refactor and uniformize the config by introducing a default context (dunglas)

This PR was squashed before being merged into the 4.2-dev branch (closes #28709).

Discussion
----------

[Serializer] Refactor and uniformize the config by introducing a default context

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes <!-- don't forget to update src/**/CHANGELOG.md files -->
| BC breaks?    | no     <!-- see https://symfony.com/bc -->
| Deprecations? | yes <!-- don't forget to update UPGRADE-*.md and src/**/CHANGELOG.md files -->
| Tests pass?   | yes    <!-- please add some, will be required by reviewers -->
| Fixed tickets | n/a   <!-- #-prefixed issue number(s), if any -->
| License       | MIT
| Doc PR        | todo <!-- required for new features -->

This PR uniformizes how the Serializer's configuration is handled:

* As currently, configuration options can be set using the context (options that weren't configurable using the context have been refactored to leverage it)
* Normalizers and encoders' constructors now accept a "default context"
* All existing global configuration flags (constructor parameters) have been deprecated in favor of this new default context
* the stateless context is always tried first, then is the default context

Some examples:

```php
// Configuring groups globally
// Before: not possible
// After
$normalizer = new ObjectNormalizer(/* deps */, ['groups' => 'the_default_group']);

// Escaping Excel-like formulas in CSV files
// Before
$encoder = new CsvEncoder(',', '"', '\\', '.', true);
// After
$encoder = new CsvEncoder(['csv_escape_formulas' => true]);
$encoder->normalize($data, 'csv', ['csv_escape_formulas' => false]); // Override for this call only
```

Benefits:

* The DX is dramatically improved, configuration is always handled in similar way
* The serializer can be used in fully stateless way
* Every options can be configured globally
* Classes that had constructors with a lot of parameters (like `CsvEncoder`) are now much easier to use
* We'll be able to improve the documentation by adding a dictionary of all available context options for the whole component
* Everything can be configured the same way

TODO in subsequent PRs:

* Add a new option in framework bundle to configure the context globally
* Uniformize the constants name (sometimes the name if `FOO`, sometimes `FOO_KEY`)
* Fix the "bug" regarding the format configuration in `DateTimeNormalizer::denormalize()`  (see comments)
* Maybe: move `$defaultContext` as the first parameter (before required services?)
* Make `XmlEncoder` stateless

Commits
-------

52b186a210 [Serializer] Refactor and uniformize the config by introducing a default context
This commit is contained in:
Kévin Dunglas 2018-10-23 08:24:58 +02:00
commit 426cf81c16
No known key found for this signature in database
GPG Key ID: 4D04EBEF06AAF3A6
20 changed files with 854 additions and 258 deletions

View File

@ -4,6 +4,7 @@ CHANGELOG
4.2.0 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 * added a `skip_null_values` context option to not serialize properties with a `null` values
* `AbstractNormalizer::handleCircularReference` is now final and receives * `AbstractNormalizer::handleCircularReference` is now final and receives
two optional extra arguments: the format and the context two optional extra arguments: the format and the context
@ -24,6 +25,15 @@ CHANGELOG
and `ObjectNormalizer` constructor and `ObjectNormalizer` constructor
* added `MetadataAwareNameConverter` to configure the serialized name of properties through metadata * added `MetadataAwareNameConverter` to configure the serialized name of properties through metadata
* `YamlEncoder` now handles the `.yml` extension too * `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 4.1.0
----- -----

View File

@ -30,20 +30,34 @@ class CsvEncoder implements EncoderInterface, DecoderInterface
const ESCAPE_FORMULAS_KEY = 'csv_escape_formulas'; const ESCAPE_FORMULAS_KEY = 'csv_escape_formulas';
const AS_COLLECTION_KEY = 'as_collection'; const AS_COLLECTION_KEY = 'as_collection';
private $delimiter;
private $enclosure;
private $escapeChar;
private $keySeparator;
private $escapeFormulas;
private $formulasStartCharacters = array('=', '-', '+', '@'); 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; if (!\is_array($defaultContext)) {
$this->enclosure = $enclosure; @trigger_error('Passing configuration options directly to the constructor is deprecated since Symfony 4.2, use the default context instead.', E_USER_DEPRECATED);
$this->escapeChar = $escapeChar;
$this->keySeparator = $keySeparator; $defaultContext = array(
$this->escapeFormulas = $escapeFormulas; 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 @@ class CsvEncoder implements EncoderInterface, DecoderInterface
} }
} }
private function getCsvOptions(array $context) private function getCsvOptions(array $context): array
{ {
$delimiter = isset($context[self::DELIMITER_KEY]) ? $context[self::DELIMITER_KEY] : $this->delimiter; $delimiter = $context[self::DELIMITER_KEY] ?? $this->defaultContext[self::DELIMITER_KEY];
$enclosure = isset($context[self::ENCLOSURE_KEY]) ? $context[self::ENCLOSURE_KEY] : $this->enclosure; $enclosure = $context[self::ENCLOSURE_KEY] ?? $this->defaultContext[self::ENCLOSURE_KEY];
$escapeChar = isset($context[self::ESCAPE_CHAR_KEY]) ? $context[self::ESCAPE_CHAR_KEY] : $this->escapeChar; $escapeChar = $context[self::ESCAPE_CHAR_KEY] ?? $this->defaultContext[self::ESCAPE_CHAR_KEY];
$keySeparator = isset($context[self::KEY_SEPARATOR_KEY]) ? $context[self::KEY_SEPARATOR_KEY] : $this->keySeparator; $keySeparator = $context[self::KEY_SEPARATOR_KEY] ?? $this->defaultContext[self::KEY_SEPARATOR_KEY];
$headers = isset($context[self::HEADERS_KEY]) ? $context[self::HEADERS_KEY] : array(); $headers = $context[self::HEADERS_KEY] ?? $this->defaultContext[self::HEADERS_KEY];
$escapeFormulas = isset($context[self::ESCAPE_FORMULAS_KEY]) ? $context[self::ESCAPE_FORMULAS_KEY] : $this->escapeFormulas; $escapeFormulas = $context[self::ESCAPE_FORMULAS_KEY] ?? $this->defaultContext[self::ESCAPE_FORMULAS_KEY];
if (!\is_array($headers)) { 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))); throw new InvalidArgumentException(sprintf('The "%s" context variable must be an array or null, given "%s".', self::HEADERS_KEY, \gettype($headers)));

View File

@ -22,19 +22,41 @@ class JsonDecode implements DecoderInterface
{ {
protected $serializer; 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. * Constructs a new JsonDecode instance.
* *
* @param bool $associative True to return the result associative array, false for a nested stdClass hierarchy * @param array $defaultContext
* @param int $depth Specifies the recursion depth
*/ */
public function __construct(bool $associative = false, int $depth = 512) public function __construct($defaultContext = array(), int $depth = 512)
{ {
$this->associative = $associative; if (!\is_array($defaultContext)) {
$this->recursionDepth = $depth; @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 @@ class JsonDecode implements DecoderInterface
* The $context array is a simple key=>value array, with the following supported keys: * The $context array is a simple key=>value array, with the following supported keys:
* *
* json_decode_associative: boolean * 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 false, returns the object as nested stdClass
* If not specified, this method will use the default set in JsonDecode::__construct * If not specified, this method will use the default set in JsonDecode::__construct
* *
@ -56,7 +78,7 @@ class JsonDecode implements DecoderInterface
* If not specified, this method will use the default set in JsonDecode::__construct * If not specified, this method will use the default set in JsonDecode::__construct
* *
* json_decode_options: integer * json_decode_options: integer
* Specifies additional options as per documentation for json_decode. * Specifies additional options as per documentation for json_decode
* *
* @return mixed * @return mixed
* *
@ -66,11 +88,9 @@ class JsonDecode implements DecoderInterface
*/ */
public function decode($data, $format, array $context = array()) public function decode($data, $format, array $context = array())
{ {
$context = $this->resolveContext($context); $associative = $context[self::ASSOCIATIVE] ?? $this->defaultContext[self::ASSOCIATIVE];
$recursionDepth = $context[self::RECURSION_DEPTH] ?? $this->defaultContext[self::RECURSION_DEPTH];
$associative = $context['json_decode_associative']; $options = $context[self::OPTIONS] ?? $this->defaultContext[self::OPTIONS];
$recursionDepth = $context['json_decode_recursion_depth'];
$options = $context['json_decode_options'];
$decodedData = json_decode($data, $associative, $recursionDepth, $options); $decodedData = json_decode($data, $associative, $recursionDepth, $options);
@ -88,20 +108,4 @@ class JsonDecode implements DecoderInterface
{ {
return JsonEncoder::FORMAT === $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);
}
} }

View File

@ -20,11 +20,24 @@ use Symfony\Component\Serializer\Exception\NotEncodableValueException;
*/ */
class JsonEncode implements EncoderInterface 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 @@ class JsonEncode implements EncoderInterface
*/ */
public function encode($data, $format, array $context = array()) public function encode($data, $format, array $context = array())
{ {
$context = $this->resolveContext($context); $jsonEncodeOptions = $context[self::OPTIONS] ?? $this->defaultContext[self::OPTIONS];
$encodedJson = json_encode($data, $jsonEncodeOptions);
$encodedJson = json_encode($data, $context['json_encode_options']); if (JSON_ERROR_NONE !== json_last_error() && (false === $encodedJson || !($jsonEncodeOptions & JSON_PARTIAL_OUTPUT_ON_ERROR))) {
if (JSON_ERROR_NONE !== json_last_error() && (false === $encodedJson || !($context['json_encode_options'] & JSON_PARTIAL_OUTPUT_ON_ERROR))) {
throw new NotEncodableValueException(json_last_error_msg()); throw new NotEncodableValueException(json_last_error_msg());
} }
@ -52,14 +64,4 @@ class JsonEncode implements EncoderInterface
{ {
return JsonEncoder::FORMAT === $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);
}
} }

View File

@ -26,7 +26,7 @@ class JsonEncoder implements EncoderInterface, DecoderInterface
public function __construct(JsonEncode $encodingImpl = null, JsonDecode $decodingImpl = null) public function __construct(JsonEncode $encodingImpl = null, JsonDecode $decodingImpl = null)
{ {
$this->encodingImpl = $encodingImpl ?: new JsonEncode(); $this->encodingImpl = $encodingImpl ?: new JsonEncode();
$this->decodingImpl = $decodingImpl ?: new JsonDecode(true); $this->decodingImpl = $decodingImpl ?: new JsonDecode(array(JsonDecode::ASSOCIATIVE => true));
} }
/** /**

View File

@ -30,30 +30,64 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa
const FORMAT = 'xml'; 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 * @var \DOMDocument
*/ */
private $dom; private $dom;
private $format; private $format;
private $context; private $context;
private $rootNodeName = 'response';
private $loadOptions;
private $decoderIgnoredNodeTypes;
private $encoderIgnoredNodeTypes;
/** /**
* Construct new XmlEncoder and allow to change the root node element name. * @param array $defaultContext
*
* @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
*/ */
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; if (!\is_array($defaultContext)) {
$this->loadOptions = null !== $loadOptions ? $loadOptions : LIBXML_NONET | LIBXML_NOBLANKS; @trigger_error('Passing configuration options directly to the constructor is deprecated since Symfony 4.2, use the default context instead.', E_USER_DEPRECATED);
$this->decoderIgnoredNodeTypes = $decoderIgnoredNodeTypes;
$this->encoderIgnoredNodeTypes = $encoderIgnoredNodeTypes; $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 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa
*/ */
public function encode($data, $format, array $context = array()) 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) { 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->dom = $this->createDomDocument($context);
$this->format = $format; $this->format = $format;
@ -79,7 +115,7 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa
$this->appendNode($this->dom, $data, $xmlRootNodeName); $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 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa
libxml_clear_errors(); libxml_clear_errors();
$dom = new \DOMDocument(); $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_use_internal_errors($internalErrors);
libxml_disable_entity_loader($disableEntities); libxml_disable_entity_loader($disableEntities);
@ -108,11 +144,12 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa
} }
$rootNode = null; $rootNode = null;
$decoderIgnoredNodeTypes = $context[self::DECODER_IGNORED_NODE_TYPES] ?? $this->defaultContext[self::DECODER_IGNORED_NODE_TYPES];
foreach ($dom->childNodes as $child) { foreach ($dom->childNodes as $child) {
if (XML_DOCUMENT_TYPE_NODE === $child->nodeType) { if (XML_DOCUMENT_TYPE_NODE === $child->nodeType) {
throw new NotEncodableValueException('Document types are not allowed.'); 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; $rootNode = $child;
} }
} }
@ -169,21 +206,29 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa
/** /**
* Sets the root node name. * Sets the root node name.
* *
* @deprecated since Symfony 4.2
*
* @param string $name Root node name * @param string $name Root node name
*/ */
public function setRootNodeName($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. * Returns the root node name.
* *
* @deprecated since Symfony 4.2
*
* @return string * @return string
*/ */
public function getRootNodeName() 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 final protected function appendXMLString(\DOMNode $node, string $val): bool
@ -291,7 +336,7 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa
} }
$data = array(); $data = array();
$typeCastAttributes = $this->resolveXmlTypeCastAttributes($context); $typeCastAttributes = (bool) ($context[self::TYPE_CASE_ATTRIBUTES] ?? $this->defaultContext[self::TYPE_CASE_ATTRIBUTES]);
foreach ($node->attributes as $attr) { foreach ($node->attributes as $attr) {
if (!is_numeric($attr->nodeValue) || !$typeCastAttributes) { if (!is_numeric($attr->nodeValue) || !$typeCastAttributes) {
@ -328,9 +373,9 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa
} }
$value = array(); $value = array();
$decoderIgnoredNodeTypes = $context[self::DECODER_IGNORED_NODE_TYPES] ?? $this->defaultContext[self::DECODER_IGNORED_NODE_TYPES];
foreach ($node->childNodes as $subnode) { foreach ($node->childNodes as $subnode) {
if (\in_array($subnode->nodeType, $this->decoderIgnoredNodeTypes, true)) { if (\in_array($subnode->nodeType, $decoderIgnoredNodeTypes, true)) {
continue; continue;
} }
@ -347,8 +392,9 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa
} }
} }
$asCollection = $context[self::AS_COLLECTION] ?? $this->defaultContext[self::AS_COLLECTION];
foreach ($value as $key => $val) { 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); $value[$key] = current($val);
} }
} }
@ -366,6 +412,8 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa
private function buildXml(\DOMNode $parentNode, $data, string $xmlRootNodeName = null): bool private function buildXml(\DOMNode $parentNode, $data, string $xmlRootNodeName = null): bool
{ {
$append = true; $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))) { if (\is_array($data) || ($data instanceof \Traversable && !$this->serializer->supportsNormalization($data, $this->format))) {
foreach ($data as $key => $data) { foreach ($data as $key => $data) {
@ -378,7 +426,7 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa
} elseif ('#' === $key) { } elseif ('#' === $key) {
$append = $this->selectNodeType($parentNode, $data); $append = $this->selectNodeType($parentNode, $data);
} elseif ('#comment' === $key) { } 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); $append = $this->appendComment($parentNode, $data);
} }
} elseif (\is_array($data) && false === is_numeric($key)) { } elseif (\is_array($data) && false === is_numeric($key)) {
@ -397,7 +445,7 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa
} }
} elseif (is_numeric($key) || !$this->isElementNameValid($key)) { } elseif (is_numeric($key) || !$this->isElementNameValid($key)) {
$append = $this->appendNode($parentNode, $data, 'item', $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); $append = $this->appendNode($parentNode, $data, $key);
} }
} }
@ -487,26 +535,6 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa
return true; 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. * Create a DOM document, taking serializer options into account.
*/ */
@ -517,17 +545,17 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa
// Set an attribute on the DOM document specifying, as part of the XML declaration, // Set an attribute on the DOM document specifying, as part of the XML declaration,
$xmlOptions = array( $xmlOptions = array(
// nicely formats output with indentation and extra space // nicely formats output with indentation and extra space
'xml_format_output' => 'formatOutput', self::FORMAT_OUTPUT => 'formatOutput',
// the version number of the document // the version number of the document
'xml_version' => 'xmlVersion', self::VERSION => 'xmlVersion',
// the encoding of the document // the encoding of the document
'xml_encoding' => 'encoding', self::ENCODING => 'encoding',
// whether the document is standalone // whether the document is standalone
'xml_standalone' => 'xmlStandalone', self::STANDALONE => 'xmlStandalone',
); );
foreach ($xmlOptions as $xmlOption => $documentProperty) { foreach ($xmlOptions as $xmlOption => $documentProperty) {
if (isset($context[$xmlOption])) { if ($contextOption = $context[$xmlOption] ?? $this->defaultContext[$xmlOption] ?? false) {
$document->$documentProperty = $context[$xmlOption]; $document->$documentProperty = $contextOption;
} }
} }

View File

@ -38,14 +38,30 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn
const ATTRIBUTES = 'attributes'; const ATTRIBUTES = 'attributes';
const ALLOW_EXTRA_ATTRIBUTES = 'allow_extra_attributes'; const ALLOW_EXTRA_ATTRIBUTES = 'allow_extra_attributes';
const DEFAULT_CONSTRUCTOR_ARGUMENTS = 'default_constructor_arguments'; 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; protected $circularReferenceLimit = 1;
/** /**
* @var callable * @deprecated since Symfony 4.2
*
* @var callable|null
*/ */
protected $circularReferenceHandler; protected $circularReferenceHandler;
@ -60,31 +76,42 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn
protected $nameConverter; protected $nameConverter;
/** /**
* @var array * @deprecated since Symfony 4.2
*/ */
protected $callbacks = array(); protected $callbacks = array();
/** /**
* @var array * @deprecated since Symfony 4.2
*/ */
protected $ignoredAttributes = array(); protected $ignoredAttributes = array();
/** /**
* @var array * @deprecated since Symfony 4.2
*/ */
protected $camelizedAttributes = array(); protected $camelizedAttributes = array();
/** /**
* Sets the {@link ClassMetadataFactoryInterface} to use. * 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->classMetadataFactory = $classMetadataFactory;
$this->nameConverter = $nameConverter; $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 * @param int $circularReferenceLimit Limit of iterations for the same object
* *
@ -92,13 +119,17 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn
*/ */
public function setCircularReferenceLimit($circularReferenceLimit) 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; return $this;
} }
/** /**
* Set circular reference handler. * Sets circular reference handler.
*
* @deprecated since Symfony 4.2
* *
* @param callable $circularReferenceHandler * @param callable $circularReferenceHandler
* *
@ -106,13 +137,17 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn
*/ */
public function setCircularReferenceHandler(callable $circularReferenceHandler) 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; return $this;
} }
/** /**
* Set normalization callbacks. * Sets normalization callbacks.
*
* @deprecated since Symfony 4.2
* *
* @param callable[] $callbacks Help normalize the result * @param callable[] $callbacks Help normalize the result
* *
@ -122,24 +157,30 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn
*/ */
public function setCallbacks(array $callbacks) 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) { foreach ($callbacks as $attribute => $callback) {
if (!\is_callable($callback)) { if (!\is_callable($callback)) {
throw new InvalidArgumentException(sprintf('The given callback for attribute "%s" is not callable.', $attribute)); 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; return $this;
} }
/** /**
* Set ignored attributes for normalization and denormalization. * Sets ignored attributes for normalization and denormalization.
*
* @deprecated since Symfony 4.2
* *
* @return self * @return self
*/ */
public function setIgnoredAttributes(array $ignoredAttributes) 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; return $this;
} }
@ -166,16 +207,17 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn
{ {
$objectHash = spl_object_hash($object); $objectHash = spl_object_hash($object);
if (isset($context[static::CIRCULAR_REFERENCE_LIMIT][$objectHash])) { $circularReferenceLimit = $context[self::CIRCULAR_REFERENCE_LIMIT] ?? $this->defaultContext[self::CIRCULAR_REFERENCE_LIMIT] ?? $this->circularReferenceLimit;
if ($context[static::CIRCULAR_REFERENCE_LIMIT][$objectHash] >= $this->circularReferenceLimit) { if (isset($context[self::CIRCULAR_REFERENCE_LIMIT_COUNTERS][$objectHash])) {
unset($context[static::CIRCULAR_REFERENCE_LIMIT][$objectHash]); if ($context[self::CIRCULAR_REFERENCE_LIMIT_COUNTERS][$objectHash] >= $circularReferenceLimit) {
unset($context[self::CIRCULAR_REFERENCE_LIMIT_COUNTERS][$objectHash]);
return true; return true;
} }
++$context[static::CIRCULAR_REFERENCE_LIMIT][$objectHash]; ++$context[self::CIRCULAR_REFERENCE_LIMIT_COUNTERS][$objectHash];
} else { } else {
$context[static::CIRCULAR_REFERENCE_LIMIT][$objectHash] = 1; $context[self::CIRCULAR_REFERENCE_LIMIT_COUNTERS][$objectHash] = 1;
} }
return false; return false;
@ -205,8 +247,9 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn
$format = \func_num_args() > 1 ? func_get_arg(1) : null; $format = \func_num_args() > 1 ? func_get_arg(1) : null;
$context = \func_num_args() > 2 ? func_get_arg(2) : array(); $context = \func_num_args() > 2 ? func_get_arg(2) : array();
if ($this->circularReferenceHandler) { $circularReferenceHandler = $context[self::CIRCULAR_REFERENCE_HANDLER] ?? $this->defaultContext[self::CIRCULAR_REFERENCE_HANDLER] ?? $this->circularReferenceHandler;
return \call_user_func($this->circularReferenceHandler, $object, $format, $context); 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)); 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 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn
*/ */
protected function getAllowedAttributes($classOrObject, array $context, $attributesAsString = false) protected function getAllowedAttributes($classOrObject, array $context, $attributesAsString = false)
{ {
$allowExtraAttributes = $context[self::ALLOW_EXTRA_ATTRIBUTES] ?? $this->defaultContext[self::ALLOW_EXTRA_ATTRIBUTES];
if (!$this->classMetadataFactory) { if (!$this->classMetadataFactory) {
if (isset($context[static::ALLOW_EXTRA_ATTRIBUTES]) && !$context[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.', static::ALLOW_EXTRA_ATTRIBUTES)); 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; return false;
} }
$groups = false; $tmpGroups = $context[self::GROUPS] ?? $this->defaultContext[self::GROUPS] ?? null;
if (isset($context[static::GROUPS]) && (\is_array($context[static::GROUPS]) || is_scalar($context[static::GROUPS]))) { $groups = (\is_array($tmpGroups) || is_scalar($tmpGroups)) ? (array) $tmpGroups : false;
$groups = (array) $context[static::GROUPS]; if (false === $groups && $allowExtraAttributes) {
} elseif (!isset($context[static::ALLOW_EXTRA_ATTRIBUTES]) || $context[static::ALLOW_EXTRA_ATTRIBUTES]) {
return false; return false;
} }
@ -267,17 +310,19 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn
*/ */
protected function isAllowedAttribute($classOrObject, $attribute, $format = null, array $context = array()) 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; return false;
} }
if (isset($context[self::ATTRIBUTES][$attribute])) { $attributes = $context[self::ATTRIBUTES] ?? $this->defaultContext[self::ATTRIBUTES] ?? null;
if (isset($attributes[$attribute])) {
// Nested attributes // Nested attributes
return true; return true;
} }
if (isset($context[self::ATTRIBUTES]) && \is_array($context[self::ATTRIBUTES])) { if (\is_array($attributes)) {
return \in_array($attribute, $context[self::ATTRIBUTES], true); return \in_array($attribute, $attributes, true);
} }
return true; return true;
@ -335,8 +380,8 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn
*/ */
protected function instantiateObject(array &$data, $class, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes, string $format = null) 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)) { if (null !== $object = $this->extractObjectToPopulate($class, $context, self::OBJECT_TO_POPULATE)) {
unset($context[static::OBJECT_TO_POPULATE]); unset($context[self::OBJECT_TO_POPULATE]);
return $object; return $object;
} }
@ -371,7 +416,7 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn
try { try {
if (null !== $constructorParameter->getClass()) { if (null !== $constructorParameter->getClass()) {
if (!$this->serializer instanceof DenormalizerInterface) { 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(); $parameterClass = $constructorParameter->getClass()->getName();
$parameterData = $this->serializer->denormalize($parameterData, $parameterClass, $format, $this->createChildContext($context, $paramName)); $parameterData = $this->serializer->denormalize($parameterData, $parameterClass, $format, $this->createChildContext($context, $paramName));
@ -388,8 +433,8 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn
// Don't run set for a parameter passed to the constructor // Don't run set for a parameter passed to the constructor
$params[] = $parameterData; $params[] = $parameterData;
unset($data[$key]); unset($data[$key]);
} elseif (isset($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[] = $context[static::DEFAULT_CONSTRUCTOR_ARGUMENTS][$class][$key]; $params[] = $param;
} elseif ($constructorParameter->isDefaultValueAvailable()) { } elseif ($constructorParameter->isDefaultValueAvailable()) {
$params[] = $constructorParameter->getDefaultValue(); $params[] = $constructorParameter->getDefaultValue();
} else { } else {

View File

@ -36,12 +36,16 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer
const DEPTH_KEY_PATTERN = 'depth_%s::%s'; const DEPTH_KEY_PATTERN = 'depth_%s::%s';
const DISABLE_TYPE_ENFORCEMENT = 'disable_type_enforcement'; const DISABLE_TYPE_ENFORCEMENT = 'disable_type_enforcement';
const SKIP_NULL_VALUES = 'skip_null_values'; 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 $propertyTypeExtractor;
private $typesCache = array(); private $typesCache = array();
private $attributesCache = array(); private $attributesCache = array();
/** /**
* @deprecated since Symfony 4.2
*
* @var callable|null * @var callable|null
*/ */
private $maxDepthHandler; private $maxDepthHandler;
@ -52,9 +56,10 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer
*/ */
protected $classDiscriminatorResolver; 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; $this->propertyTypeExtractor = $propertyTypeExtractor;
@ -91,20 +96,25 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer
$attributes = $this->getAttributes($object, $format, $context); $attributes = $this->getAttributes($object, $format, $context);
$class = $this->objectClassResolver ? \call_user_func($this->objectClassResolver, $object) : \get_class($object); $class = $this->objectClassResolver ? \call_user_func($this->objectClassResolver, $object) : \get_class($object);
$attributesMetadata = $this->classMetadataFactory ? $this->classMetadataFactory->getMetadataFor($class)->getAttributesMetadata() : null; $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) { foreach ($attributes as $attribute) {
$maxDepthReached = false; $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; continue;
} }
$attributeValue = $this->getAttributeValue($object, $attribute, $format, $context); $attributeValue = $this->getAttributeValue($object, $attribute, $format, $context);
if ($maxDepthReached) { 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)) { if (null !== $attributeValue && !is_scalar($attributeValue)) {
@ -175,7 +185,7 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer
return $allowedAttributes; return $allowedAttributes;
} }
if (isset($context['attributes'])) { if ($context[self::ATTRIBUTES] ?? $this->defaultContext[self::ATTRIBUTES] ?? false) {
return $this->extractAttributes($object, $format, $context); return $this->extractAttributes($object, $format, $context);
} }
@ -217,9 +227,13 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer
/** /**
* Sets a handler function that will be called when the max depth is reached. * 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 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; $this->maxDepthHandler = $handler;
} }
@ -253,7 +267,7 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer
} }
if ((false !== $allowedAttributes && !\in_array($attribute, $allowedAttributes)) || !$this->isAllowedAttribute($class, $attribute, $format, $context)) { 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; $extraAttributes[] = $attribute;
} }
@ -354,7 +368,7 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer
} }
} }
if (!empty($context[self::DISABLE_TYPE_ENFORCEMENT])) { if ($context[self::DISABLE_TYPE_ENFORCEMENT] ?? $this->defaultContext[self::DISABLE_TYPE_ENFORCEMENT] ?? false) {
return $data; return $data;
} }
@ -405,7 +419,7 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer
*/ */
private function updateData(array $data, string $attribute, $attributeValue, string $class, ?string $format, array $context): array 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; return $data;
} }
@ -425,16 +439,16 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer
*/ */
private function isMaxDepthReached(array $attributesMetadata, string $class, string $attribute, array &$context): bool 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 ( if (
!isset($context[static::ENABLE_MAX_DEPTH]) || !$enableMaxDepth ||
!$context[static::ENABLE_MAX_DEPTH] ||
!isset($attributesMetadata[$attribute]) || !isset($attributesMetadata[$attribute]) ||
null === $maxDepth = $attributesMetadata[$attribute]->getMaxDepth() null === $maxDepth = $attributesMetadata[$attribute]->getMaxDepth()
) { ) {
return false; return false;
} }
$key = sprintf(static::DEPTH_KEY_PATTERN, $class, $attribute); $key = sprintf(self::DEPTH_KEY_PATTERN, $class, $attribute);
if (!isset($context[$key])) { if (!isset($context[$key])) {
$context[$key] = 1; $context[$key] = 1;
@ -457,6 +471,11 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer
*/ */
private function getCacheKey(?string $format, array $context) 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 { try {
return md5($format.serialize($context)); return md5($format.serialize($context));
} catch (\Exception $exception) { } catch (\Exception $exception) {

View File

@ -24,6 +24,18 @@ use Symfony\Component\Validator\ConstraintViolationListInterface;
*/ */
class ConstraintViolationListNormalizer implements NormalizerInterface, CacheableSupportsMethodInterface 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} * {@inheritdoc}
*/ */
@ -49,17 +61,17 @@ class ConstraintViolationListNormalizer implements NormalizerInterface, Cacheabl
} }
$result = array( $result = array(
'type' => $context['type'] ?? 'https://symfony.com/errors/validation', 'type' => $context[self::TYPE] ?? $this->defaultContext[self::TYPE] ?? 'https://symfony.com/errors/validation',
'title' => $context['title'] ?? 'Validation Failed', 'title' => $context[self::TITLE] ?? $this->defaultContext[self::TITLE] ?? 'Validation Failed',
); );
if (isset($context['status'])) { if (null !== $status = ($context[self::STATUS] ?? $this->defaultContext[self::STATUS] ?? null)) {
$result['status'] = $context['status']; $result['status'] = $status;
} }
if ($messages) { if ($messages) {
$result['detail'] = implode("\n", $messages); $result['detail'] = implode("\n", $messages);
} }
if (isset($context['instance'])) { if (null !== $instance = ($context[self::INSTANCE] ?? $this->defaultContext[self::INSTANCE] ?? null)) {
$result['instance'] = $context['instance']; $result['instance'] = $instance;
} }
return $result + array('violations' => $violations); return $result + array('violations' => $violations);

View File

@ -24,11 +24,22 @@ class DateIntervalNormalizer implements NormalizerInterface, DenormalizerInterfa
{ {
const FORMAT_KEY = 'dateinterval_format'; 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 @@ class DateIntervalNormalizer implements NormalizerInterface, DenormalizerInterfa
throw new InvalidArgumentException('The object must be an instance of "\DateInterval".'); 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($context[self::FORMAT_KEY] ?? $this->defaultContext[self::FORMAT_KEY]);
return $object->format($dateIntervalFormat);
} }
/** /**
@ -79,7 +88,7 @@ class DateIntervalNormalizer implements NormalizerInterface, DenormalizerInterfa
throw new UnexpectedValueException('Expected a valid ISO 8601 interval string.'); 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).'$/'; $valuePattern = '/^'.preg_replace('/%([yYmMdDhHiIsSwW])(\w)/', '(?P<$1>\d+)$2', $dateIntervalFormat).'$/';
if (!preg_match($valuePattern, $data)) { if (!preg_match($valuePattern, $data)) {

View File

@ -25,8 +25,7 @@ class DateTimeNormalizer implements NormalizerInterface, DenormalizerInterface,
const FORMAT_KEY = 'datetime_format'; const FORMAT_KEY = 'datetime_format';
const TIMEZONE_KEY = 'datetime_timezone'; const TIMEZONE_KEY = 'datetime_timezone';
private $format; private $defaultContext;
private $timezone;
private static $supportedTypes = array( private static $supportedTypes = array(
\DateTimeInterface::class => true, \DateTimeInterface::class => true,
@ -34,10 +33,24 @@ class DateTimeNormalizer implements NormalizerInterface, DenormalizerInterface,
\DateTime::class => true, \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->defaultContext = array(
$this->timezone = $timezone; 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 @@ class DateTimeNormalizer implements NormalizerInterface, DenormalizerInterface,
throw new InvalidArgumentException('The object must implement the "\DateTimeInterface".'); 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); $timezone = $this->getTimezone($context);
if (null !== $timezone) { if (null !== $timezone) {
$object = (new \DateTimeImmutable('@'.$object->getTimestamp()))->setTimezone($timezone); $object = (new \DateTimeImmutable('@'.$object->getTimestamp()))->setTimezone($timezone);
} }
return $object->format($format); return $object->format($dateTimeFormat);
} }
/** /**
@ -76,7 +89,7 @@ class DateTimeNormalizer implements NormalizerInterface, DenormalizerInterface,
*/ */
public function denormalize($data, $class, $format = null, array $context = array()) 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); $timezone = $this->getTimezone($context);
if ('' === $data || null === $data) { if ('' === $data || null === $data) {
@ -142,8 +155,7 @@ class DateTimeNormalizer implements NormalizerInterface, DenormalizerInterface,
private function getTimezone(array $context) 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) { if (null === $dateTimeZone) {
return null; return null;
} }

View File

@ -30,13 +30,13 @@ class ObjectNormalizer extends AbstractObjectNormalizer
{ {
protected $propertyAccessor; 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)) { if (!\class_exists(PropertyAccess::class)) {
throw new LogicException('The ObjectNormalizer class requires the "PropertyAccess" component. Install "symfony/property-access" to use it.'); 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(); $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
} }

View File

@ -100,7 +100,26 @@ CSV
public function testEncodeCustomSettings() 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')); $value = array('a' => 'he\'llo', 'c' => array('d' => 'foo'));
@ -175,7 +194,21 @@ CSV;
public function testEncodeFormulas() 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' $this->assertSame(<<<'CSV'
0 0
@ -378,7 +411,26 @@ CSV
public function testDecodeCustomSettings() 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'))); $expected = array(array('a' => 'hell\'o', 'bar' => array('baz' => 'b')));
$this->assertEquals($expected, $this->encoder->decode(<<<'CSV' $this->assertEquals($expected, $this->encoder->decode(<<<'CSV'

View File

@ -47,6 +47,9 @@ class XmlEncoderTest extends TestCase
$this->assertEquals($expected, $this->encoder->encode($obj, 'xml')); $this->assertEquals($expected, $this->encoder->encode($obj, 'xml'));
} }
/**
* @group legacy
*/
public function testSetRootNodeName() public function testSetRootNodeName()
{ {
$obj = new ScalarDummy(); $obj = new ScalarDummy();
@ -543,6 +546,16 @@ XML;
} }
public function testDecodePreserveComments() public function testDecodePreserveComments()
{
$this->doTestDecodePreserveComments();
}
public function testLegacyDecodePreserveComments()
{
$this->doTestDecodePreserveComments(true);
}
private function doTestDecodePreserveComments(bool $legacy = false)
{ {
$source = <<<'XML' $source = <<<'XML'
<?xml version="1.0"?> <?xml version="1.0"?>
@ -559,7 +572,14 @@ XML;
</people> </people>
XML; 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())); $serializer = new Serializer(array(new CustomNormalizer()), array('xml' => new XmlEncoder()));
$this->encoder->setSerializer($serializer); $this->encoder->setSerializer($serializer);
@ -573,7 +593,21 @@ XML;
public function testDecodeAlwaysAsCollection() 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())); $serializer = new Serializer(array(new CustomNormalizer()), array('xml' => new XmlEncoder()));
$this->encoder->setSerializer($serializer); $this->encoder->setSerializer($serializer);
@ -773,9 +807,26 @@ XML;
$this->assertEquals($expected, $this->encoder->encode($data, 'xml')); $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 = '<response/>'; $expected = '<response/>';
@ -784,7 +835,24 @@ XML;
public function testEncodeWithoutComment() 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' $expected = <<<'XML'
<?xml version="1.0"?> <?xml version="1.0"?>

View File

@ -58,7 +58,21 @@ class DateIntervalNormalizerTest extends TestCase
*/ */
public function testNormalizeUsingFormatPassedInConstructor($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 @@ class DateIntervalNormalizerTest extends TestCase
*/ */
public function testDenormalizeUsingFormatPassedInConstructor($format, $input, $output) 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));
} }
/** /**

View File

@ -49,12 +49,37 @@ class DateTimeNormalizerTest extends TestCase
public function testNormalizeUsingFormatPassedInConstructor() 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() 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-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')))); $this->assertSame('2016-12-01T09:00:00+09:00', $normalizer->normalize(new \DateTime('2016/12/01', new \DateTimeZone('UTC'))));
@ -103,10 +128,20 @@ class DateTimeNormalizerTest extends TestCase
} }
public function testDenormalizeUsingTimezonePassedInConstructor() public function testDenormalizeUsingTimezonePassedInConstructor()
{
$this->doTestDenormalizeUsingTimezonePassedInConstructor();
}
public function testLegacyDenormalizeUsingTimezonePassedInConstructor()
{
$this->doTestDenormalizeUsingTimezonePassedInConstructor(true);
}
private function doTestDenormalizeUsingTimezonePassedInConstructor(bool $legacy = false)
{ {
$timezone = new \DateTimeZone('Japan'); $timezone = new \DateTimeZone('Japan');
$expected = new \DateTime('2016/12/01 17:35:00', $timezone); $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( $this->assertEquals($expected, $normalizer->denormalize('2016.12.01 17:35:00', \DateTime::class, null, array(
DateTimeNormalizer::FORMAT_KEY => 'Y.m.d H:i:s', DateTimeNormalizer::FORMAT_KEY => 'Y.m.d H:i:s',

View File

@ -37,9 +37,14 @@ class GetSetMethodNormalizerTest extends TestCase
private $serializer; private $serializer;
protected function setUp() protected function setUp()
{
$this->createNormalizer();
}
private function createNormalizer(array $defaultContext = array())
{ {
$this->serializer = $this->getMockBuilder(__NAMESPACE__.'\SerializerNormalizer')->getMock(); $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); $this->normalizer->setSerializer($this->serializer);
} }
@ -285,10 +290,22 @@ class GetSetMethodNormalizerTest extends TestCase
*/ */
public function testCallbacks($callbacks, $value, $result, $message) 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(GetSetMethodNormalizer::CALLBACKS => $callbacks));
$obj = new GetConstructorDummy('', $value, true); $obj = new GetConstructorDummy('', $value, true);
$this->assertEquals( $this->assertEquals(
$result, $result,
$this->normalizer->normalize($obj, 'any'), $this->normalizer->normalize($obj, 'any'),
@ -301,7 +318,21 @@ class GetSetMethodNormalizerTest extends TestCase
*/ */
public function testUncallableCallbacks() 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); $obj = new GetConstructorDummy('baz', 'quux', true);
@ -310,7 +341,18 @@ class GetSetMethodNormalizerTest extends TestCase
public function testIgnoredAttributes() 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 = new GetSetDummy();
$obj->setFoo('foo'); $obj->setFoo('foo');
@ -404,12 +446,24 @@ class GetSetMethodNormalizerTest extends TestCase
*/ */
public function testUnableToNormalizeCircularReference() public function testUnableToNormalizeCircularReference()
{ {
$serializer = new Serializer(array($this->normalizer)); $this->doTestUnableToNormalizeCircularReference();
$this->normalizer->setSerializer($serializer); }
$this->normalizer->setCircularReferenceLimit(2);
/**
* @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(); $obj = new CircularReferenceDummy();
$this->normalizer->normalize($obj); $this->normalizer->normalize($obj);
} }
@ -430,11 +484,23 @@ class GetSetMethodNormalizerTest extends TestCase
public function testCircularReferenceHandler() public function testCircularReferenceHandler()
{ {
$serializer = new Serializer(array($this->normalizer)); $this->doTestCircularReferenceHandler();
$this->normalizer->setSerializer($serializer); }
$this->normalizer->setCircularReferenceHandler(function ($obj) {
public function testLegacyCircularReferenceHandler()
{
$this->doTestCircularReferenceHandler(true);
}
private function doTestCircularReferenceHandler(bool $legacy = false)
{
$handler = function ($obj) {
return \get_class($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(); $obj = new CircularReferenceDummy();

View File

@ -33,9 +33,14 @@ class JsonSerializableNormalizerTest extends TestCase
private $serializer; private $serializer;
protected function setUp() protected function setUp()
{
$this->createNormalizer();
}
private function createNormalizer(array $defaultContext = array())
{ {
$this->serializer = $this->getMockBuilder(JsonSerializerNormalizer::class)->getMock(); $this->serializer = $this->getMockBuilder(JsonSerializerNormalizer::class)->getMock();
$this->normalizer = new JsonSerializableNormalizer(); $this->normalizer = new JsonSerializableNormalizer(null, null, $defaultContext);
$this->normalizer->setSerializer($this->serializer); $this->normalizer->setSerializer($this->serializer);
} }
@ -65,7 +70,23 @@ class JsonSerializableNormalizerTest extends TestCase
*/ */
public function testCircularNormalize() 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 $this->serializer
->expects($this->once()) ->expects($this->once())

View File

@ -17,6 +17,7 @@ use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
use Symfony\Component\PropertyInfo\PropertyInfoExtractor; use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; 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\Mapping\Loader\AnnotationLoader;
use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface; use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface;
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
@ -48,9 +49,14 @@ class ObjectNormalizerTest extends TestCase
private $serializer; private $serializer;
protected function setUp() protected function setUp()
{
$this->createNormalizer();
}
private function createNormalizer(array $defaultContext = array(), ClassMetadataFactoryInterface $classMetadataFactory = null)
{ {
$this->serializer = $this->getMockBuilder(__NAMESPACE__.'\ObjectSerializerNormalizer')->getMock(); $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); $this->normalizer->setSerializer($this->serializer);
} }
@ -393,8 +399,20 @@ class ObjectNormalizerTest extends TestCase
*/ */
public function testCallbacks($callbacks, $value, $result, $message) 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); $obj = new ObjectConstructorDummy('', $value, true);
$this->assertEquals( $this->assertEquals(
@ -409,7 +427,21 @@ class ObjectNormalizerTest extends TestCase
*/ */
public function testUncallableCallbacks() 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); $obj = new ObjectConstructorDummy('baz', 'quux', true);
@ -418,7 +450,18 @@ class ObjectNormalizerTest extends TestCase
public function testIgnoredAttributes() 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 = new ObjectDummy();
$obj->setFoo('foo'); $obj->setFoo('foo');
@ -433,7 +476,18 @@ class ObjectNormalizerTest extends TestCase
public function testIgnoredAttributesDenormalize() 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 = new ObjectDummy();
$obj->setFoo('foo'); $obj->setFoo('foo');
@ -466,7 +520,7 @@ class ObjectNormalizerTest extends TestCase
$this->assertInstanceOf(ObjectConstructorDummy::class, $object); $this->assertInstanceOf(ObjectConstructorDummy::class, $object);
$this->assertSame('bar', $attributeName); $this->assertSame('bar', $attributeName);
$this->assertSame('any', $format); $this->assertSame('any', $format);
$this->assertArrayHasKey('circular_reference_limit', $context); $this->assertArrayHasKey('circular_reference_limit_counters', $context);
}, },
), ),
'baz', 'baz',
@ -532,9 +586,22 @@ class ObjectNormalizerTest extends TestCase
*/ */
public function testUnableToNormalizeCircularReference() 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)); $serializer = new Serializer(array($this->normalizer));
$this->normalizer->setSerializer($serializer); $this->normalizer->setSerializer($serializer);
$this->normalizer->setCircularReferenceLimit(2);
$obj = new CircularReferenceDummy(); $obj = new CircularReferenceDummy();
@ -558,27 +625,41 @@ class ObjectNormalizerTest extends TestCase
public function testCircularReferenceHandler() public function testCircularReferenceHandler()
{ {
$serializer = new Serializer(array($this->normalizer)); $this->doTestCircularReferenceHandler();
$this->normalizer->setSerializer($serializer); }
$this->normalizer->setCircularReferenceHandler(function ($obj) {
public function testLegacyCircularReferenceHandler()
{
$this->doTestCircularReferenceHandler(true);
}
private function doTestCircularReferenceHandler(bool $legacy = false)
{
$this->createNormalizerWithCircularReferenceHandler(function ($obj) {
return \get_class($obj); return \get_class($obj);
}); }, $legacy);
$obj = new CircularReferenceDummy(); $obj = new CircularReferenceDummy();
$expected = array('me' => 'Symfony\Component\Serializer\Tests\Fixtures\CircularReferenceDummy'); $expected = array('me' => 'Symfony\Component\Serializer\Tests\Fixtures\CircularReferenceDummy');
$this->assertEquals($expected, $this->normalizer->normalize($obj)); $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->assertInstanceOf(CircularReferenceDummy::class, $obj);
$this->assertSame('test', $format); $this->assertSame('test', $format);
$this->arrayHasKey('foo', $context); $this->arrayHasKey('foo', $context);
return \get_class($obj); return \get_class($obj);
}); }, $legacy);
$this->assertEquals($expected, $this->normalizer->normalize($obj, 'test', array('foo' => 'bar'))); $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() public function testDenormalizeNonExistingAttribute()
{ {
$this->assertEquals( $this->assertEquals(
@ -620,11 +701,16 @@ class ObjectNormalizerTest extends TestCase
public function testMaxDepth() public function testMaxDepth()
{ {
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); $this->doTestMaxDepth();
$this->normalizer = new ObjectNormalizer($classMetadataFactory); }
$serializer = new Serializer(array($this->normalizer));
$this->normalizer->setSerializer($serializer);
public function testLegacyMaxDepth()
{
$this->doTestMaxDepth(true);
}
private function doTestMaxDepth(bool $legacy = false)
{
$level1 = new MaxDepthDummy(); $level1 = new MaxDepthDummy();
$level1->foo = 'level1'; $level1->foo = 'level1';
@ -636,7 +722,8 @@ class ObjectNormalizerTest extends TestCase
$level3->foo = 'level3'; $level3->foo = 'level3';
$level2->child = $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( $expected = array(
'bar' => null, 'bar' => null,
@ -667,14 +754,13 @@ class ObjectNormalizerTest extends TestCase
), ),
); );
$this->normalizer->setMaxDepthHandler(function ($obj) { $this->createNormalizerWithMaxDepthHandler(function () {
return 'handler'; return 'handler';
}); }, $legacy);
$result = $this->serializer->normalize($level1, null, array(ObjectNormalizer::ENABLE_MAX_DEPTH => true));
$result = $serializer->normalize($level1, null, array(ObjectNormalizer::ENABLE_MAX_DEPTH => true));
$this->assertEquals($expected, $result); $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->assertSame('level3', $object);
$this->assertInstanceOf(MaxDepthDummy::class, $parentObject); $this->assertInstanceOf(MaxDepthDummy::class, $parentObject);
$this->assertSame('foo', $attributeName); $this->assertSame('foo', $attributeName);
@ -682,9 +768,23 @@ class ObjectNormalizerTest extends TestCase
$this->assertArrayHasKey(ObjectNormalizer::ENABLE_MAX_DEPTH, $context); $this->assertArrayHasKey(ObjectNormalizer::ENABLE_MAX_DEPTH, $context);
return 'handler'; 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);
} }
/** /**

View File

@ -37,9 +37,14 @@ class PropertyNormalizerTest extends TestCase
private $serializer; private $serializer;
protected function setUp() protected function setUp()
{
$this->createNormalizer();
}
private function createNormalizer(array $defaultContext = array())
{ {
$this->serializer = $this->getMockBuilder('Symfony\Component\Serializer\SerializerInterface')->getMock(); $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); $this->normalizer->setSerializer($this->serializer);
} }
@ -121,7 +126,20 @@ class PropertyNormalizerTest extends TestCase
*/ */
public function testCallbacks($callbacks, $value, $result, $message) 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); $obj = new PropertyConstructorDummy('', $value);
@ -137,7 +155,24 @@ class PropertyNormalizerTest extends TestCase
*/ */
public function testUncallableCallbacks() 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'); $obj = new PropertyConstructorDummy('baz', 'quux');
@ -146,7 +181,18 @@ class PropertyNormalizerTest extends TestCase
public function testIgnoredAttributes() 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 = new PropertyDummy();
$obj->foo = 'foo'; $obj->foo = 'foo';
@ -324,9 +370,22 @@ class PropertyNormalizerTest extends TestCase
*/ */
public function testUnableToNormalizeCircularReference() public function testUnableToNormalizeCircularReference()
{ {
$serializer = new Serializer(array($this->normalizer)); $this->doTestUnableToNormalizeCircularReference();
$this->normalizer->setSerializer($serializer); }
$this->normalizer->setCircularReferenceLimit(2);
/**
* @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(); $obj = new PropertyCircularReferenceDummy();
@ -350,11 +409,23 @@ class PropertyNormalizerTest extends TestCase
public function testCircularReferenceHandler() public function testCircularReferenceHandler()
{ {
$serializer = new Serializer(array($this->normalizer)); $this->doTestCircularReferenceHandler();
$this->normalizer->setSerializer($serializer); }
$this->normalizer->setCircularReferenceHandler(function ($obj) {
public function testLegacyCircularReferenceHandler()
{
$this->doTestCircularReferenceHandler(true);
}
private function doTestCircularReferenceHandler(bool $legacy = false)
{
$handler = function ($obj) {
return \get_class($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(); $obj = new PropertyCircularReferenceDummy();