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:
commit
426cf81c16
@ -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
|
||||||
-----
|
-----
|
||||||
|
@ -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)));
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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) {
|
||||||
|
@ -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);
|
||||||
|
@ -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)) {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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'
|
||||||
|
@ -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"?>
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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',
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -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())
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user