diff --git a/Encoder/CsvEncoder.php b/Encoder/CsvEncoder.php index 3902b56134e..e2677308195 100644 --- a/Encoder/CsvEncoder.php +++ b/Encoder/CsvEncoder.php @@ -72,6 +72,10 @@ public function encode(mixed $data, string $format, array $context = []): string } elseif (!$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/Tests/Encoder/CsvEncoderTest.php b/Tests/Encoder/CsvEncoderTest.php index 048d790b04f..34cc940a7d0 100644 --- a/Tests/Encoder/CsvEncoderTest.php +++ b/Tests/Encoder/CsvEncoderTest.php @@ -708,6 +708,30 @@ public function testEndOfLinePassedInConstructor() $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)()]; + } + /** * @group legacy */