diff --git a/src/Symfony/Component/Serializer/Encoder/CsvEncoder.php b/src/Symfony/Component/Serializer/Encoder/CsvEncoder.php index 8f9c3cff0fb3c..07043d946d751 100644 --- a/src/Symfony/Component/Serializer/Encoder/CsvEncoder.php +++ b/src/Symfony/Component/Serializer/Encoder/CsvEncoder.php @@ -65,6 +65,10 @@ public function encode(mixed $data, string $format, array $context = []): string } elseif (empty($data)) { $data = [[]]; } else { + if ($data instanceof \Traversable) { + // Generators can only be iterated once — convert to array to allow multiple traversals + $data = iterator_to_array($data); + } // Sequential arrays of arrays are considered as collections $i = 0; foreach ($data as $key => $value) { diff --git a/src/Symfony/Component/Serializer/Tests/Encoder/CsvEncoderTest.php b/src/Symfony/Component/Serializer/Tests/Encoder/CsvEncoderTest.php index c0be73a8bd685..cde6d333e99f0 100644 --- a/src/Symfony/Component/Serializer/Tests/Encoder/CsvEncoderTest.php +++ b/src/Symfony/Component/Serializer/Tests/Encoder/CsvEncoderTest.php @@ -710,4 +710,28 @@ public function testEndOfLinePassedInConstructor() $encoder = new CsvEncoder([CsvEncoder::END_OF_LINE => "\r\n"]); $this->assertSame("foo,bar\r\nhello,test\r\n", $encoder->encode($value, 'csv')); } + + /** @dataProvider provideIterable */ + public function testIterable(mixed $data) + { + $this->assertEquals(<<<'CSV' + foo,bar + hello,"hey ho" + hi,"let's go" + + CSV, $this->encoder->encode($data, 'csv')); + } + + public static function provideIterable() + { + $data = [ + ['foo' => 'hello', 'bar' => 'hey ho'], + ['foo' => 'hi', 'bar' => 'let\'s go'], + ]; + + yield 'array' => [$data]; + yield 'array iterator' => [new \ArrayIterator($data)]; + yield 'iterator aggregate' => [new \IteratorIterator(new \ArrayIterator($data))]; + yield 'generator' => [(fn (): \Generator => yield from $data)()]; + } }