8000 feature #28709 [Serializer] Refactor and uniformize the config by int… · symfony/symfony@426cf81 · GitHub
[go: up one dir, main page]

Skip to content

Commit 426cf81

Browse files
committed
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 ------- 52b186a [Serializer] Refactor and uniformize the config by introducing a default context
2 parents a0cbcac + 52b186a commit 426cf81

20 files changed

+854
-258
lines changed

src/Symfony/Component/Serializer/CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
4.2.0
55
-----
66

7+
* using the default context is the new recommended way to configure normalizers and encoders
78
* added a `skip_null_values` context option to not serialize properties with a `null` values
89
* `AbstractNormalizer::handleCircularReference` is now final and receives
910
two optional extra arguments: the format and the context
@@ -24,6 +25,15 @@ CHANGELOG
2425
and `ObjectNormalizer` constructor
2526
* added `MetadataAwareNameConverter` to configure the serialized name of properties through metadata
2627
* `YamlEncoder` now handles the `.yml` extension too
28+
* `AbstractNormalizer::$circularReferenceLimit`, `AbstractNormalizer::$circularReferenceHandler`,
29+
`AbstractNormalizer::$callbacks`, `AbstractNormalizer::$ignoredAttributes`,
30+
`AbstractNormalizer::$camelizedAttributes`, `AbstractNormalizer::setCircularReferenceLimit()`,
31+
`AbstractNormalizer::setCircularReferenceHandler()`, `AbstractNormalizer::setCallbacks()` and
32+
`AbstractNormalizer::setIgnoredAttributes()` are deprecated, use the default context instead.
33+
* `AbstractObjectNormalizer::$maxDepthHandler` and `AbstractObjectNormalizer::setMaxDepthHandler()`
34+
are deprecated, use the default context instead.
35+
* passing configuration options directly to the constructor of `CsvEncoder`, `JsonDecode` and
36+
`XmlEncoder` is deprecated since Symfony 4.2, use the default context instead.
2737

2838
4.1.0
2939
-----

src/Symfony/Component/Serializer/Encoder/CsvEncoder.php

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,34 @@ class CsvEncoder implements EncoderInterface, DecoderInterface
3030
const ESCAPE_FORMULAS_KEY = 'csv_escape_formulas';
3131
const AS_COLLECTION_KEY = 'as_collection';
3232

33-
private $delimiter;
34-
private $enclosure;
35-
private $escapeChar;
36-
private $keySeparator;
37-
private $escapeFormulas;
3833
private $formulasStartCharacters = array('=', '-', '+', '@');
34+
private $defaultContext = array(
35+
self::DELIMITER_KEY => ',',
36+
self::ENCLOSURE_KEY => '"',
37+
self::ESCAPE_CHAR_KEY => '\\',
38+
self::ESCAPE_FORMULAS_KEY => false,
39+
self::HEADERS_KEY => array(),
40+
self::KEY_SEPARATOR_KEY => '.',
41+
);
3942

40-
public function __construct(string $delimiter = ',', string $enclosure = '"', string $escapeChar = '\\', string $keySeparator = '.', bool $escapeFormulas = false)
43+
/**
44+
* @param array $defaultContext
45+
*/
46+
public function __construct($defaultContext = array(), string $enclosure = '"', string $escapeChar = '\\', string $keySeparator = '.', bool $escapeFormulas = false)
4147
{
42-
$this->delimiter = $delimiter;
43-
$this->enclosure = $enclosure;
44-
$this->escapeChar = $escapeChar;
45-
$this->keySeparator = $keySeparator;
46-
$this->escapeFormulas = $escapeFormulas;
48+
if (!\is_array($defaultContext)) {
49+
@trigger_error('Passing configuration options directly to the constructor is deprecated since Symfony 4.2, use the default context instead.', E_USER_DEPRECATED);
50+
51+
$defaultContext = array(
52+
self::DELIMITER_KEY => (string) $defaultContext,
53+
self::ENCLOSURE_KEY => $enclosure,
54+
self::ESCAPE_CHAR_KEY => $escapeChar,
55+
self::KEY_SEPARATOR_KEY => $keySeparator,
56+
self::ESCAPE_FORMULAS_KEY => $escapeFormulas,
57+
);
58+
}
59+
60+
$this->defaultContext = array_merge($this->defaultContext, $defaultContext);
4761
}
4862

4963
/**
@@ -200,14 +214,14 @@ private function flatten(array $array, array &$result, string $keySeparator, str
200214
}
201215
}
202216

203-
private function getCsvOptions(array $context)
217+
private function getCsvOptions(array $context): array
204218
{
205-
$delimiter = isset($context[self::DELIMITER_KEY]) ? $context[self::DELIMITER_KEY] : $this->delimiter;
206-
$enclosure = isset($context[self::ENCLOSURE_KEY]) ? $context[self::ENCLOSURE_KEY] : $this->enclosure;
207-
$escapeChar = isset($context[self::ESCAPE_CHAR_KEY]) ? $context[self::ESCAPE_CHAR_KEY] : $this->escapeChar;
208-
$keySeparator = isset($context[self::KEY_SEPARATOR_KEY]) ? $context[self::KEY_SEPARATOR_KEY] : $this->keySeparator;
209-
$headers = isset($context[self::HEADERS_KEY]) ? $context[self::HEADERS_KEY] : array();
210-
$escapeFormulas = isset($context[self::ESCAPE_FORMULAS_KEY]) ? $context[self::ESCAPE_FORMULAS_KEY] : $this->escapeFormulas;
219+
$delimiter = $context[self::DELIMITER_KEY] ?? $this->defaultContext[self::DELIMITER_KEY];
220+
$enclosure = $context[self::ENCLOSURE_KEY] ?? $this->defaultContext[self::ENCLOSURE_KEY];
221+
$escapeChar = $context[self::ESCAPE_CHAR_KEY] ?? $this->defaultContext[self::ESCAPE_CHAR_KEY];
222+
$keySeparator = $context[self::KEY_SEPARATOR_KEY] ?? $this->defaultContext[self::KEY_SEPARATOR_KEY];
223+
$headers = $context[self::HEADERS_KEY] ?? $this->defaultContext[self::HEADERS_KEY];
224+
$escapeFormulas = $context[self::ESCAPE_FORMULAS_KEY] ?? $this->defaultContext[self::ESCAPE_FORMULAS_KEY];
211225

212226
if (!\is_array($headers)) {
213227
throw new InvalidArgumentException(sprintf('The "%s" context variable must be an array or null, given "%s".', self::HEADERS_KEY, \gettype($headers)));

src/Symfony/Component/Serializer/Encoder/JsonDecode.php

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,41 @@ class JsonDecode implements DecoderInterface
2222
{
2323
protected $serializer;
2424

25-
private $associative;
26-
private $recursionDepth;
25+
/**
26+
* True to return the result as an associative array, false for a nested stdClass hierarchy.
27+
*/
28+
const ASSOCIATIVE = 'json_decode_associative';
29+
30+
const OPTIONS = 'json_decode_options';
31+
32+
/**
33+
* Specifies the recursion depth.
34+
*/
35+
const RECURSION_DEPTH = 'json_decode_recursion_depth';
36+
37+
private $defaultContext = array(
38+
self::ASSOCIATIVE => false,
39+
self::OPTIONS => 0,
40+
self::RECURSION_DEPTH => 512,
41+
);
2742

2843
/**
2944
* Constructs a new JsonDecode instance.
3045
*
31-
* @param bool $associative True to return the result associative array, false for a nested stdClass hierarchy
32-
* @param int $depth Specifies the recursion depth
46+
* @param array $defaultContext
3347
*/
34-
public function __construct(bool $associative = false, int $depth = 512)
48+
public function __construct($defaultContext = array(), int $depth = 512)
3549
{
36-
$this->associative = $associative;
37-
$this->recursionDepth = $depth;
50+
if (!\is_array($defaultContext)) {
51+
@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);
52+
53+
$defaultContext = array(
54+
self::ASSOCIATIVE => (bool) $defaultContext,
55+
self::RECURSION_DEPTH => $depth,
56+
);
57+
}
58+
59+
$this->defaultContext = array_merge($this->defaultContext, $defaultContext);
3860
}
3961

4062
/**
@@ -47,7 +69,7 @@ public function __construct(bool $associative = false, int $depth = 512)
4769
* The $context array is a simple key=>value array, with the following supported keys:
4870
*
4971
* json_decode_associative: boolean
50-
* If true, returns the object as associative array.
72+
* If true, returns the object as an associative array.
5173
* If false, returns the object as nested stdClass
5274
* If not specified, this method will use the default set in JsonDecode::__construct
5375
*
@@ -56,7 +78,7 @@ public function __construct(bool $associative = false, int $depth = 512)
5678
* If not specified, this method will use the default set in JsonDecode::__construct
5779
*
5880
* json_decode_options: integer
59-
* Specifies additional options as per documentation for json_decode.
81+
* Specifies additional options as per documentation for json_decode
6082
*
6183
* @return mixed
6284
*
@@ -66,11 +88,9 @@ public function __construct(bool $associative = false, int $depth = 512)
6688
*/
6789
public function decode($data, $format, array $context = array())
6890
{
69-
$context = $this->resolveContext($context);
70-
71-
$associative = $context['json_decode_associative'];
72-
$recursionDepth = $context['json_decode_recursion_depth'];
73-
$options = $context['json_decode_options'];
91+
$associative = $context[self::ASSOCIATIVE] ?? $this->defaultContext[self::ASSOCIATIVE];
92+
$recursionDepth = $context[self::RECURSION_DEPTH] ?? $this->defaultContext[self::RECURSION_DEPTH];
93+
$options = $context[self::OPTIONS] ?? $this->defaultContext[self::OPTIONS];
7494

7595
$decodedData = json_decode($data, $associative, $recursionDepth, $options);
7696

@@ -88,20 +108,4 @@ public function supportsDecoding($format)
88108
{
89109
return JsonEncoder::FORMAT === $format;
90110
}
91-
92-
/**
93-
* Merges the default options of the Json Decoder with the passed context.
94-
*
95-
* @return array
96-
*/
97-
private function resolveContext(array $context)
98-
{
99-
$defaultOptions = array(
100-
'json_decode_associative' => $this->associative,
101-
'json_decode_recursion_depth' => $this->recursionDepth,
102-
'json_decode_options' => 0,
103-
);
104-
105-
return array_merge($defaultOptions, $context);
106-
}
107111
}

src/Symfony/Component/Serializer/Encoder/JsonEncode.php

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,24 @@
2020
*/
2121
class JsonEncode implements EncoderInterface
2222
{
23-
private $options;
23+
const OPTIONS = 'json_encode_options';
2424

25-
public function __construct(int $bitmask = 0)
25+
private $defaultContext = array(
26+
self::OPTIONS => 0,
27+
);
28+
29+
/**
30+
* @param array $defaultContext
31+
*/
32+
public function __construct($defaultContext = array())
2633
{
27-
$this->options = $bitmask;
34+
if (!\is_array($defaultContext)) {
35+
@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);
36+
37+
$this->defaultContext[self::OPTIONS] = (int) $defaultContext;
38+
} else {
39+
$this->defaultContext = array_merge($this->defaultContext, $defaultContext);
40+
}
2841
}
2942

3043
/**
@@ -34,11 +47,10 @@ public function __construct(int $bitmask = 0)
3447
*/
3548
public function encode($data, $format, array $context = array())
3649
{
37-
$context = $this->resolveContext($context);
38-
39-
$encodedJson = json_encode($data, $context['json_encode_options']);
50+
$jsonEncodeOptions = $context[self::OPTIONS] ?? $this->defaultContext[self::OPTIONS];
51+
$encodedJson = json_encode($data, $jsonEncodeOptions);
4052

41-
if (JSON_ERROR_NONE !== json_last_error() && (false === $encodedJson || !($context['json_encode_options'] & JSON_PARTIAL_OUTPUT_ON_ERROR))) {
53+
if (JSON_ERROR_NONE !== json_last_error() && (false === $encodedJson || !($jsonEncodeOptions & JSON_PARTIAL_OUTPUT_ON_ERROR))) {
4254
throw new NotEncodableValueException(json_last_error_msg());
4355
}
4456

@@ -52,14 +64,4 @@ public function supportsEncoding($format)
5264
{
5365
return JsonEncoder::FORMAT === $format;
5466
}
55-
56-
/**
57-
* Merge default json encode options with context.
58-
*
59-
* @return array
60-
*/
61-
private function resolveContext(array $context = array())
62-
{
63-
return array_merge(array('json_encode_options' => $this->options), $context);
64-
}
6567
}

src/Symfony/Component/Serializer/Encoder/JsonEncoder.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class JsonEncoder implements EncoderInterface, DecoderInterface
2626
public function __construct(JsonEncode $encodingImpl = null, JsonDecode $decodingImpl = null)
2727
{
2828
$this->encodingImpl = $encodingImpl ?: new JsonEncode();
29-
$this->decodingImpl = $decodingImpl ?: new JsonDecode(true);
29+
$this->decodingImpl = $decodingImpl ?: new JsonDecode(array(JsonDecode::ASSOCIATIVE => true));
3030
}
3131

3232
/**

0 commit comments

Comments
 (0)
0