Skip to content

Commit 5a91f0f

Browse files
committed
Merge branch '3.4'
* 3.4: fixed CS [Serializer] Add Support for in CustomNormalizer Remove Validator\TypeTestCase and add validator logic to base TypeTestCase [Lock] Include lock component in framework bundle [WebProfilerBundle] Render file links for twig templates CsvEncoder handling variable structures and custom header order Saltless Encoder Interface [Serializer] throw more specific exceptions # Conflicts: # src/Symfony/Bundle/FrameworkBundle/composer.json # src/Symfony/Bundle/SecurityBundle/Command/UserPasswordEncoderCommand.php # src/Symfony/Component/Serializer/Encoder/XmlEncoder.php # src/Symfony/Component/Serializer/Normalizer/AbstractNormalizer.php # src/Symfony/Component/Serializer/Serializer.php
2 parents 5956330 + 81fa7d0 commit 5a91f0f

18 files changed

+275
-58
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ CHANGELOG
1919
to disable throwing an `UnexpectedValueException` on a type mismatch
2020
* added support for serializing `DateInterval` objects
2121
* added getter for extra attributes in `ExtraAttributesException`
22+
* improved `CsvEncoder` to handle variable nested structures
23+
* CSV headers can be passed to the `CsvEncoder` via the `csv_headers` serialization context variable
2224

2325
3.3.0
2426
-----

Encoder/CsvEncoder.php

Lines changed: 59 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
* Encodes CSV data.
1818
*
1919
* @author Kévin Dunglas <[email protected]>
20+
* @author Oliver Hoff <[email protected]>
2021
*/
2122
class CsvEncoder implements EncoderInterface, DecoderInterface
2223
{
@@ -25,6 +26,7 @@ class CsvEncoder implements EncoderInterface, DecoderInterface
2526
const ENCLOSURE_KEY = 'csv_enclosure';
2627
const ESCAPE_CHAR_KEY = 'csv_escape_char';
2728
const KEY_SEPARATOR_KEY = 'csv_key_separator';
29+
const HEADERS_KEY = 'csv_headers';
2830

2931
private $delimiter;
3032
private $enclosure;
@@ -69,21 +71,22 @@ public function encode($data, $format, array $context = array())
6971
}
7072
}
7173

72-
list($delimiter, $enclosure, $escapeChar, $keySeparator) = $this->getCsvOptions($context);
74+
list($delimiter, $enclosure, $escapeChar, $keySeparator, $headers) = $this->getCsvOptions($context);
7375

74-
$headers = null;
75-
foreach ($data as $value) {
76-
$result = array();
77-
$this->flatten($value, $result, $keySeparator);
76+
foreach ($data as &$value) {
77+
$flattened = array();
78+
$this->flatten($value, $flattened, $keySeparator);
79+
$value = $flattened;
80+
}
81+
unset($value);
7882

79-
if (null === $headers) {
80-
$headers = array_keys($result);
81-
fputcsv($handle, $headers, $delimiter, $enclosure, $escapeChar);
82-
} elseif (array_keys($result) !== $headers) {
83-
throw new InvalidArgumentException('To use the CSV encoder, each line in the data array must have the same structure. You may want to use a custom normalizer class to normalize the data format before passing it to the CSV encoder.');
84-
}
83+
$headers = array_merge(array_values($headers), array_diff($this->extractHeaders($data), $headers));
84+
85+
fputcsv($handle, $headers, $delimiter, $enclosure, $escapeChar);
8586

86-
fputcsv($handle, $result, $delimiter, $enclosure, $escapeChar);
87+
$headers = array_fill_keys($headers, '');
88+
foreach ($data as $row) {
89+
fputcsv($handle, array_replace($headers, $row), $delimiter, $enclosure, $escapeChar);
8790
}
8891

8992
rewind($handle);
@@ -194,7 +197,50 @@ private function getCsvOptions(array $context)
194197
$enclosure = isset($context[self::ENCLOSURE_KEY]) ? $context[self::ENCLOSURE_KEY] : $this->enclosure;
195198
$escapeChar = isset($context[self::ESCAPE_CHAR_KEY]) ? $context[self::ESCAPE_CHAR_KEY] : $this->escapeChar;
196199
$keySeparator = isset($context[self::KEY_SEPARATOR_KEY]) ? $context[self::KEY_SEPARATOR_KEY] : $this->keySeparator;
200+
$headers = isset($context[self::HEADERS_KEY]) ? $context[self::HEADERS_KEY] : array();
201+
202+
if (!is_array($headers)) {
203+
throw new InvalidArgumentException(sprintf('The "%s" context variable must be an array or null, given "%s".', self::HEADERS_KEY, gettype($headers)));
204+
}
205+
206+
return array($delimiter, $enclosure, $escapeChar, $keySeparator, $headers);
207+
}
208+
209+
/**
210+
* @param array $data
211+
*
212+
* @return string[]
213+
*/
214+
private function extractHeaders(array $data)
215+
{
216+
$headers = array();
217+
$flippedHeaders = array();
218+
219+
foreach ($data as $row) {
220+
$previousHeader = null;
221+
222+
foreach ($row as $header => $_) {
223+
if (isset($flippedHeaders[$header])) {
224+
$previousHeader = $header;
225+
continue;
226+
}
227+
228+
if (null === $previousHeader) {
229+
$n = count($headers);
230+
} else {
231+
$n = $flippedHeaders[$previousHeader] + 1;
232+
233+
for ($j = count($headers); $j > $n; --$j) {
234+
++$flippedHeaders[$headers[$j] = $headers[$j - 1]];
235+
}
236+
}
237+
238+
$headers[$n] = $header;
239+
$flippedHeaders[$header] = $n;
240+
$previousHeader = $header;
241+
}
242+
}
197243

198-
return array($delimiter, $enclosure, $escapeChar, $keySeparator);
244+
return $headers;
199245
}
200246
}

Encoder/JsonDecode.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
namespace Symfony\Component\Serializer\Encoder;
1313

14-
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
14+
use Symfony\Component\Serializer\Exception\NotEncodableValueException;
1515

1616
/**
1717
* Decodes JSON data.
@@ -73,7 +73,7 @@ public function __construct($associative = false, $depth = 512)
7373
*
7474
* @return mixed
7575
*
76-
* @throws UnexpectedValueException
76+
* @throws NotEncodableValueException
7777
*
7878
* @see http://php.net/json_decode json_decode
7979
*/
@@ -88,7 +88,7 @@ public function decode($data, $format, array $context = array())
8888
$decodedData = json_decode($data, $associative, $recursionDepth, $options);
8989

9090
if (JSON_ERROR_NONE !== $this->lastError = json_last_error()) {
91-
throw new UnexpectedValueException(json_last_error_msg());
91+
throw new NotEncodableValueException(json_last_error_msg());
9292
}
9393

9494
return $decodedData;

Encoder/JsonEncode.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
namespace Symfony\Component\Serializer\Encoder;
1313

14-
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
14+
use Symfony\Component\Serializer\Exception\NotEncodableValueException;
1515

1616
/**
1717
* Encodes JSON data.
@@ -40,7 +40,7 @@ public function encode($data, $format, array $context = array())
4040
$encodedJson = json_encode($data, $context['json_encode_options']);
4141

4242
if (JSON_ERROR_NONE !== $this->lastError = json_last_error()) {
43-
throw new UnexpectedValueException(json_last_error_msg());
43+
throw new NotEncodableValueException(json_last_error_msg());
4444
}
4545

4646
return $encodedJson;

Encoder/XmlEncoder.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
namespace Symfony\Component\Serializer\Encoder;
1313

14-
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
14+
use Symfony\Component\Serializer\Exception\NotEncodableValueException;
1515
use Symfony\Component\Serializer\SerializerAwareInterface;
1616
use Symfony\Component\Serializer\SerializerAwareTrait;
1717

@@ -82,7 +82,7 @@ public function encode($data, $format, array $context = array())
8282
public function decode($data, $format, array $context = array())
8383
{
8484
if ('' === trim($data)) {
85-
throw new UnexpectedValueException('Invalid XML data, it can not be empty.');
85+
throw new NotEncodableValueException('Invalid XML data, it can not be empty.');
8686
}
8787

8888
$internalErrors = libxml_use_internal_errors(true);
@@ -98,13 +98,13 @@ public function decode($data, $format, array $context = array())
9898
if ($error = libxml_get_last_error()) {
9999
libxml_clear_errors();
100100

101-
throw new UnexpectedValueException($error->message);
101+
throw new NotEncodableValueException($error->message);
102102
}
103103

104104
$rootNode = null;
105105
foreach ($dom->childNodes as $child) {
106106
if (XML_DOCUMENT_TYPE_NODE === $child->nodeType) {
107-
throw new UnexpectedValueException('Document types are not allowed.');
107+
throw new NotEncodableValueException('Document types are not allowed.');
108108
}
109109
if (!$rootNode && XML_PI_NODE !== $child->nodeType) {
110110
$rootNode = $child;
@@ -384,7 +384,7 @@ private function parseXmlValue(\DOMNode $node, array $context = array())
384384
*
385385
* @return bool
386386
*
387-
* @throws UnexpectedValueException
387+
* @throws NotEncodableValueException
388388
*/
389389
private function buildXml(\DOMNode $parentNode, $data, $xmlRootNodeName = null)
390390
{
@@ -441,7 +441,7 @@ private function buildXml(\DOMNode $parentNode, $data, $xmlRootNodeName = null)
441441
return $this->appendNode($parentNode, $data, 'data');
442442
}
443443

444-
throw new UnexpectedValueException(sprintf('An unexpected value could not be serialized: %s', var_export($data, true)));
444+
throw new NotEncodableValueException(sprintf('An unexpected value could not be serialized: %s', var_export($data, true)));
445445
}
446446

447447
/**
@@ -489,7 +489,7 @@ private function needsCdataWrapping($val)
489489
*
490490
* @return bool
491491
*
492-
* @throws UnexpectedValueException
492+
* @throws NotEncodableValueException
493493
*/
494494
private function selectNodeType(\DOMNode $node, $val)
495495
{
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Serializer\Exception;
13+
14+
/**
15+
* @author Christian Flothmann <[email protected]>
16+
*/
17+
class NotEncodableValueException extends UnexpectedValueException
18+
{
19+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Serializer\Exception;
13+
14+
/**
15+
* @author Christian Flothmann <[email protected]>
16+
*/
17+
class NotNormalizableValueException extends UnexpectedValueException
18+
{
19+
}

Normalizer/AbstractNormalizer.php

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
*/
2929
abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface
3030
{
31+
use ObjectToPopulateTrait;
3132
use SerializerAwareTrait;
3233

3334
const CIRCULAR_REFERENCE_LIMIT = 'circular_reference_limit';
@@ -307,12 +308,7 @@ protected function getConstructor(array &$data, $class, array &$context, \Reflec
307308
*/
308309
protected function instantiateObject(array &$data, $class, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes, string $format = null)
309310
{
310-
if (
311-
isset($context[static::OBJECT_TO_POPULATE]) &&
312-
is_object($context[static::OBJECT_TO_POPULATE]) &&
313-
$context[static::OBJECT_TO_POPULATE] instanceof $class
314-
) {
315-
$object = $context[static::OBJECT_TO_POPULATE];
311+
if (null !== $object = $this->extractObjectToPopulate($class, $context, static::OBJECT_TO_POPULATE)) {
316312
unset($context[static::OBJECT_TO_POPULATE]);
317313

318314
return $object;

Normalizer/AbstractObjectNormalizer.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
use Symfony\Component\Serializer\Encoder\JsonEncoder;
1616
use Symfony\Component\Serializer\Exception\ExtraAttributesException;
1717
use Symfony\Component\Serializer\Exception\LogicException;
18-
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
18+
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
1919
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
2020
use Symfony\Component\PropertyInfo\Type;
2121
use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface;
@@ -200,7 +200,7 @@ public function denormalize($data, $class, $format = null, array $context = arra
200200
try {
201201
$this->setAttributeValue($object, $attribute, $value, $format, $context);
202202
} catch (InvalidArgumentException $e) {
203-
throw new UnexpectedValueException($e->getMessage(), $e->getCode(), $e);
203+
throw new NotNormalizableValueException($e->getMessage(), $e->getCode(), $e);
204204
}
205205
}
206206

@@ -233,7 +233,7 @@ abstract protected function setAttributeValue($object, $attribute, $value, $form
233233
*
234234
* @return mixed
235235
*
236-
* @throws UnexpectedValueException
236+
* @throws NotNormalizableValueException
237237
* @throws LogicException
238238
*/
239239
private function validateAndDenormalize($currentClass, $attribute, $data, $format, array $context)
@@ -292,7 +292,7 @@ private function validateAndDenormalize($currentClass, $attribute, $data, $forma
292292
return $data;
293293
}
294294

295-
throw new UnexpectedValueException(sprintf('The type of the "%s" attribute for class "%s" must be one of "%s" ("%s" given).', $attribute, $currentClass, implode('", "', array_keys($expectedTypes)), gettype($data)));
295+
throw new NotNormalizableValueException(sprintf('The type of the "%s" attribute for class "%s" must be one of "%s" ("%s" given).', $attribute, $currentClass, implode('", "', array_keys($expectedTypes)), gettype($data)));
296296
}
297297

298298
/**

Normalizer/ArrayDenormalizer.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
use Symfony\Component\Serializer\Exception\BadMethodCallException;
1515
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
16-
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
16+
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
1717
use Symfony\Component\Serializer\SerializerAwareInterface;
1818
use Symfony\Component\Serializer\SerializerInterface;
1919

@@ -33,6 +33,8 @@ class ArrayDenormalizer implements ContextAwareDenormalizerInterface, Serializer
3333

3434
/**
3535
* {@inheritdoc}
36+
*
37+
* @throws NotNormalizableValueException
3638
*/
3739
public function denormalize($data, $class, $format = null, array $context = array())
3840
{
@@ -52,7 +54,7 @@ public function denormalize($data, $class, $format = null, array $context = arra
5254
$builtinType = isset($context['key_type']) ? $context['key_type']->getBuiltinType() : null;
5355
foreach ($data as $key => $value) {
5456
if (null !== $builtinType && !call_user_func('is_'.$builtinType, $key)) {
55-
throw new UnexpectedValueException(sprintf('The type of the key "%s" must be "%s" ("%s" given).', $key, $builtinType, gettype($key)));
57+
throw new NotNormalizableValueException(sprintf('The type of the key "%s" must be "%s" ("%s" given).', $key, $builtinType, gettype($key)));
5658
}
5759

5860
$data[$key] = $serializer->denormalize($value, $class, $format, $context);

0 commit comments

Comments
 (0)