Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
81.82% covered (warning)
81.82%
36 / 44
50.00% covered (danger)
50.00%
1 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
XlsxExport
81.82% covered (warning)
81.82%
36 / 44
50.00% covered (danger)
50.00%
1 / 2
16.35
0.00% covered (danger)
0.00%
0 / 1
 streamFromSheets
80.49% covered (warning)
80.49%
33 / 41
0.00% covered (danger)
0.00%
0 / 1
15.46
 getSupportedExtensions
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2namespace Apie\Export;
3
4use Apie\Export\Concerns\FlattensValues;
5use Apie\Export\Lists\FileExtensionList;
6use Apie\Export\ValueObjects\FileExtension;
7use Nyholm\Psr7\Stream;
8use OpenSpout\Common\Entity\Row;
9use OpenSpout\Writer\XLSX\Options;
10use OpenSpout\Writer\XLSX\Properties;
11use OpenSpout\Writer\XLSX\Writer;
12use Psr\Http\Message\StreamInterface;
13use UnitEnum;
14
15class XlsxExport implements ExportInterface
16{
17    use FlattensValues;
18
19    public function streamFromSheets(array $sheets, string $outputFilename = 'export.xlsx'): StreamInterface
20    {
21        $tempFile = tempnam(sys_get_temp_dir(), 'apie_ods_');
22        if ($tempFile === false) {
23            throw new \RuntimeException('Unable to create temporary file for ODS export.');
24        }
25
26        $writer = new Writer(new Options(properties: new Properties(
27            title: 'Apie ' . ucfirst(basename($outputFilename)),
28            creator: 'Apie library',
29            lastModifiedBy: 'Apie library'
30        )));
31        $writer->openToFile($tempFile);
32        try {
33            $sheetIndex = 0;
34            foreach ($sheets as $sheetName => $data) {
35                if ($sheetIndex > 0) {
36                    $writer->addNewSheetAndMakeItCurrent();
37                }
38                foreach ($data as $row) {
39                    // If row is Traversable or object, try to convert to array
40                    if (is_object($row) && !$row instanceof \Stringable && !$row instanceof UnitEnum) {
41                        if ($row instanceof \Traversable) {
42                            $row = iterator_to_array($row);
43                        } else {
44                            // cast to array will extract properties; prefer that to avoid errors
45                            $row = (array)$row;
46                        }
47                    }
48                    if (!is_array($row)) {
49                        // single value row -> wrap
50                        $row = [$row];
51                    }
52
53                    $converted = [];
54                    foreach ($row as $cell) {
55                        $cell = $this->toSingleValue($cell);
56                        if ($cell === null) {
57                            $converted[] = '';
58                        } else {
59                            $converted[] = $cell;
60                        }
61                    }
62
63                    $writer->addRow(Row::fromValues($converted));
64                }
65                $sheetIndex++;
66            }
67
68            $writer->close();
69
70            $source = fopen($tempFile, 'rb');
71            if ($source === false) {
72                throw new \RuntimeException('Unable to read temporary ODS file.');
73            }
74
75            $stream = fopen('php://temp', 'r+');
76            if ($stream === false) {
77                fclose($source);
78                throw new \RuntimeException('Unable to create temporary stream for ODS export.');
79            }
80
81            stream_copy_to_stream($source, $stream);
82            fclose($source);
83            rewind($stream);
84        } finally {
85            @unlink($tempFile);
86        }
87
88        return new Stream($stream);
89    }
90
91    public function getSupportedExtensions(): FileExtensionList
92    {
93        return new FileExtensionList([
94            new FileExtension('xlsx'),
95        ]);
96    }
97}