1212namespace Symfony \Component \Serializer \Encoder ;
1313
1414use Symfony \Component \Serializer \Exception \InvalidArgumentException ;
15+ use Symfony \Component \Serializer \Exception \UnexpectedValueException ;
1516
1617/**
1718 * Encodes CSV data.
@@ -30,6 +31,9 @@ class CsvEncoder implements EncoderInterface, DecoderInterface
3031 const ESCAPE_FORMULAS_KEY = 'csv_escape_formulas ' ;
3132 const AS_COLLECTION_KEY = 'as_collection ' ;
3233 const NO_HEADERS_KEY = 'no_headers ' ;
34+ const OUTPUT_UTF8_BOM_KEY = 'output_utf8_bom ' ;
35+
36+ private const UTF8_BOM = "\xEF\xBB\xBF" ;
3337
3438 private $ formulasStartCharacters = ['= ' , '- ' , '+ ' , '@ ' ];
3539 private $ defaultContext = [
@@ -40,6 +44,7 @@ class CsvEncoder implements EncoderInterface, DecoderInterface
4044 self ::HEADERS_KEY => [],
4145 self ::KEY_SEPARATOR_KEY => '. ' ,
4246 self ::NO_HEADERS_KEY => false ,
47+ self ::OUTPUT_UTF8_BOM_KEY => false ,
4348 ];
4449
4550 /**
@@ -90,7 +95,7 @@ public function encode($data, $format, array $context = [])
9095 }
9196 }
9297
93- list ($ delimiter , $ enclosure , $ escapeChar , $ keySeparator , $ headers , $ escapeFormulas ) = $ this ->getCsvOptions ($ context );
98+ list ($ delimiter , $ enclosure , $ escapeChar , $ keySeparator , $ headers , $ escapeFormulas, $ outputBom ) = $ this ->getCsvOptions ($ context );
9499
95100 foreach ($ data as &$ value ) {
96101 $ flattened = [];
@@ -114,6 +119,14 @@ public function encode($data, $format, array $context = [])
114119 $ value = stream_get_contents ($ handle );
115120 fclose ($ handle );
116121
122+ if ($ outputBom ) {
123+ if (!preg_match ('//u ' , $ value )) {
124+ throw new UnexpectedValueException ('You are trying to add an UTF-8 BOM to a non UTF-8 text. ' );
125+ }
126+
127+ $ value = self ::UTF8_BOM .$ value ;
128+ }
129+
117130 return $ value ;
118131 }
119132
@@ -134,6 +147,10 @@ public function decode($data, $format, array $context = [])
134147 fwrite ($ handle , $ data );
135148 rewind ($ handle );
136149
150+ if (0 === strpos ($ data , self ::UTF8_BOM )) {
151+ fseek ($ handle , \strlen (self ::UTF8_BOM ));
152+ }
153+
137154 $ headers = null ;
138155 $ nbHeaders = 0 ;
139156 $ headerCount = [];
@@ -238,12 +255,13 @@ private function getCsvOptions(array $context): array
238255 $ keySeparator = $ context [self ::KEY_SEPARATOR_KEY ] ?? $ this ->defaultContext [self ::KEY_SEPARATOR_KEY ];
239256 $ headers = $ context [self ::HEADERS_KEY ] ?? $ this ->defaultContext [self ::HEADERS_KEY ];
240257 $ escapeFormulas = $ context [self ::ESCAPE_FORMULAS_KEY ] ?? $ this ->defaultContext [self ::ESCAPE_FORMULAS_KEY ];
258+ $ outputBom = $ context [self ::OUTPUT_UTF8_BOM_KEY ] ?? $ this ->defaultContext [self ::OUTPUT_UTF8_BOM_KEY ];
241259
242260 if (!\is_array ($ headers )) {
243261 throw new InvalidArgumentException (sprintf ('The "%s" context variable must be an array or null, given "%s". ' , self ::HEADERS_KEY , \gettype ($ headers )));
244262 }
245263
246- return [$ delimiter , $ enclosure , $ escapeChar , $ keySeparator , $ headers , $ escapeFormulas ];
264+ return [$ delimiter , $ enclosure , $ escapeChar , $ keySeparator , $ headers , $ escapeFormulas, $ outputBom ];
247265 }
248266
249267 /**
0 commit comments