Skip to content

Commit 859eb91

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 620094a commit 859eb91

File tree

11 files changed

+114
-5
lines changed

11 files changed

+114
-5
lines changed

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

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

56-
if (!\is_array($data)) {
56+
if (!is_iterable($data)) {
5757
$data = array(array($data));
5858
} elseif (empty($data)) {
5959
$data = array(array());
@@ -185,10 +185,10 @@ public function supportsDecoding($format)
185185
/**
186186
* Flattens an array and generates keys including the path.
187187
*/
188-
private function flatten(array $array, array &$result, string $keySeparator, string $parentKey = '', bool $escapeFormulas = false)
188+
private function flatten($array, array &$result, string $keySeparator, string $parentKey = '', bool $escapeFormulas = false)
189189
{
190190
foreach ($array as $key => $value) {
191-
if (\is_array($value)) {
191+
if (is_iterable($value)) {
192192
$this->flatten($value, $result, $keySeparator, $parentKey.$key.$keySeparator, $escapeFormulas);
193193
} else {
194194
if ($escapeFormulas && \in_array(substr($value, 0, 1), $this->formulasStartCharacters, true)) {
@@ -219,7 +219,7 @@ private function getCsvOptions(array $context)
219219
/**
220220
* @return string[]
221221
*/
222-
private function extractHeaders(array $data)
222+
private function extractHeaders(iterable $data)
223223
{
224224
$headers = array();
225225
$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.
@@ -24,6 +25,8 @@ class YamlEncoder implements EncoderInterface, DecoderInterface
2425
{
2526
const FORMAT = 'yaml';
2627

28+
const ARRAY_OBJECTS_KEY = 'array_objects';
29+
2730
private $dumper;
2831
private $parser;
2932
private $defaultContext = array('yaml_inline' => 0, 'yaml_indent' => 0, 'yaml_flags' => 0);
@@ -46,6 +49,10 @@ public function encode($data, $format, array $context = array())
4649
{
4750
$context = array_merge($this->defaultContext, $context);
4851

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

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer
3737
const DISABLE_TYPE_ENFORCEMENT = 'disable_type_enforcement';
3838
const SKIP_NULL_VALUES = 'skip_null_values';
3939

40+
const ARRAY_OBJECTS_KEY = 'array_objects';
41+
4042
private $propertyTypeExtractor;
4143
private $typesCache = array();
4244
private $attributesCache = array();
@@ -122,6 +124,10 @@ public function normalize($object, $format = null, array $context = array())
122124
$data = $this->updateData($data, $attribute, $this->serializer->normalize($attributeValue, $format, $this->createChildContext($context, $attribute)), $class, $format, $context);
123125
}
124126

127+
if (isset($context[self::ARRAY_OBJECTS_KEY]) && !\count($data)) {
128+
return new \ArrayObject();
129+
}
130+
125131
return $data;
126132
}
127133

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
@@ -276,6 +276,43 @@ public function testEncodeFormulasWithSettingsPassedInContext()
276276
)));
277277
}
278278

279+
public function testEncodeArrayObject()
280+
{
281+
$value = new \ArrayObject(array('foo' => 'hello', 'bar' => 'hey ho'));
282+
283+
$this->assertEquals(<<<'CSV'
284+
foo,bar
285+
hello,"hey ho"
286+
287+
CSV
288+
, $this->encoder->encode($value, 'csv'));
289+
290+
$value = new \ArrayObject();
291+
292+
$this->assertEquals("\n", $this->encoder->encode($value, 'csv'));
293+
}
294+
295+
public function testEncodeNestedArrayObject()
296+
{
297+
$value = new \ArrayObject(array('foo' => new \ArrayObject(array('nested' => 'value')), 'bar' => new \ArrayObject(array('another' => 'word'))));
298+
299+
$this->assertEquals(<<<'CSV'
300+
foo.nested,bar.another
301+
value,word
302+
303+
CSV
304+
, $this->encoder->encode($value, 'csv'));
305+
}
306+
307+
public function testEncodeEmptyArrayObject()
308+
{
309+
$value = new \ArrayObject();
310+
$this->assertEquals("\n", $this->encoder->encode($value, 'csv'));
311+
312+
$value = array('foo' => new \ArrayObject());
313+
$this->assertEquals("\n\n", $this->encoder->encode($value, 'csv'));
314+
}
315+
279316
public function testSupportsDecoding()
280317
{
281318
$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
@@ -47,6 +47,26 @@ public function testEncodeScalar()
4747
$this->assertEquals($expected, $this->encoder->encode($obj, 'xml'));
4848
}
4949

50+
public function testEncodeArrayObject()
51+
{
52+
$obj = new \ArrayObject(array('foo' => 'bar'));
53+
54+
$expected = '<?xml version="1.0"?>'."\n".
55+
'<response><foo>bar</foo></response>'."\n";
56+
57+
$this->assertEquals($expected, $this->encoder->encode($obj, 'xml'));
58+
}
59+
60+
public function testEncodeEmptyArrayObject()
61+
{
62+
$obj = new \ArrayObject();
63+
64+
$expected = '<?xml version="1.0"?>'."\n".
65+
'<response/>'."\n";
66+
67+
$this->assertEquals($expected, $this->encoder->encode($obj, 'xml'));
68+
}
69+
5070
public function testSetRootNodeName()
5171
{
5272
$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
@@ -156,6 +156,11 @@ public function testIgnoredAttributes()
156156
array(),
157157
$this->normalizer->normalize($obj, 'any')
158158
);
159+
160+
$this->assertEquals(
161+
new \ArrayObject(),
162+
$this->normalizer->normalize($obj, 'any', array('array_objects' => true))
163+
);
159164
}
160165

161166
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