Skip to content

Commit 4341fea

Browse files
committed
[Serializer] Encode empty objects as objects, not arrays
Allows Normalizers to return a representation of an empty object that the encoder recognizes as such.
1 parent 73032f3 commit 4341fea

File tree

11 files changed

+115
-6
lines changed

11 files changed

+115
-6
lines changed

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ public function encode($data, $format, array $context = array())
6767
{
6868
$handle = fopen('php://temp,', 'w+');
6969

70-
if (!\is_array($data)) {
70+
if (!is_iterable($data)) {
7171
$data = array(array($data));
7272
} elseif (empty($data)) {
7373
$data = array(array());
@@ -199,10 +199,10 @@ public function supportsDecoding($format)
199199
/**
200200
* Flattens an array and generates keys including the path.
201201
*/
202-
private function flatten(array $array, array &$result, string $keySeparator, string $parentKey = '', bool $escapeFormulas = false)
202+
private function flatten($array, array &$result, string $keySeparator, string $parentKey = '', bool $escapeFormulas = false)
203203
{
204204
foreach ($array as $key => $value) {
205-
if (\is_array($value)) {
205+
if (is_iterable($value)) {
206206
$this->flatten($value, $result, $keySeparator, $parentKey.$key.$keySeparator, $escapeFormulas);
207207
} else {
208208
if ($escapeFormulas && \in_array(substr($value, 0, 1), $this->formulasStartCharacters, true)) {
@@ -233,7 +233,7 @@ private function getCsvOptions(array $context): array
233233
/**
234234
* @return string[]
235235
*/
236-
private function extractHeaders(array $data)
236+
private function extractHeaders(iterable $data)
237237
{
238238
$headers = array();
239239
$flippedHeaders = array();

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Symfony\Component\Serializer\Exception\RuntimeException;
1515
use Symfony\Component\Yaml\Dumper;
1616
use Symfony\Component\Yaml\Parser;
17+
use Symfony\Component\Yaml\Yaml;
1718

1819
/**
1920
* Encodes YAML data.
@@ -25,6 +26,8 @@ class YamlEncoder implements EncoderInterface, DecoderInterface
2526
const FORMAT = 'yaml';
2627
private const ALTERNATIVE_FORMAT = 'yml';
2728

29+
public const ARRAY_OBJECTS_KEY = 'array_objects';
30+
2831
private $dumper;
2932
private $parser;
3033
private $defaultContext = array('yaml_inline' => 0, 'yaml_indent' => 0, 'yaml_flags' => 0);
@@ -47,6 +50,10 @@ public function encode($data, $format, array $context = array())
4750
{
4851
$context = array_merge($this->defaultContext, $context);
4952

53+
if (isset($context[self::ARRAY_OBJECTS_KEY])) {
54+
$context['yaml_flags'] |= Yaml::DUMP_OBJECT_AS_MAP;
55+
}
56+
5057
return $this->dumper->dump($data, $context['yaml_inline'], $context['yaml_indent'], $context['yaml_flags']);
5158
}
5259

src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer
3939
const MAX_DEPTH_HANDLER = 'max_depth_handler';
4040
const EXCLUDE_FROM_CACHE_KEY = 'exclude_from_cache_key';
4141

42+
public const ARRAY_OBJECTS_KEY = 'array_objects';
43+
4244
private $propertyTypeExtractor;
4345
private $typesCache = array();
4446
private $attributesCache = array();
@@ -110,7 +112,7 @@ public function normalize($object, $format = null, array $context = array())
110112
}
111113

112114
/**
113-
* @var $callback callable|null
115+
* @var callable|null
114116
*/
115117
$callback = $context[self::CALLBACKS][$attribute] ?? $this->defaultContext[self::CALLBACKS][$attribute] ?? $this->callbacks[$attribute] ?? null;
116118
if ($callback) {
@@ -132,6 +134,10 @@ public function normalize($object, $format = null, array $context = array())
132134
$data = $this->updateData($data, $attribute, $this->serializer->normalize($attributeValue, $format, $this->createChildContext($context, $attribute)), $class, $format, $context);
133135
}
134136

137+
if (isset($context[self::ARRAY_OBJECTS_KEY]) && !\count($data)) {
138+
return new \ArrayObject();
139+
}
140+
135141
return $data;
136142
}
137143

src/Symfony/Component/Serializer/Normalizer/NormalizerInterface.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ interface NormalizerInterface
2929
* @param string $format Format the normalization result will be encoded as
3030
* @param array $context Context options for the normalizer
3131
*
32-
* @return array|string|int|float|bool
32+
* @return array|string|int|float|bool|\ArrayObject
3333
*
3434
* @throws InvalidArgumentException Occurs when the object given is not an attempted type for the normalizer
3535
* @throws CircularReferenceException Occurs when the normalizer detects a circular reference when no circular

src/Symfony/Component/Serializer/Tests/Encoder/CsvEncoderTest.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,43 @@ public function testEncodeFormulasWithSettingsPassedInContext()
309309
)));
310310
}
311311

312+
public function testEncodeArrayObject()
313+
{
314+
$value = new \ArrayObject(array('foo' => 'hello', 'bar' => 'hey ho'));
315+
316+
$this->assertEquals(<<<'CSV'
317+
foo,bar
318+
hello,"hey ho"
319+
320+
CSV
321+
, $this->encoder->encode($value, 'csv'));
322+
323+
$value = new \ArrayObject();
324+
325+
$this->assertEquals("\n", $this->encoder->encode($value, 'csv'));
326+
}
327+
328+
public function testEncodeNestedArrayObject()
329+
{
330+
$value = new \ArrayObject(array('foo' => new \ArrayObject(array('nested' => 'value')), 'bar' => new \ArrayObject(array('another' => 'word'))));
331+
332+
$this->assertEquals(<<<'CSV'
333+
foo.nested,bar.another
334+
value,word
335+
336+
CSV
337+
, $this->encoder->encode($value, 'csv'));
338+
}
339+
340+
public function testEncodeEmptyArrayObject()
341+
{
342+
$value = new \ArrayObject();
343+
$this->assertEquals("\n", $this->encoder->encode($value, 'csv'));
344+
345+
$value = array('foo' => new \ArrayObject());
346+
$this->assertEquals("\n\n", $this->encoder->encode($value, 'csv'));
347+
}
348+
312349
public function testSupportsDecoding()
313350
{
314351
$this->assertTrue($this->encoder->supportsDecoding('csv'));

src/Symfony/Component/Serializer/Tests/Encoder/JsonEncodeTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ public function encodeProvider()
4646
return array(
4747
array(array(), '[]', array()),
4848
array(array(), '{}', array('json_encode_options' => JSON_FORCE_OBJECT)),
49+
array(new \ArrayObject(), '{}', array()),
50+
array(new \ArrayObject(array('foo' => 'bar')), '{"foo":"bar"}', array()),
4951
);
5052
}
5153

src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,26 @@ public function testEncodeScalar()
5050
/**
5151
* @group legacy
5252
*/
53+
public function testEncodeArrayObject()
54+
{
55+
$obj = new \ArrayObject(array('foo' => 'bar'));
56+
57+
$expected = '<?xml version="1.0"?>'."\n".
58+
'<response><foo>bar</foo></response>'."\n";
59+
60+
$this->assertEquals($expected, $this->encoder->encode($obj, 'xml'));
61+
}
62+
63+
public function testEncodeEmptyArrayObject()
64+
{
65+
$obj = new \ArrayObject();
66+
67+
$expected = '<?xml version="1.0"?>'."\n".
68+
'<response/>'."\n";
69+
70+
$this->assertEquals($expected, $this->encoder->encode($obj, 'xml'));
71+
}
72+
5373
public function testSetRootNodeName()
5474
{
5575
$obj = new ScalarDummy();

src/Symfony/Component/Serializer/Tests/Encoder/YamlEncoderTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ public function testEncode()
2828

2929
$this->assertEquals('foo', $encoder->encode('foo', 'yaml'));
3030
$this->assertEquals('{ foo: 1 }', $encoder->encode(array('foo' => 1), 'yaml'));
31+
$this->assertEquals('null', $encoder->encode(new \ArrayObject(array('foo' => 1)), 'yaml'));
32+
$this->assertEquals('{ foo: 1 }', $encoder->encode(new \ArrayObject(array('foo' => 1)), 'yaml', array('array_objects' => true)));
3133
}
3234

3335
public function testSupportsEncoding()

src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,12 +171,25 @@ public function testSkipNullValues()
171171
$result = $normalizer->normalize($dummy, null, array(AbstractObjectNormalizer::SKIP_NULL_VALUES => true));
172172
$this->assertSame(array('bar' => 'present'), $result);
173173
}
174+
175+
public function testNormalizeEmptyObject()
176+
{
177+
$normalizer = new AbstractObjectNormalizerDummy();
178+
179+
// This results in objects turning into arrays in some encoders
180+
$normalizedData = $normalizer->normalize(new EmptyDummy());
181+
$this->assertEquals(array(), $normalizedData);
182+
183+
$normalizedData = $normalizer->normalize(new EmptyDummy(), 'any', array('array_objects' => true));
184+
$this->assertEquals(new \ArrayObject(), $normalizedData);
185+
}
174186
}
175187

176188
class AbstractObjectNormalizerDummy extends AbstractObjectNormalizer
177189
{
178190
protected function extractAttributes($object, $format = null, array $context = array())
179191
{
192+
return array();
180193
}
181194

182195
protected function getAttributeValue($object, $attribute, $format = null, array $context = array())
@@ -206,6 +219,10 @@ class Dummy
206219
public $baz;
207220
}
208221

222+
class EmptyDummy
223+
{
224+
}
225+
209226
class AbstractObjectNormalizerWithMetadata extends AbstractObjectNormalizer
210227
{
211228
public function __construct()

src/Symfony/Component/Serializer/Tests/Normalizer/PropertyNormalizerTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,11 @@ private function doTestIgnoredAttributes(bool $legacy = false)
202202
array(),
203203
$this->normalizer->normalize($obj, 'any')
204204
);
205+
206+
$this->assertEquals(
207+
new \ArrayObject(),
208+
$this->normalizer->normalize($obj, 'any', array('array_objects' => true))
209+
);
205210
}
206211

207212
public function testGroupsNormalize()

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy