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:
commit
1cef186dfe
@ -21,6 +21,10 @@ use Symfony\Component\Serializer\Exception\InvalidArgumentException;
|
|||||||
class CsvEncoder implements EncoderInterface, DecoderInterface
|
class CsvEncoder implements EncoderInterface, DecoderInterface
|
||||||
{
|
{
|
||||||
const FORMAT = 'csv';
|
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 $delimiter;
|
||||||
private $enclosure;
|
private $enclosure;
|
||||||
@ -65,19 +69,21 @@ class CsvEncoder implements EncoderInterface, DecoderInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
list($delimiter, $enclosure, $escapeChar, $keySeparator) = $this->getCsvOptions($context);
|
||||||
|
|
||||||
$headers = null;
|
$headers = null;
|
||||||
foreach ($data as $value) {
|
foreach ($data as $value) {
|
||||||
$result = array();
|
$result = array();
|
||||||
$this->flatten($value, $result);
|
$this->flatten($value, $result, $keySeparator);
|
||||||
|
|
||||||
if (null === $headers) {
|
if (null === $headers) {
|
||||||
$headers = array_keys($result);
|
$headers = array_keys($result);
|
||||||
fputcsv($handle, $headers, $this->delimiter, $this->enclosure, $this->escapeChar);
|
fputcsv($handle, $headers, $delimiter, $enclosure, $escapeChar);
|
||||||
} elseif (array_keys($result) !== $headers) {
|
} 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.');
|
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);
|
rewind($handle);
|
||||||
@ -108,14 +114,16 @@ class CsvEncoder implements EncoderInterface, DecoderInterface
|
|||||||
$nbHeaders = 0;
|
$nbHeaders = 0;
|
||||||
$result = array();
|
$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);
|
$nbCols = count($cols);
|
||||||
|
|
||||||
if (null === $headers) {
|
if (null === $headers) {
|
||||||
$nbHeaders = $nbCols;
|
$nbHeaders = $nbCols;
|
||||||
|
|
||||||
foreach ($cols as $col) {
|
foreach ($cols as $col) {
|
||||||
$headers[] = explode($this->keySeparator, $col);
|
$headers[] = explode($keySeparator, $col);
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
@ -166,16 +174,27 @@ class CsvEncoder implements EncoderInterface, DecoderInterface
|
|||||||
*
|
*
|
||||||
* @param array $array
|
* @param array $array
|
||||||
* @param array $result
|
* @param array $result
|
||||||
|
* @param string $keySeparator
|
||||||
* @param string $parentKey
|
* @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) {
|
foreach ($array as $key => $value) {
|
||||||
if (is_array($value)) {
|
if (is_array($value)) {
|
||||||
$this->flatten($value, $result, $parentKey.$key.$this->keySeparator);
|
$this->flatten($value, $result, $keySeparator, $parentKey.$key.$keySeparator);
|
||||||
} else {
|
} else {
|
||||||
$result[$parentKey.$key] = $value;
|
$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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -112,6 +112,23 @@ CSV
|
|||||||
, $this->encoder->encode($value, '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()
|
public function testEncodeEmptyArray()
|
||||||
{
|
{
|
||||||
$this->assertEquals("\n\n", $this->encoder->encode(array(), 'csv'));
|
$this->assertEquals("\n\n", $this->encoder->encode(array(), 'csv'));
|
||||||
@ -207,6 +224,21 @@ CSV
|
|||||||
, '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()
|
public function testDecodeMalformedCollection()
|
||||||
{
|
{
|
||||||
$expected = array(
|
$expected = array(
|
||||||
|
Reference in New Issue
Block a user