Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 47657e5

Browse files
committed
feature #19197 [Serializer][FrameworkBundle] Add a CSV encoder (dunglas)
This PR was squashed before being merged into the 3.2-dev branch (closes #19197). Discussion ---------- [Serializer][FrameworkBundle] Add a CSV encoder | 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 | todo Usage: ```php use Symfony\Component\Serializer\Serializer; use Symfony\Component\Serializer\Encoder\CsvEncoder; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; $serializer = new Serializer(array(new ObjectNormalizer()), array(new CsvEncoder())); // or $serializer = $container->get('serializer'); when using the full stack framework $serializer->encode($something, 'csv'); $serializer->decode(<<<'CSV' id,name 1,Kévin CSV , 'csv'); ``` CSV files must contain a header line with property names as keys. ping @clementtalleu @Simperfit @gorghoa Commits ------- e71f5be [Serializer][FrameworkBundle] Add a CSV encoder
2 parents d2a7994 + e71f5be commit 47657e5

File tree

3 files changed

+419
-0
lines changed

3 files changed

+419
-0
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
use Symfony\Component\Config\FileLocator;
2828
use Symfony\Component\PropertyAccess\PropertyAccessor;
2929
use Symfony\Component\Serializer\Encoder\YamlEncoder;
30+
use Symfony\Component\Serializer\Encoder\CsvEncoder;
3031
use Symfony\Component\Serializer\Mapping\Factory\CacheClassMetadataFactory;
3132
use Symfony\Component\Serializer\Normalizer\DataUriNormalizer;
3233
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
@@ -1045,6 +1046,12 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder
10451046
$definition->addTag('serializer.encoder');
10461047
}
10471048

1049+
if (class_exists(CsvEncoder::class)) {
1050+
$definition = $container->register('serializer.encoder.csv', CsvEncoder::class);
1051+
$definition->setPublic(false);
1052+
$definition->addTag('serializer.encoder');
1053+
}
1054+
10481055
$loader->load('serializer.xml');
10491056
$chainLoader = $container->getDefinition('serializer.mapping.chain_loader');
10501057

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
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\Encoder;
13+
14+
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
15+
16+
/**
17+
* Encodes CSV data.
18+
*
19+
* @author Kévin Dunglas <[email protected]>
20+
*/
21+
class CsvEncoder implements EncoderInterface, DecoderInterface
22+
{
23+
const FORMAT = 'csv';
24+
25+
private $delimiter;
26+
private $enclosure;
27+
private $escapeChar;
28+
private $keySeparator;
29+
30+
/**
31+
* @param string $delimiter
32+
* @param string $enclosure
33+
* @param string $escapeChar
34+
* @param string $keySeparator
35+
*/
36+
public function __construct($delimiter = ',', $enclosure = '"', $escapeChar = '\\', $keySeparator = '.')
37+
{
38+
$this->delimiter = $delimiter;
39+
$this->enclosure = $enclosure;
40+
$this->escapeChar = $escapeChar;
41+
$this->keySeparator = $keySeparator;
42+
}
43+
44+
/**
45+
* {@inheritdoc}
46+
*/
47+
public function encode($data, $format, array $context = array())
48+
{
49+
$handle = fopen('php://temp,', 'w+');
50+
51+
if (!is_array($data)) {
52+
$data = array(array($data));
53+
} elseif (empty($data)) {
54+
$data = array(array());
55+
} else {
56+
// Sequential arrays of arrays are considered as collections
57+
$i = 0;
58+
foreach ($data as $key => $value) {
59+
if ($i !== $key || !is_array($value)) {
60+
$data = array($data);
61+
break;
62+
}
63+
64+
++$i;
65+
}
66+
}
67+
68+
$headers = null;
69+
foreach ($data as $value) {
70+
$result = array();
71+
$this->flatten($value, $result);
72+
73+
if (null === $headers) {
74+
$headers = array_keys($result);
75+
fputcsv($handle, $headers, $this->delimiter, $this->enclosure, $this->escapeChar);
76+
} elseif (array_keys($result) !== $headers) {
77+
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.');
78+
}
79+
80+
fputcsv($handle, $result, $this->delimiter, $this->enclosure, $this->escapeChar);
81+
}
82+
83+
rewind($handle);
84+
$value = stream_get_contents($handle);
85+
fclose($handle);
86+
87+
return $value;
88+
}
89+
90+
/**
91+
* {@inheritdoc}
92+
*/
93+
public function supportsEncoding($format)
94+
{
95+
return self::FORMAT === $format;
96+
}
97+
98+
/**
99+
* {@inheritdoc}
100+
*/
101+
public function decode($data, $format, array $context = array())
102+
{
103+
$handle = fopen('php://temp', 'r+');
104+
fwrite($handle, $data);
105+
rewind($handle);
106+
107+
$headers = null;
108+
$nbHeaders = 0;
109+
$result = array();
110+
111+
while (false !== ($cols = fgetcsv($handle, 0, $this->delimiter, $this->enclosure, $this->escapeChar))) {
112+
$nbCols = count($cols);
113+
114+
if (null === $headers) {
115+
$nbHeaders = $nbCols;
116+
117+
foreach ($cols as $col) {
118+
$headers[] = explode($this->keySeparator, $col);
119+
}
120+
121+
continue;
122+
}
123+
124+
$item = array();
125+
for ($i = 0; ($i < $nbCols) && ($i < $nbHeaders); ++$i) {
126+
$depth = count($headers[$i]);
127+
$arr = &$item;
128+
for ($j = 0; $j < $depth; ++$j) {
129+
// Handle nested arrays
130+
if ($j === ($depth - 1)) {
131+
$arr[$headers[$i][$j]] = $cols[$i];
132+
133+
continue;
134+
}
135+
136+
if (!isset($arr[$headers[$i][$j]])) {
137+
$arr[$headers[$i][$j]] = array();
138+
}
139+
140+
$arr = &$arr[$headers[$i][$j]];
141+
}
142+
}
143+
144+
$result[] = $item;
145+
}
146+
fclose($handle);
147+
148+
if (empty($result) || isset($result[1])) {
149+
return $result;
150+
}
151+
152+
// If there is only one data line in the document, return it (the line), the result is not considered as a collection
153+
return $result[0];
154+
}
155+
156+
/**
157+
* {@inheritdoc}
158+
*/
159+
public function supportsDecoding($format)
160+
{
161+
return self::FORMAT === $format;
162+
}
163+
164+
/**
165+
* Flattens an array and generates keys including the path.
166+
*
167+
* @param array $array
168+
* @param array $result
169+
* @param string $parentKey
170+
*/
171+
private function flatten(array $array, array &$result, $parentKey = '')
172+
{
173+
foreach ($array as $key => $value) {
174+
if (is_array($value)) {
175+
$this->flatten($value, $result, $parentKey.$key.$this->keySeparator);
176+
} else {
177+
$result[$parentKey.$key] = $value;
178+
}
179+
}
180+
}
181+
}

0 commit comments

Comments
 (0)