feature #22537 [Serializer] Allow to pass csv encoder options in context (ogizanagi)

This PR was merged into the 3.3-dev branch.

Discussion
----------

[Serializer] Allow to pass csv encoder options in context

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | N/A
| License       | MIT
| Doc PR        | N/A

CSV contents typically are provided by one or many third-parties, not always allowing you to get control over the provided format. In case you need to import csv files with different formats, either you have to instantiate a decoder yourself/inject it instead of the main serializer instance, either you need another serializer instance with a differently configured csv encoder registered within.

This PR allows to configure any encoder option through the context, so you can keep injecting and using the same serializer instance.

Commits
-------

10a76aac15 [Serializer] Allow to pass csv encoder options in context
This commit is contained in:
Fabien Potencier 2017-04-29 08:50:00 -07:00
commit 1cef186dfe
2 changed files with 58 additions and 7 deletions

View File

@ -21,6 +21,10 @@ use Symfony\Component\Serializer\Exception\InvalidArgumentException;
class CsvEncoder implements EncoderInterface, DecoderInterface
{
const FORMAT = 'csv';
const DELIMITER_KEY = 'csv_delimiter';
const ENCLOSURE_KEY = 'csv_enclosure';
const ESCAPE_CHAR_KEY = 'csv_escape_char';
const KEY_SEPARATOR_KEY = 'csv_key_separator';
private $delimiter;
private $enclosure;
@ -65,19 +69,21 @@ class CsvEncoder implements EncoderInterface, DecoderInterface
}
}
list($delimiter, $enclosure, $escapeChar, $keySeparator) = $this->getCsvOptions($context);
$headers = null;
foreach ($data as $value) {
$result = array();
$this->flatten($value, $result);
$this->flatten($value, $result, $keySeparator);
if (null === $headers) {
$headers = array_keys($result);
fputcsv($handle, $headers, $this->delimiter, $this->enclosure, $this->escapeChar);
fputcsv($handle, $headers, $delimiter, $enclosure, $escapeChar);
} elseif (array_keys($result) !== $headers) {
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.');
}
fputcsv($handle, $result, $this->delimiter, $this->enclosure, $this->escapeChar);
fputcsv($handle, $result, $delimiter, $enclosure, $escapeChar);
}
rewind($handle);
@ -108,14 +114,16 @@ class CsvEncoder implements EncoderInterface, DecoderInterface
$nbHeaders = 0;
$result = array();
while (false !== ($cols = fgetcsv($handle, 0, $this->delimiter, $this->enclosure, $this->escapeChar))) {
list($delimiter, $enclosure, $escapeChar, $keySeparator) = $this->getCsvOptions($context);
while (false !== ($cols = fgetcsv($handle, 0, $delimiter, $enclosure, $escapeChar))) {
$nbCols = count($cols);
if (null === $headers) {
$nbHeaders = $nbCols;
foreach ($cols as $col) {
$headers[] = explode($this->keySeparator, $col);
$headers[] = explode($keySeparator, $col);
}
continue;
@ -166,16 +174,27 @@ class CsvEncoder implements EncoderInterface, DecoderInterface
*
* @param array $array
* @param array $result
* @param string $keySeparator
* @param string $parentKey
*/
private function flatten(array $array, array &$result, $parentKey = '')
private function flatten(array $array, array &$result, $keySeparator, $parentKey = '')
{
foreach ($array as $key => $value) {
if (is_array($value)) {
$this->flatten($value, $result, $parentKey.$key.$this->keySeparator);
$this->flatten($value, $result, $keySeparator, $parentKey.$key.$keySeparator);
} else {
$result[$parentKey.$key] = $value;
}
}
}
private function getCsvOptions(array $context)
{
$delimiter = isset($context[self::DELIMITER_KEY]) ? $context[self::DELIMITER_KEY] : $this->delimiter;
$enclosure = isset($context[self::ENCLOSURE_KEY]) ? $context[self::ENCLOSURE_KEY] : $this->enclosure;
$escapeChar = isset($context[self::ESCAPE_CHAR_KEY]) ? $context[self::ESCAPE_CHAR_KEY] : $this->escapeChar;
$keySeparator = isset($context[self::KEY_SEPARATOR_KEY]) ? $context[self::KEY_SEPARATOR_KEY] : $this->keySeparator;
return array($delimiter, $enclosure, $escapeChar, $keySeparator);
}
}

View File

@ -112,6 +112,23 @@ CSV
, $this->encoder->encode($value, 'csv'));
}
public function testEncodeCustomSettingsPassedInContext()
{
$value = array('a' => 'he\'llo', 'c' => array('d' => 'foo'));
$this->assertSame(<<<'CSV'
a;c-d
'he''llo';foo
CSV
, $this->encoder->encode($value, 'csv', array(
CsvEncoder::DELIMITER_KEY => ';',
CsvEncoder::ENCLOSURE_KEY => "'",
CsvEncoder::ESCAPE_CHAR_KEY => '|',
CsvEncoder::KEY_SEPARATOR_KEY => '-',
)));
}
public function testEncodeEmptyArray()
{
$this->assertEquals("\n\n", $this->encoder->encode(array(), 'csv'));
@ -207,6 +224,21 @@ CSV
, 'csv'));
}
public function testDecodeCustomSettingsPassedInContext()
{
$expected = array('a' => 'hell\'o', 'bar' => array('baz' => 'b'));
$this->assertEquals($expected, $this->encoder->decode(<<<'CSV'
a;bar-baz
'hell''o';b;c
CSV
, 'csv', array(
CsvEncoder::DELIMITER_KEY => ';',
CsvEncoder::ENCLOSURE_KEY => "'",
CsvEncoder::ESCAPE_CHAR_KEY => '|',
CsvEncoder::KEY_SEPARATOR_KEY => '-',
)));
}
public function testDecodeMalformedCollection()
{
$expected = array(