Skip to content

Commit 80e7a0f

Browse files
committed
WIP
1 parent e05c25c commit 80e7a0f

14 files changed

+311
-83
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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\Context;
13+
14+
/**
15+
* Holds contexts needed by serialalization indexed by their FQCN.
16+
*
17+
* @author Mathias Arlaud <[email protected]>
18+
*/
19+
final class Context
20+
{
21+
/**
22+
* @template T of object
23+
*
24+
* @var array<class-string<T>, T> $optionsMap
25+
*/
26+
private array $optionsMap = [];
27+
28+
public function addOptions(object $options): self
29+
{
30+
$this->optionsMap[get_class($options)] = $options;
31+
32+
return $this;
33+
}
34+
35+
/**
36+
* @param class-string $optionsClass
37+
*/
38+
public function removeOptions(string $optionsClass): self
39+
{
40+
unset($this->optionsMap[$optionsClass]);
41+
42+
return $this;
43+
}
44+
45+
/**
46+
* @param class-string $optionsClass
47+
*/
48+
public function hasOptions(string $optionsClass): bool
49+
{
50+
return isset($this->optionsMap[$optionsClass]);
51+
}
52+
53+
/**
54+
* @template T of object
55+
*
56+
* @param class-string<T>
57+
*
58+
* @return T|null
59+
*/
60+
public function getOptions(string $optionsClass): ?object
61+
{
62+
return $this->optionsMap[$optionsClass] ?? null;
63+
}
64+
}

src/Symfony/Component/Serializer/Encoder/ChainDecoder.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\Serializer\Encoder;
1313

14+
use Symfony\Component\Serializer\Context\Context;
1415
use Symfony\Component\Serializer\Exception\RuntimeException;
1516

1617
/**
@@ -35,9 +36,10 @@ public function __construct(array $decoders = [])
3536
/**
3637
* {@inheritdoc}
3738
*/
38-
final public function decode(string $data, string $format, array $context = []): mixed
39+
final public function decode(string $data, string $format, Context $context = null): mixed
3940
{
40-
return $this->getDecoder($format, $context)->decode($data, $format, $context);
41+
// TODO
42+
return $this->getDecoder($format, [])->decode($data, $format, $context);
4143
}
4244

4345
/**

src/Symfony/Component/Serializer/Encoder/ChainEncoder.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\Serializer\Encoder;
1313

14+
use Symfony\Component\Serializer\Context\Context;
1415
use Symfony\Component\Serializer\Exception\RuntimeException;
1516

1617
/**
@@ -35,9 +36,10 @@ public function __construct(array $encoders = [])
3536
/**
3637
* {@inheritdoc}
3738
*/
38-
final public function encode(mixed $data, string $format, array $context = []): string
39+
final public function encode(mixed $data, string $format, Context $context = null): string
3940
{
40-
return $this->getEncoder($format, $context)->encode($data, $format, $context);
41+
// TODO
42+
return $this->getEncoder($format, [])->encode($data, $format, $context);
4143
}
4244

4345
/**

src/Symfony/Component/Serializer/Encoder/CsvEncoder.php

Lines changed: 33 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
namespace Symfony\Component\Serializer\Encoder;
1313

14-
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
14+
use Symfony\Component\Serializer\Context\Context;
1515
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
1616

1717
/**
@@ -23,43 +23,25 @@
2323
class CsvEncoder implements EncoderInterface, DecoderInterface
2424
{
2525
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';
3626

3727
private const UTF8_BOM = "\xEF\xBB\xBF";
3828

3929
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 = [])
30+
31+
private CsvEncoderOptions $defaultOptions;
32+
33+
public function __construct(Context $defaultOptions = null)
5434
{
55-
$this->defaultContext = array_merge($this->defaultContext, $defaultContext);
35+
$this->defaultOptions = $defaultOptions?->getOptions(CsvEncoderOptions::class) ?? new CsvEncoderOptions();
5636
}
5737

5838
/**
5939
* {@inheritdoc}
6040
*/
61-
public function encode(mixed $data, string $format, array $context = []): string
41+
public function encode(mixed $data, string $format, Context $context = null): string
6242
{
43+
$options = $this->getOptions($context);
44+
6345
$handle = fopen('php://temp,', 'w+');
6446

6547
if (!is_iterable($data)) {
@@ -79,37 +61,35 @@ public function encode(mixed $data, string $format, array $context = []): string
7961
}
8062
}
8163

82-
[$delimiter, $enclosure, $escapeChar, $keySeparator, $headers, $escapeFormulas, $outputBom] = $this->getCsvOptions($context);
83-
8464
foreach ($data as &$value) {
8565
$flattened = [];
86-
$this->flatten($value, $flattened, $keySeparator, '', $escapeFormulas);
66+
$this->flatten($value, $flattened, $options->getKeySeparator(), '', $options->isEscapeFormulas());
8767
$value = $flattened;
8868
}
8969
unset($value);
9070

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()));
9272

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());
9777
}
9878
}
9979

10080
$headers = array_fill_keys($headers, '');
10181
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());
10585
}
10686
}
10787

10888
rewind($handle);
10989
$value = stream_get_contents($handle);
11090
fclose($handle);
11191

112-
if ($outputBom) {
92+
if ($options->isOutputUtf8Bom()) {
11393
if (!preg_match('//u', $value)) {
11494
throw new UnexpectedValueException('You are trying to add a UTF-8 BOM to a non UTF-8 text.');
11595
}
@@ -131,8 +111,10 @@ public function supportsEncoding(string $format): bool
131111
/**
132112
* {@inheritdoc}
133113
*/
134-
public function decode(string $data, string $format, array $context = []): mixed
114+
public function decode(string $data, string $format, Context $context = null): mixed
135115
{
116+
$options = $this->getOptions($context);
117+
136118
$handle = fopen('php://temp', 'r+');
137119
fwrite($handle, $data);
138120
rewind($handle);
@@ -146,22 +128,20 @@ public function decode(string $data, string $format, array $context = []): mixed
146128
$headerCount = [];
147129
$result = [];
148130

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()))) {
152132
$nbCols = \count($cols);
153133

154134
if (null === $headers) {
155135
$nbHeaders = $nbCols;
156136

157-
if ($context[self::NO_HEADERS_KEY] ?? $this->defaultContext[self::NO_HEADERS_KEY]) {
137+
if (!$options->isWithHeaders()) {
158138
for ($i = 0; $i < $nbCols; ++$i) {
159139
$headers[] = [$i];
160140
}
161141
$headerCount = array_fill(0, $nbCols, 1);
162142
} else {
163143
foreach ($cols as $col) {
164-
$header = explode($keySeparator, $col);
144+
$header = explode($options->getKeySeparator(), $col);
165145
$headers[] = $header;
166146
$headerCount[] = \count($header);
167147
}
@@ -194,7 +174,7 @@ public function decode(string $data, string $format, array $context = []): mixed
194174
}
195175
fclose($handle);
196176

197-
if ($asCollection) {
177+
if ($options->isAsCollection()) {
198178
return $result;
199179
}
200180

@@ -214,6 +194,13 @@ public function supportsDecoding(string $format): bool
214194
return self::FORMAT === $format;
215195
}
216196

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+
217204
/**
218205
* Flattens an array and generates keys including the path.
219206
*/
@@ -233,24 +220,6 @@ private function flatten(iterable $array, array &$result, string $keySeparator,
233220
}
234221
}
235222

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-
254223
/**
255224
* @return string[]
256225
*/

0 commit comments

Comments
 (0)