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

This commit is contained in:
Kévin Dunglas 2018-10-03 17:09:15 +02:00
parent 316e95c06c
commit 52b186a210
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
-----
* using the default context is the new recommended way to configure normalizers and encoders
* added a `skip_null_values` context option to not serialize properties with a `null` values
* `AbstractNormalizer::handleCircularReference` is now final and receives
two optional extra arguments: the format and the context
@ -24,6 +25,15 @@ CHANGELOG
and `ObjectNormalizer` constructor
* added `MetadataAwareNameConverter` to configure the serialized name of properties through metadata
* `YamlEncoder` now handles the `.yml` extension too
* `AbstractNormalizer::$circularReferenceLimit`, `AbstractNormalizer::$circularReferenceHandler`,
`AbstractNormalizer::$callbacks`, `AbstractNormalizer::$ignoredAttributes`,
`AbstractNormalizer::$camelizedAttributes`, `AbstractNormalizer::setCircularReferenceLimit()`,
`AbstractNormalizer::setCircularReferenceHandler()`, `AbstractNormalizer::setCallbacks()` and
`AbstractNormalizer::setIgnoredAttributes()` are deprecated, use the default context instead.
* `AbstractObjectNormalizer::$maxDepthHandler` and `AbstractObjectNormalizer::setMaxDepthHandler()`
are deprecated, use the default context instead.
* passing configuration options directly to the constructor of `CsvEncoder`, `JsonDecode` and
`XmlEncoder` is deprecated since Symfony 4.2, use the default context instead.
4.1.0
-----

View File

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

View File

@ -22,19 +22,41 @@ class JsonDecode implements DecoderInterface
{
protected $serializer;
private $associative;
private $recursionDepth;
/**
* True to return the result as an associative array, false for a nested stdClass hierarchy.
*/
const ASSOCIATIVE = 'json_decode_associative';
const OPTIONS = 'json_decode_options';
/**
* Specifies the recursion depth.
*/
const RECURSION_DEPTH = 'json_decode_recursion_depth';
private $defaultContext = array(
self::ASSOCIATIVE => false,
self::OPTIONS => 0,
self::RECURSION_DEPTH => 512,
);
/**
* Constructs a new JsonDecode instance.
*
* @param bool $associative True to return the result associative array, false for a nested stdClass hierarchy
* @param int $depth Specifies the recursion depth
* @param array $defaultContext
*/
public function __construct(bool $associative = false, int $depth = 512)
public function __construct($defaultContext = array(), int $depth = 512)
{
$this->associative = $associative;
$this->recursionDepth = $depth;
if (!\is_array($defaultContext)) {
@trigger_error(sprintf('Using constructor parameters that are not a default context is deprecated since Symfony 4.2, use the "%s" and "%s" keys of the context instead.', self::ASSOCIATIVE, self::RECURSION_DEPTH), E_USER_DEPRECATED);
$defaultContext = array(
self::ASSOCIATIVE => (bool) $defaultContext,
self::RECURSION_DEPTH => $depth,
);
}
$this->defaultContext = array_merge($this->defaultContext, $defaultContext);
}
/**
@ -47,7 +69,7 @@ class JsonDecode implements DecoderInterface
* The $context array is a simple key=>value array, with the following supported keys:
*
* json_decode_associative: boolean
* If true, returns the object as associative array.
* If true, returns the object as an associative array.
* If false, returns the object as nested stdClass
* If not specified, this method will use the default set in JsonDecode::__construct
*
@ -56,7 +78,7 @@ class JsonDecode implements DecoderInterface
* If not specified, this method will use the default set in JsonDecode::__construct
*
* json_decode_options: integer
* Specifies additional options as per documentation for json_decode.
* Specifies additional options as per documentation for json_decode
*
* @return mixed
*
@ -66,11 +88,9 @@ class JsonDecode implements DecoderInterface
*/
public function decode($data, $format, array $context = array())
{
$context = $this->resolveContext($context);
$associative = $context['json_decode_associative'];
$recursionDepth = $context['json_decode_recursion_depth'];
$options = $context['json_decode_options'];
$associative = $context[self::ASSOCIATIVE] ?? $this->defaultContext[self::ASSOCIATIVE];
$recursionDepth = $context[self::RECURSION_DEPTH] ?? $this->defaultContext[self::RECURSION_DEPTH];
$options = $context[self::OPTIONS] ?? $this->defaultContext[self::OPTIONS];
$decodedData = json_decode($data, $associative, $recursionDepth, $options);
@ -88,20 +108,4 @@ class JsonDecode implements DecoderInterface
{
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
{
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())
{
$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 || !($context['json_encode_options'] & JSON_PARTIAL_OUTPUT_ON_ERROR))) {
if (JSON_ERROR_NONE !== json_last_error() && (false === $encodedJson || !($jsonEncodeOptions & JSON_PARTIAL_OUTPUT_ON_ERROR))) {
throw new NotEncodableValueException(json_last_error_msg());
}
@ -52,14 +64,4 @@ class JsonEncode implements EncoderInterface
{
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)
{
$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 AS_COLLECTION = 'as_collection';
/**
* An array of ignored XML node types while decoding, each one of the DOM Predefined XML_* constants.
*/
const DECODER_IGNORED_NODE_TYPES = 'decoder_ignored_node_types';
/**
* An array of ignored XML node types while encoding, each one of the DOM Predefined XML_* constants.
*/
const ENCODER_IGNORED_NODE_TYPES = 'encoder_ignored_node_types';
const ENCODING = 'xml_encoding';
const FORMAT_OUTPUT = 'xml_format_output';
/**
* A bit field of LIBXML_* constants.
*/
const LOAD_OPTIONS = 'load_options';
const REMOVE_EMPTY_TAGS = 'remove_empty_tags';
const ROOT_NODE_NAME = 'xml_root_node_name';
const STANDALONE = 'xml_standalone';
const TYPE_CASE_ATTRIBUTES = 'xml_type_cast_attributes';
const VERSION = 'xml_version';
private $defaultContext = array(
self::AS_COLLECTION => false,
self::DECODER_IGNORED_NODE_TYPES => array(XML_PI_NODE, XML_COMMENT_NODE),
self::ENCODER_IGNORED_NODE_TYPES => array(),
self::LOAD_OPTIONS => LIBXML_NONET | LIBXML_NOBLANKS,
self::REMOVE_EMPTY_TAGS => false,
self::ROOT_NODE_NAME => 'response',
self::TYPE_CASE_ATTRIBUTES => true,
);
/**
* @var \DOMDocument
*/
private $dom;
private $format;
private $context;
private $rootNodeName = 'response';
private $loadOptions;
private $decoderIgnoredNodeTypes;
private $encoderIgnoredNodeTypes;
/**
* Construct new XmlEncoder and allow to change the root node element name.
*
* @param int|null $loadOptions A bit field of LIBXML_* constants
* @param int[] $decoderIgnoredNodeTypes an array of ignored XML node types while decoding, each one of the DOM Predefined XML_* Constants
* @param int[] $encoderIgnoredNodeTypes an array of ignored XML node types while encoding, each one of the DOM Predefined XML_* Constants
* @param array $defaultContext
*/
public function __construct(string $rootNodeName = 'response', int $loadOptions = null, array $decoderIgnoredNodeTypes = array(XML_PI_NODE, XML_COMMENT_NODE), array $encoderIgnoredNodeTypes = array())
public function __construct($defaultContext = array(), int $loadOptions = null, array $decoderIgnoredNodeTypes = array(XML_PI_NODE, XML_COMMENT_NODE), array $encoderIgnoredNodeTypes = array())
{
$this->rootNodeName = $rootNodeName;
$this->loadOptions = null !== $loadOptions ? $loadOptions : LIBXML_NONET | LIBXML_NOBLANKS;
$this->decoderIgnoredNodeTypes = $decoderIgnoredNodeTypes;
$this->encoderIgnoredNodeTypes = $encoderIgnoredNodeTypes;
if (!\is_array($defaultContext)) {
@trigger_error('Passing configuration options directly to the constructor is deprecated since Symfony 4.2, use the default context instead.', E_USER_DEPRECATED);
$defaultContext = array(
self::DECODER_IGNORED_NODE_TYPES => $decoderIgnoredNodeTypes,
self::ENCODER_IGNORED_NODE_TYPES => $encoderIgnoredNodeTypes,
self::LOAD_OPTIONS => $loadOptions ?? LIBXML_NONET | LIBXML_NOBLANKS,
self::ROOT_NODE_NAME => (string) $defaultContext,
);
}
$this->defaultContext = array_merge($this->defaultContext, $defaultContext);
}
/**
@ -61,11 +95,13 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa
*/
public function encode($data, $format, array $context = array())
{
$encoderIgnoredNodeTypes = $context[self::ENCODER_IGNORED_NODE_TYPES] ?? $this->defaultContext[self::ENCODER_IGNORED_NODE_TYPES];
$ignorePiNode = \in_array(XML_PI_NODE, $encoderIgnoredNodeTypes, true);
if ($data instanceof \DOMDocument) {
return $data->saveXML(\in_array(XML_PI_NODE, $this->encoderIgnoredNodeTypes, true) ? $data->documentElement : null);
return $data->saveXML($ignorePiNode ? $data->documentElement : null);
}
$xmlRootNodeName = $this->resolveXmlRootName($context);
$xmlRootNodeName = $context[self::ROOT_NODE_NAME] ?? $this->defaultContext[self::ROOT_NODE_NAME];
$this->dom = $this->createDomDocument($context);
$this->format = $format;
@ -79,7 +115,7 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa
$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();
$dom = new \DOMDocument();
$dom->loadXML($data, $this->loadOptions);
$dom->loadXML($data, $context[self::LOAD_OPTIONS] ?? $this->defaultContext[self::LOAD_OPTIONS]);
libxml_use_internal_errors($internalErrors);
libxml_disable_entity_loader($disableEntities);
@ -108,11 +144,12 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa
}
$rootNode = null;
$decoderIgnoredNodeTypes = $context[self::DECODER_IGNORED_NODE_TYPES] ?? $this->defaultContext[self::DECODER_IGNORED_NODE_TYPES];
foreach ($dom->childNodes as $child) {
if (XML_DOCUMENT_TYPE_NODE === $child->nodeType) {
throw new NotEncodableValueException('Document types are not allowed.');
}
if (!$rootNode && !\in_array($child->nodeType, $this->decoderIgnoredNodeTypes, true)) {
if (!$rootNode && !\in_array($child->nodeType, $decoderIgnoredNodeTypes, true)) {
$rootNode = $child;
}
}
@ -169,21 +206,29 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa
/**
* Sets the root node name.
*
* @deprecated since Symfony 4.2
*
* @param string $name Root node name
*/
public function setRootNodeName($name)
{
$this->rootNodeName = $name;
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the context instead.', __METHOD__), E_USER_DEPRECATED);
$this->defaultContext[self::ROOT_NODE_NAME] = $name;
}
/**
* Returns the root node name.
*
* @deprecated since Symfony 4.2
*
* @return string
*/
public function getRootNodeName()
{
return $this->rootNodeName;
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the context instead.', __METHOD__), E_USER_DEPRECATED);
return $this->defaultContext[self::ROOT_NODE_NAME];
}
final protected function appendXMLString(\DOMNode $node, string $val): bool
@ -291,7 +336,7 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa
}
$data = array();
$typeCastAttributes = $this->resolveXmlTypeCastAttributes($context);
$typeCastAttributes = (bool) ($context[self::TYPE_CASE_ATTRIBUTES] ?? $this->defaultContext[self::TYPE_CASE_ATTRIBUTES]);
foreach ($node->attributes as $attr) {
if (!is_numeric($attr->nodeValue) || !$typeCastAttributes) {
@ -328,9 +373,9 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa
}
$value = array();
$decoderIgnoredNodeTypes = $context[self::DECODER_IGNORED_NODE_TYPES] ?? $this->defaultContext[self::DECODER_IGNORED_NODE_TYPES];
foreach ($node->childNodes as $subnode) {
if (\in_array($subnode->nodeType, $this->decoderIgnoredNodeTypes, true)) {
if (\in_array($subnode->nodeType, $decoderIgnoredNodeTypes, true)) {
continue;
}
@ -347,8 +392,9 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa
}
}
$asCollection = $context[self::AS_COLLECTION] ?? $this->defaultContext[self::AS_COLLECTION];
foreach ($value as $key => $val) {
if (empty($context['as_collection']) && \is_array($val) && 1 === \count($val)) {
if (!$asCollection && \is_array($val) && 1 === \count($val)) {
$value[$key] = current($val);
}
}
@ -366,6 +412,8 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa
private function buildXml(\DOMNode $parentNode, $data, string $xmlRootNodeName = null): bool
{
$append = true;
$removeEmptyTags = $this->context[self::REMOVE_EMPTY_TAGS] ?? $this->defaultContext[self::REMOVE_EMPTY_TAGS] ?? false;
$encoderIgnoredNodeTypes = $this->context[self::ENCODER_IGNORED_NODE_TYPES] ?? $this->defaultContext[self::ENCODER_IGNORED_NODE_TYPES];
if (\is_array($data) || ($data instanceof \Traversable && !$this->serializer->supportsNormalization($data, $this->format))) {
foreach ($data as $key => $data) {
@ -378,7 +426,7 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa
} elseif ('#' === $key) {
$append = $this->selectNodeType($parentNode, $data);
} elseif ('#comment' === $key) {
if (!\in_array(XML_COMMENT_NODE, $this->encoderIgnoredNodeTypes, true)) {
if (!\in_array(XML_COMMENT_NODE, $encoderIgnoredNodeTypes, true)) {
$append = $this->appendComment($parentNode, $data);
}
} elseif (\is_array($data) && false === is_numeric($key)) {
@ -397,7 +445,7 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa
}
} elseif (is_numeric($key) || !$this->isElementNameValid($key)) {
$append = $this->appendNode($parentNode, $data, 'item', $key);
} elseif (null !== $data || !isset($this->context['remove_empty_tags']) || false === $this->context['remove_empty_tags']) {
} elseif (null !== $data || !$removeEmptyTags) {
$append = $this->appendNode($parentNode, $data, $key);
}
}
@ -487,26 +535,6 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa
return true;
}
/**
* Get real XML root node name, taking serializer options into account.
*/
private function resolveXmlRootName(array $context = array()): string
{
return isset($context['xml_root_node_name'])
? $context['xml_root_node_name']
: $this->rootNodeName;
}
/**
* Get XML option for type casting attributes Defaults to true.
*/
private function resolveXmlTypeCastAttributes(array $context = array()): bool
{
return isset($context['xml_type_cast_attributes'])
? (bool) $context['xml_type_cast_attributes']
: true;
}
/**
* Create a DOM document, taking serializer options into account.
*/
@ -517,17 +545,17 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa
// Set an attribute on the DOM document specifying, as part of the XML declaration,
$xmlOptions = array(
// nicely formats output with indentation and extra space
'xml_format_output' => 'formatOutput',
self::FORMAT_OUTPUT => 'formatOutput',
// the version number of the document
'xml_version' => 'xmlVersion',
self::VERSION => 'xmlVersion',
// the encoding of the document
'xml_encoding' => 'encoding',
self::ENCODING => 'encoding',
// whether the document is standalone
'xml_standalone' => 'xmlStandalone',
self::STANDALONE => 'xmlStandalone',
);
foreach ($xmlOptions as $xmlOption => $documentProperty) {
if (isset($context[$xmlOption])) {
$document->$documentProperty = $context[$xmlOption];
if ($contextOption = $context[$xmlOption] ?? $this->defaultContext[$xmlOption] ?? false) {
$document->$documentProperty = $contextOption;
}
}

View File

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

View File

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

View File

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

View File

@ -24,11 +24,22 @@ class DateIntervalNormalizer implements NormalizerInterface, DenormalizerInterfa
{
const FORMAT_KEY = 'dateinterval_format';
private $format;
private $defaultContext = array(
self::FORMAT_KEY => 'P%yY%mM%dDT%hH%iM%sS',
);
public function __construct(string $format = 'P%yY%mM%dDT%hH%iM%sS')
/**
* @param array $defaultContext
*/
public function __construct($defaultContext = array())
{
$this->format = $format;
if (!\is_array($defaultContext)) {
@trigger_error(sprintf('The "format" parameter is deprecated since Symfony 4.2, use the "%s" key of the context instead.', self::FORMAT_KEY), E_USER_DEPRECATED);
$defaultContext = array(self::FORMAT_KEY => (string) $defaultContext);
}
$this->defaultContext = array_merge($this->defaultContext, $defaultContext);
}
/**
@ -42,9 +53,7 @@ class DateIntervalNormalizer implements NormalizerInterface, DenormalizerInterfa
throw new InvalidArgumentException('The object must be an instance of "\DateInterval".');
}
$dateIntervalFormat = isset($context[self::FORMAT_KEY]) ? $context[self::FORMAT_KEY] : $this->format;
return $object->format($dateIntervalFormat);
return $object->format($context[self::FORMAT_KEY] ?? $this->defaultContext[self::FORMAT_KEY]);
}
/**
@ -79,7 +88,7 @@ class DateIntervalNormalizer implements NormalizerInterface, DenormalizerInterfa
throw new UnexpectedValueException('Expected a valid ISO 8601 interval string.');
}
$dateIntervalFormat = isset($context[self::FORMAT_KEY]) ? $context[self::FORMAT_KEY] : $this->format;
$dateIntervalFormat = $context[self::FORMAT_KEY] ?? $this->defaultContext[self::FORMAT_KEY];
$valuePattern = '/^'.preg_replace('/%([yYmMdDhHiIsSwW])(\w)/', '(?P<$1>\d+)$2', $dateIntervalFormat).'$/';
if (!preg_match($valuePattern, $data)) {

View File

@ -25,8 +25,7 @@ class DateTimeNormalizer implements NormalizerInterface, DenormalizerInterface,
const FORMAT_KEY = 'datetime_format';
const TIMEZONE_KEY = 'datetime_timezone';
private $format;
private $timezone;
private $defaultContext;
private static $supportedTypes = array(
\DateTimeInterface::class => true,
@ -34,10 +33,24 @@ class DateTimeNormalizer implements NormalizerInterface, DenormalizerInterface,
\DateTime::class => true,
);
public function __construct(?string $format = \DateTime::RFC3339, \DateTimeZone $timezone = null)
/**
* @param array $defaultContext
*/
public function __construct($defaultContext = array(), \DateTimeZone $timezone = null)
{
$this->format = $format;
$this->timezone = $timezone;
$this->defaultContext = array(
self::FORMAT_KEY => \DateTime::RFC3339,
self::TIMEZONE_KEY => null,
);
if (!\is_array($defaultContext)) {
@trigger_error('Passing configuration options directly to the constructor is deprecated since Symfony 4.2, use the default context instead.', E_USER_DEPRECATED);
$defaultContext = array(self::FORMAT_KEY => (string) $defaultContext);
$defaultContext[self::TIMEZONE_KEY] = $timezone;
}
$this->defaultContext = array_merge($this->defaultContext, $defaultContext);
}
/**
@ -51,14 +64,14 @@ class DateTimeNormalizer implements NormalizerInterface, DenormalizerInterface,
throw new InvalidArgumentException('The object must implement the "\DateTimeInterface".');
}
$format = isset($context[self::FORMAT_KEY]) ? $context[self::FORMAT_KEY] : $this->format;
$dateTimeFormat = $context[self::FORMAT_KEY] ?? $this->defaultContext[self::FORMAT_KEY];
$timezone = $this->getTimezone($context);
if (null !== $timezone) {
$object = (new \DateTimeImmutable('@'.$object->getTimestamp()))->setTimezone($timezone);
}
return $object->format($format);
return $object->format($dateTimeFormat);
}
/**
@ -76,7 +89,7 @@ class DateTimeNormalizer implements NormalizerInterface, DenormalizerInterface,
*/
public function denormalize($data, $class, $format = null, array $context = array())
{
$dateTimeFormat = isset($context[self::FORMAT_KEY]) ? $context[self::FORMAT_KEY] : null;
$dateTimeFormat = $context[self::FORMAT_KEY] ?? null;
$timezone = $this->getTimezone($context);
if ('' === $data || null === $data) {
@ -142,8 +155,7 @@ class DateTimeNormalizer implements NormalizerInterface, DenormalizerInterface,
private function getTimezone(array $context)
{
$dateTimeZone = array_key_exists(self::TIMEZONE_KEY, $context) ? $context[self::TIMEZONE_KEY] : $this->timezone;
$dateTimeZone = $context[self::TIMEZONE_KEY] ?? $this->defaultContext[self::TIMEZONE_KEY];
if (null === $dateTimeZone) {
return null;
}

View File

@ -30,13 +30,13 @@ class ObjectNormalizer extends AbstractObjectNormalizer
{
protected $propertyAccessor;
public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, PropertyAccessorInterface $propertyAccessor = null, PropertyTypeExtractorInterface $propertyTypeExtractor = null, ClassDiscriminatorResolverInterface $classDiscriminatorResolver = null, callable $objectClassResolver = null)
public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, PropertyAccessorInterface $propertyAccessor = null, PropertyTypeExtractorInterface $propertyTypeExtractor = null, ClassDiscriminatorResolverInterface $classDiscriminatorResolver = null, callable $objectClassResolver = null, array $defaultContext = array())
{
if (!\class_exists(PropertyAccess::class)) {
throw new LogicException('The ObjectNormalizer class requires the "PropertyAccess" component. Install "symfony/property-access" to use it.');
}
parent::__construct($classMetadataFactory, $nameConverter, $propertyTypeExtractor, $classDiscriminatorResolver, $objectClassResolver);
parent::__construct($classMetadataFactory, $nameConverter, $propertyTypeExtractor, $classDiscriminatorResolver, $objectClassResolver, $defaultContext);
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
}

View File

@ -100,7 +100,26 @@ CSV
public function testEncodeCustomSettings()
{
$this->encoder = new CsvEncoder(';', "'", '|', '-');
$this->doTestEncodeCustomSettings();
}
public function testLegacyEncodeCustomSettings()
{
$this->doTestEncodeCustomSettings(true);
}
private function doTestEncodeCustomSettings(bool $legacy = false)
{
if ($legacy) {
$this->encoder = new CsvEncoder(';', "'", '|', '-');
} else {
$this->encoder = new CsvEncoder(array(
CsvEncoder::DELIMITER_KEY => ';',
CsvEncoder::ENCLOSURE_KEY => "'",
CsvEncoder::ESCAPE_CHAR_KEY => '|',
CsvEncoder::KEY_SEPARATOR_KEY => '-',
));
}
$value = array('a' => 'he\'llo', 'c' => array('d' => 'foo'));
@ -175,7 +194,21 @@ CSV;
public function testEncodeFormulas()
{
$this->encoder = new CsvEncoder(',', '"', '\\', '.', true);
$this->doTestEncodeFormulas();
}
public function testLegacyEncodeFormulas()
{
$this->doTestEncodeFormulas(true);
}
private function doTestEncodeFormulas(bool $legacy = false)
{
if ($legacy) {
$this->encoder = new CsvEncoder(',', '"', '\\', '.', true);
} else {
$this->encoder = new CsvEncoder(array(CsvEncoder::ESCAPE_FORMULAS_KEY => true));
}
$this->assertSame(<<<'CSV'
0
@ -378,7 +411,26 @@ CSV
public function testDecodeCustomSettings()
{
$this->encoder = new CsvEncoder(';', "'", '|', '-');
$this->doTestDecodeCustomSettings();
}
public function testLegacyDecodeCustomSettings()
{
$this->doTestDecodeCustomSettings(true);
}
private function doTestDecodeCustomSettings(bool $legacy = false)
{
if ($legacy) {
$this->encoder = new CsvEncoder(';', "'", '|', '-');
} else {
$this->encoder = new CsvEncoder(array(
CsvEncoder::DELIMITER_KEY => ';',
CsvEncoder::ENCLOSURE_KEY => "'",
CsvEncoder::ESCAPE_CHAR_KEY => '|',
CsvEncoder::KEY_SEPARATOR_KEY => '-',
));
}
$expected = array(array('a' => 'hell\'o', 'bar' => array('baz' => 'b')));
$this->assertEquals($expected, $this->encoder->decode(<<<'CSV'

View File

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

View File

@ -58,7 +58,21 @@ class DateIntervalNormalizerTest extends TestCase
*/
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)
{
$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()
{
$this->assertEquals('16', (new DateTimeNormalizer('y'))->normalize(new \DateTime('2016/01/01', new \DateTimeZone('UTC'))));
$this->doTestNormalizeUsingFormatPassedInConstructor();
}
public function testLegacyNormalizeUsingFormatPassedInConstructor()
{
$this->doTestNormalizeUsingFormatPassedInConstructor(true);
}
private function doTestNormalizeUsingFormatPassedInConstructor(bool $legacy = false)
{
$normalizer = $legacy ? new DateTimeNormalizer('y') : new DateTimeNormalizer(array(DateTimeNormalizer::FORMAT_KEY => 'y'));
$this->assertEquals('16', $normalizer->normalize(new \DateTime('2016/01/01', new \DateTimeZone('UTC'))));
}
public function testNormalizeUsingTimeZonePassedInConstructor()
{
$normalizer = new DateTimeNormalizer(\DateTime::RFC3339, new \DateTimeZone('Japan'));
$this->doTestNormalizeUsingTimeZonePassedInConstructor();
}
public function testLegacyNormalizeUsingTimeZonePassedInConstructor()
{
$this->doTestNormalizeUsingTimeZonePassedInConstructor(true);
}
private function doTestNormalizeUsingTimeZonePassedInConstructor(bool $legacy = false)
{
if ($legacy) {
$normalizer = new DateTimeNormalizer(\DateTime::RFC3339, new \DateTimeZone('Japan'));
} else {
$normalizer = new DateTimeNormalizer(array(DateTimeNormalizer::TIMEZONE_KEY => new \DateTimeZone('Japan')));
}
$this->assertSame('2016-12-01T00:00:00+09:00', $normalizer->normalize(new \DateTime('2016/12/01', new \DateTimeZone('Japan'))));
$this->assertSame('2016-12-01T09:00:00+09:00', $normalizer->normalize(new \DateTime('2016/12/01', new \DateTimeZone('UTC'))));
@ -103,10 +128,20 @@ class DateTimeNormalizerTest extends TestCase
}
public function testDenormalizeUsingTimezonePassedInConstructor()
{
$this->doTestDenormalizeUsingTimezonePassedInConstructor();
}
public function testLegacyDenormalizeUsingTimezonePassedInConstructor()
{
$this->doTestDenormalizeUsingTimezonePassedInConstructor(true);
}
private function doTestDenormalizeUsingTimezonePassedInConstructor(bool $legacy = false)
{
$timezone = new \DateTimeZone('Japan');
$expected = new \DateTime('2016/12/01 17:35:00', $timezone);
$normalizer = new DateTimeNormalizer(null, $timezone);
$normalizer = $legacy ? new DateTimeNormalizer(null, $timezone) : new DateTimeNormalizer(array(DateTimeNormalizer::TIMEZONE_KEY => $timezone));
$this->assertEquals($expected, $normalizer->denormalize('2016.12.01 17:35:00', \DateTime::class, null, array(
DateTimeNormalizer::FORMAT_KEY => 'Y.m.d H:i:s',

View File

@ -37,9 +37,14 @@ class GetSetMethodNormalizerTest extends TestCase
private $serializer;
protected function setUp()
{
$this->createNormalizer();
}
private function createNormalizer(array $defaultContext = array())
{
$this->serializer = $this->getMockBuilder(__NAMESPACE__.'\SerializerNormalizer')->getMock();
$this->normalizer = new GetSetMethodNormalizer();
$this->normalizer = new GetSetMethodNormalizer(null, null, null, null, null, $defaultContext);
$this->normalizer->setSerializer($this->serializer);
}
@ -285,10 +290,22 @@ class GetSetMethodNormalizerTest extends TestCase
*/
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);
$this->assertEquals(
$result,
$this->normalizer->normalize($obj, 'any'),
@ -301,7 +318,21 @@ class GetSetMethodNormalizerTest extends TestCase
*/
public function testUncallableCallbacks()
{
$this->normalizer->setCallbacks(array('bar' => null));
$this->doTestUncallableCallbacks();
}
/**
* @expectedException \InvalidArgumentException
*/
public function testLegacyUncallableCallbacks()
{
$this->doTestUncallableCallbacks(true);
}
private function doTestUncallableCallbacks(bool $legacy = false)
{
$callbacks = array('bar' => null);
$legacy ? $this->normalizer->setCallbacks($callbacks) : $this->createNormalizer(array(GetSetMethodNormalizer::CALLBACKS => $callbacks));
$obj = new GetConstructorDummy('baz', 'quux', true);
@ -310,7 +341,18 @@ class GetSetMethodNormalizerTest extends TestCase
public function testIgnoredAttributes()
{
$this->normalizer->setIgnoredAttributes(array('foo', 'bar', 'baz', 'camelCase', 'object'));
$this->doTestIgnoredAttributes();
}
public function testLegacyIgnoredAttributes()
{
$this->doTestIgnoredAttributes(true);
}
private function doTestIgnoredAttributes(bool $legacy = false)
{
$ignoredAttributes = array('foo', 'bar', 'baz', 'camelCase', 'object');
$legacy ? $this->normalizer->setIgnoredAttributes($ignoredAttributes) : $this->createNormalizer(array(GetSetMethodNormalizer::IGNORED_ATTRIBUTES => $ignoredAttributes));
$obj = new GetSetDummy();
$obj->setFoo('foo');
@ -404,12 +446,24 @@ class GetSetMethodNormalizerTest extends TestCase
*/
public function testUnableToNormalizeCircularReference()
{
$serializer = new Serializer(array($this->normalizer));
$this->normalizer->setSerializer($serializer);
$this->normalizer->setCircularReferenceLimit(2);
$this->doTestUnableToNormalizeCircularReference();
}
/**
* @expectedException \Symfony\Component\Serializer\Exception\CircularReferenceException
*/
public function testLegacyUnableToNormalizeCircularReference()
{
$this->doTestUnableToNormalizeCircularReference(true);
}
private function doTestUnableToNormalizeCircularReference(bool $legacy = false)
{
$legacy ? $this->normalizer->setCircularReferenceLimit(2) : $this->createNormalizer(array(GetSetMethodNormalizer::CIRCULAR_REFERENCE_LIMIT => 2));
$this->serializer = new Serializer(array($this->normalizer));
$this->normalizer->setSerializer($this->serializer);
$obj = new CircularReferenceDummy();
$this->normalizer->normalize($obj);
}
@ -430,11 +484,23 @@ class GetSetMethodNormalizerTest extends TestCase
public function testCircularReferenceHandler()
{
$serializer = new Serializer(array($this->normalizer));
$this->normalizer->setSerializer($serializer);
$this->normalizer->setCircularReferenceHandler(function ($obj) {
$this->doTestCircularReferenceHandler();
}
public function testLegacyCircularReferenceHandler()
{
$this->doTestCircularReferenceHandler(true);
}
private function doTestCircularReferenceHandler(bool $legacy = false)
{
$handler = function ($obj) {
return \get_class($obj);
});
};
$legacy ? $this->normalizer->setCircularReferenceHandler($handler) : $this->createNormalizer(array(GetSetMethodNormalizer::CIRCULAR_REFERENCE_HANDLER => $handler));
$this->serializer = new Serializer(array($this->normalizer));
$this->normalizer->setSerializer($this->serializer);
$obj = new CircularReferenceDummy();

View File

@ -33,9 +33,14 @@ class JsonSerializableNormalizerTest extends TestCase
private $serializer;
protected function setUp()
{
$this->createNormalizer();
}
private function createNormalizer(array $defaultContext = array())
{
$this->serializer = $this->getMockBuilder(JsonSerializerNormalizer::class)->getMock();
$this->normalizer = new JsonSerializableNormalizer();
$this->normalizer = new JsonSerializableNormalizer(null, null, $defaultContext);
$this->normalizer->setSerializer($this->serializer);
}
@ -65,7 +70,23 @@ class JsonSerializableNormalizerTest extends TestCase
*/
public function testCircularNormalize()
{
$this->normalizer->setCircularReferenceLimit(1);
$this->doTestCircularNormalize();
}
/**
* @expectedException \Symfony\Component\Serializer\Exception\CircularReferenceException
*/
public function testLegacyCircularNormalize()
{
$this->doTestCircularNormalize(true);
}
/**
* @expectedException \Symfony\Component\Serializer\Exception\CircularReferenceException
*/
private function doTestCircularNormalize(bool $legacy = false)
{
$legacy ? $this->normalizer->setCircularReferenceLimit(1) : $this->createNormalizer(array(JsonSerializableNormalizer::CIRCULAR_REFERENCE_LIMIT => 1));
$this->serializer
->expects($this->once())

View File

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

View File

@ -37,9 +37,14 @@ class PropertyNormalizerTest extends TestCase
private $serializer;
protected function setUp()
{
$this->createNormalizer();
}
private function createNormalizer(array $defaultContext = array())
{
$this->serializer = $this->getMockBuilder('Symfony\Component\Serializer\SerializerInterface')->getMock();
$this->normalizer = new PropertyNormalizer();
$this->normalizer = new PropertyNormalizer(null, null, null, null, null, $defaultContext);
$this->normalizer->setSerializer($this->serializer);
}
@ -121,7 +126,20 @@ class PropertyNormalizerTest extends TestCase
*/
public function testCallbacks($callbacks, $value, $result, $message)
{
$this->normalizer->setCallbacks($callbacks);
$this->doTestCallbacks($callbacks, $value, $result, $message);
}
/**
* @dataProvider provideCallbacks
*/
public function testLegacyCallbacks($callbacks, $value, $result, $message)
{
$this->doTestCallbacks($callbacks, $value, $result, $message, true);
}
private function doTestCallbacks($callbacks, $value, $result, $message, bool $legacy = false)
{
$legacy ? $this->normalizer->setCallbacks($callbacks) : $this->createNormalizer(array(PropertyNormalizer::CALLBACKS => $callbacks));
$obj = new PropertyConstructorDummy('', $value);
@ -137,7 +155,24 @@ class PropertyNormalizerTest extends TestCase
*/
public function testUncallableCallbacks()
{
$this->normalizer->setCallbacks(array('bar' => null));
$this->doTestUncallableCallbacks();
}
/**
* @expectedException \InvalidArgumentException
*/
public function testLegacyUncallableCallbacks()
{
$this->doTestUncallableCallbacks(true);
}
/**
* @expectedException \InvalidArgumentException
*/
private function doTestUncallableCallbacks(bool $legacy = false)
{
$callbacks = array('bar' => null);
$legacy ? $this->normalizer->setCallbacks($callbacks) : $this->createNormalizer(array(PropertyNormalizer::CALLBACKS => $callbacks));
$obj = new PropertyConstructorDummy('baz', 'quux');
@ -146,7 +181,18 @@ class PropertyNormalizerTest extends TestCase
public function testIgnoredAttributes()
{
$this->normalizer->setIgnoredAttributes(array('foo', 'bar', 'camelCase'));
$this->doTestIgnoredAttributes();
}
public function testLegacyIgnoredAttributes()
{
$this->doTestIgnoredAttributes(true);
}
private function doTestIgnoredAttributes(bool $legacy = false)
{
$ignoredAttributes = array('foo', 'bar', 'camelCase');
$legacy ? $this->normalizer->setIgnoredAttributes($ignoredAttributes) : $this->createNormalizer(array(PropertyNormalizer::IGNORED_ATTRIBUTES => $ignoredAttributes));
$obj = new PropertyDummy();
$obj->foo = 'foo';
@ -324,9 +370,22 @@ class PropertyNormalizerTest extends TestCase
*/
public function testUnableToNormalizeCircularReference()
{
$serializer = new Serializer(array($this->normalizer));
$this->normalizer->setSerializer($serializer);
$this->normalizer->setCircularReferenceLimit(2);
$this->doTestUnableToNormalizeCircularReference();
}
/**
* @expectedException \Symfony\Component\Serializer\Exception\CircularReferenceException
*/
public function testLegacyUnableToNormalizeCircularReference()
{
$this->doTestUnableToNormalizeCircularReference(true);
}
private function doTestUnableToNormalizeCircularReference(bool $legacy = false)
{
$legacy ? $this->normalizer->setCircularReferenceLimit(2) : $this->createNormalizer(array(PropertyNormalizer::CIRCULAR_REFERENCE_LIMIT => 2));
$this->serializer = new Serializer(array($this->normalizer));
$this->normalizer->setSerializer($this->serializer);
$obj = new PropertyCircularReferenceDummy();
@ -350,11 +409,23 @@ class PropertyNormalizerTest extends TestCase
public function testCircularReferenceHandler()
{
$serializer = new Serializer(array($this->normalizer));
$this->normalizer->setSerializer($serializer);
$this->normalizer->setCircularReferenceHandler(function ($obj) {
$this->doTestCircularReferenceHandler();
}
public function testLegacyCircularReferenceHandler()
{
$this->doTestCircularReferenceHandler(true);
}
private function doTestCircularReferenceHandler(bool $legacy = false)
{
$handler = function ($obj) {
return \get_class($obj);
});
};
$legacy ? $this->normalizer->setCircularReferenceHandler($handler) : $this->createNormalizer(array(PropertyNormalizer::CIRCULAR_REFERENCE_HANDLER => $handler));
$this->serializer = new Serializer(array($this->normalizer));
$this->normalizer->setSerializer($this->serializer);
$obj = new PropertyCircularReferenceDummy();