11
11
12
12
namespace Symfony \Component \Serializer \Encoder ;
13
13
14
- use Symfony \Component \Serializer \Exception \ InvalidArgumentException ;
14
+ use Symfony \Component \Serializer \Context \ Context ;
15
15
use Symfony \Component \Serializer \Exception \UnexpectedValueException ;
16
16
17
17
/**
23
23
class CsvEncoder implements EncoderInterface, DecoderInterface
24
24
{
25
25
public const FORMAT = 'csv ' ;
26
- public const DELIMITER_KEY = 'csv_delimiter ' ;
27
- public const ENCLOSURE_KEY = 'csv_enclosure ' ;
28
- public const ESCAPE_CHAR_KEY = 'csv_escape_char ' ;
29
- public const KEY_SEPARATOR_KEY = 'csv_key_separator ' ;
30
- public const HEADERS_KEY = 'csv_headers ' ;
31
- public const ESCAPE_FORMULAS_KEY = 'csv_escape_formulas ' ;
32
- public const AS_COLLECTION_KEY = 'as_collection ' ;
33
- public const NO_HEADERS_KEY = 'no_headers ' ;
34
- public const END_OF_LINE = 'csv_end_of_line ' ;
35
- public const OUTPUT_UTF8_BOM_KEY = 'output_utf8_bom ' ;
36
26
37
27
private const UTF8_BOM = "\xEF\xBB\xBF" ;
38
28
39
- private $ formulasStartCharacters = ['= ' , '- ' , '+ ' , '@ ' ];
40
- private $ defaultContext = [
41
- self ::DELIMITER_KEY => ', ' ,
42
- self ::ENCLOSURE_KEY => '" ' ,
43
- self ::ESCAPE_CHAR_KEY => '' ,
44
- self ::END_OF_LINE => "\n" ,
45
- self ::ESCAPE_FORMULAS_KEY => false ,
46
- self ::HEADERS_KEY => [],
47
- self ::KEY_SEPARATOR_KEY => '. ' ,
48
- self ::NO_HEADERS_KEY => false ,
49
- self ::AS_COLLECTION_KEY => true ,
50
- self ::OUTPUT_UTF8_BOM_KEY => false ,
51
- ];
52
-
53
- public function __construct (array $ defaultContext = [])
29
+ private array $ formulasStartCharacters = ['= ' , '- ' , '+ ' , '@ ' ];
30
+
31
+ private CsvEncoderOptions $ defaultOptions ;
32
+
33
+ public function __construct (Context $ defaultContext = null )
54
34
{
55
- $ this ->defaultContext = array_merge ( $ this -> defaultContext , $ defaultContext );
35
+ $ this ->defaultOptions = $ defaultContext ?->getOptions(CsvEncoderOptions::class) ?? new CsvEncoderOptions ( );
56
36
}
57
37
58
38
/**
59
39
* {@inheritdoc}
60
40
*/
61
- public function encode (mixed $ data , string $ format , array $ context = [] ): string
41
+ public function encode (mixed $ data , string $ format , Context $ context = null ): string
62
42
{
43
+ $ options = $ this ->getOptions ($ context );
44
+
63
45
$ handle = fopen ('php://temp, ' , 'w+ ' );
64
46
65
47
if (!is_iterable ($ data )) {
@@ -79,37 +61,35 @@ public function encode(mixed $data, string $format, array $context = []): string
79
61
}
80
62
}
81
63
82
- [$ delimiter , $ enclosure , $ escapeChar , $ keySeparator , $ headers , $ escapeFormulas , $ outputBom ] = $ this ->getCsvOptions ($ context );
83
-
84
64
foreach ($ data as &$ value ) {
85
65
$ flattened = [];
86
- $ this ->flatten ($ value , $ flattened , $ keySeparator , '' , $ escapeFormulas );
66
+ $ this ->flatten ($ value , $ flattened , $ options -> getKeySeparator () , '' , $ options -> isEscapeFormulas () );
87
67
$ value = $ flattened ;
88
68
}
89
69
unset($ value );
90
70
91
- $ headers = array_merge (array_values ($ headers ) , array_diff ($ this ->extractHeaders ($ data ), $ headers ));
71
+ $ headers = array_merge (array_values ($ options -> getHeaders ()) , array_diff ($ this ->extractHeaders ($ data ), $ options -> getHeaders () ));
92
72
93
- if (!( $ context [ self :: NO_HEADERS_KEY ] ?? $ this -> defaultContext [ self :: NO_HEADERS_KEY ] )) {
94
- fputcsv ($ handle , $ headers , $ delimiter , $ enclosure , $ escapeChar );
95
- if ("\n" !== ( $ context [ self :: END_OF_LINE ] ?? $ this -> defaultContext [ self :: END_OF_LINE ] ) && 0 === fseek ($ handle , -1 , \SEEK_CUR )) {
96
- fwrite ($ handle , $ context [ self :: END_OF_LINE ] );
73
+ if ($ options -> isWithHeaders ( )) {
74
+ fputcsv ($ handle , $ headers , $ options -> getDelimiter () , $ options -> getEnclosure () , $ options -> getEscapeChar () );
75
+ if ("\n" !== $ options -> getEndOfLine ( ) && 0 === fseek ($ handle , -1 , \SEEK_CUR )) {
76
+ fwrite ($ handle , $ options -> getEndOfLine () );
97
77
}
98
78
}
99
79
100
80
$ headers = array_fill_keys ($ headers , '' );
101
81
foreach ($ data as $ row ) {
102
- fputcsv ($ handle , array_replace ($ headers , $ row ), $ delimiter , $ enclosure , $ escapeChar );
103
- if ("\n" !== ( $ context [ self :: END_OF_LINE ] ?? $ this -> defaultContext [ self :: END_OF_LINE ] ) && 0 === fseek ($ handle , -1 , \SEEK_CUR )) {
104
- fwrite ($ handle , $ context [ self :: END_OF_LINE ] );
82
+ fputcsv ($ handle , array_replace ($ headers , $ row ), $ options -> getDelimiter () , $ options -> getEnclosure () , $ options -> getEscapeChar () );
83
+ if ("\n" !== $ options -> getEndOfLine ( ) && 0 === fseek ($ handle , -1 , \SEEK_CUR )) {
84
+ fwrite ($ handle , $ options -> getEndOfLine () );
105
85
}
106
86
}
107
87
108
88
rewind ($ handle );
109
89
$ value = stream_get_contents ($ handle );
110
90
fclose ($ handle );
111
91
112
- if ($ outputBom ) {
92
+ if ($ options -> isOutputUtf8Bom () ) {
113
93
if (!preg_match ('//u ' , $ value )) {
114
94
throw new UnexpectedValueException ('You are trying to add a UTF-8 BOM to a non UTF-8 text. ' );
115
95
}
@@ -131,8 +111,10 @@ public function supportsEncoding(string $format): bool
131
111
/**
132
112
* {@inheritdoc}
133
113
*/
134
- public function decode (string $ data , string $ format , array $ context = [] ): mixed
114
+ public function decode (string $ data , string $ format , Context $ context = null ): mixed
135
115
{
116
+ $ options = $ this ->getOptions ($ context );
117
+
136
118
$ handle = fopen ('php://temp ' , 'r+ ' );
137
119
fwrite ($ handle , $ data );
138
120
rewind ($ handle );
@@ -146,22 +128,20 @@ public function decode(string $data, string $format, array $context = []): mixed
146
128
$ headerCount = [];
147
129
$ result = [];
148
130
149
- [$ delimiter , $ enclosure , $ escapeChar , $ keySeparator , , , , $ asCollection ] = $ this ->getCsvOptions ($ context );
150
-
151
- while (false !== ($ cols = fgetcsv ($ handle , 0 , $ delimiter , $ enclosure , $ escapeChar ))) {
131
+ while (false !== ($ cols = fgetcsv ($ handle , 0 , $ options ->getDelimiter (), $ options ->getEnclosure (), $ options ->getEscapeChar ()))) {
152
132
$ nbCols = \count ($ cols );
153
133
154
134
if (null === $ headers ) {
155
135
$ nbHeaders = $ nbCols ;
156
136
157
- if ($ context [ self :: NO_HEADERS_KEY ] ?? $ this -> defaultContext [ self :: NO_HEADERS_KEY ] ) {
137
+ if (! $ options -> isWithHeaders () ) {
158
138
for ($ i = 0 ; $ i < $ nbCols ; ++$ i ) {
159
139
$ headers [] = [$ i ];
160
140
}
161
141
$ headerCount = array_fill (0 , $ nbCols , 1 );
162
142
} else {
163
143
foreach ($ cols as $ col ) {
164
- $ header = explode ($ keySeparator , $ col );
144
+ $ header = explode ($ options -> getKeySeparator () , $ col );
165
145
$ headers [] = $ header ;
166
146
$ headerCount [] = \count ($ header );
167
147
}
@@ -194,7 +174,7 @@ public function decode(string $data, string $format, array $context = []): mixed
194
174
}
195
175
fclose ($ handle );
196
176
197
- if ($ asCollection ) {
177
+ if ($ options -> isAsCollection () ) {
198
178
return $ result ;
199
179
}
200
180
@@ -214,6 +194,13 @@ public function supportsDecoding(string $format): bool
214
194
return self ::FORMAT === $ format ;
215
195
}
216
196
197
+ private function getOptions (?Context $ context ): CsvEncoderOptions
198
+ {
199
+ $ options = $ context ?->getOptions(CsvEncoderOptions::class);
200
+
201
+ return null !== $ options ? $ options ->merge ($ this ->defaultOptions ) : $ this ->defaultOptions ;
202
+ }
203
+
217
204
/**
218
205
* Flattens an array and generates keys including the path.
219
206
*/
@@ -233,24 +220,6 @@ private function flatten(iterable $array, array &$result, string $keySeparator,
233
220
}
234
221
}
235
222
236
- private function getCsvOptions (array $ context ): array
237
- {
238
- $ delimiter = $ context [self ::DELIMITER_KEY ] ?? $ this ->defaultContext [self ::DELIMITER_KEY ];
239
- $ enclosure = $ context [self ::ENCLOSURE_KEY ] ?? $ this ->defaultContext [self ::ENCLOSURE_KEY ];
240
- $ escapeChar = $ context [self ::ESCAPE_CHAR_KEY ] ?? $ this ->defaultContext [self ::ESCAPE_CHAR_KEY ];
241
- $ keySeparator = $ context [self ::KEY_SEPARATOR_KEY ] ?? $ this ->defaultContext [self ::KEY_SEPARATOR_KEY ];
242
- $ headers = $ context [self ::HEADERS_KEY ] ?? $ this ->defaultContext [self ::HEADERS_KEY ];
243
- $ escapeFormulas = $ context [self ::ESCAPE_FORMULAS_KEY ] ?? $ this ->defaultContext [self ::ESCAPE_FORMULAS_KEY ];
244
- $ outputBom = $ context [self ::OUTPUT_UTF8_BOM_KEY ] ?? $ this ->defaultContext [self ::OUTPUT_UTF8_BOM_KEY ];
245
- $ asCollection = $ context [self ::AS_COLLECTION_KEY ] ?? $ this ->defaultContext [self ::AS_COLLECTION_KEY ];
246
-
247
- if (!\is_array ($ headers )) {
248
- throw new InvalidArgumentException (sprintf ('The "%s" context variable must be an array or null, given "%s". ' , self ::HEADERS_KEY , get_debug_type ($ headers )));
249
- }
250
-
251
- return [$ delimiter , $ enclosure , $ escapeChar , $ keySeparator , $ headers , $ escapeFormulas , $ outputBom , $ asCollection ];
252
- }
253
-
254
223
/**
255
224
* @return string[]
256
225
*/
0 commit comments