17
17
* Encodes CSV data.
18
18
*
19
19
* @author Kévin Dunglas <dunglas@gmail.com>
20
+ * @author Oliver Hoff <oliver@hofff.com>
20
21
*/
21
22
class CsvEncoder implements EncoderInterface, DecoderInterface
22
23
{
@@ -25,6 +26,7 @@ class CsvEncoder implements EncoderInterface, DecoderInterface
25
26
const ENCLOSURE_KEY = 'csv_enclosure ' ;
26
27
const ESCAPE_CHAR_KEY = 'csv_escape_char ' ;
27
28
const KEY_SEPARATOR_KEY = 'csv_key_separator ' ;
29
+ const HEADERS_KEY = 'csv_headers ' ;
28
30
29
31
private $ delimiter ;
30
32
private $ enclosure ;
@@ -69,21 +71,22 @@ public function encode($data, $format, array $context = array())
69
71
}
70
72
}
71
73
72
- list ($ delimiter , $ enclosure , $ escapeChar , $ keySeparator ) = $ this ->getCsvOptions ($ context );
74
+ list ($ delimiter , $ enclosure , $ escapeChar , $ keySeparator, $ headers ) = $ this ->getCsvOptions ($ context );
73
75
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 );
78
82
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 );
85
86
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 );
87
90
}
88
91
89
92
rewind ($ handle );
@@ -194,7 +197,50 @@ private function getCsvOptions(array $context)
194
197
$ enclosure = isset ($ context [self ::ENCLOSURE_KEY ]) ? $ context [self ::ENCLOSURE_KEY ] : $ this ->enclosure ;
195
198
$ escapeChar = isset ($ context [self ::ESCAPE_CHAR_KEY ]) ? $ context [self ::ESCAPE_CHAR_KEY ] : $ this ->escapeChar ;
196
199
$ 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
+ }
197
243
198
- return array ( $ delimiter , $ enclosure , $ escapeChar , $ keySeparator ) ;
244
+ return $ headers ;
199
245
}
200
246
}
0 commit comments