Skip to content

Commit 3d56244

Browse files
committed
add stream support to Reader/Csv
1 parent 38a69cd commit 3d56244

File tree

4 files changed

+91
-28
lines changed

4 files changed

+91
-28
lines changed

src/PhpSpreadsheet/Reader/BaseReader.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,6 @@ public function load($file, int $flags = 0): Spreadsheet
194194
*/
195195
protected function openFile($file): void
196196
{
197-
$fileHandle = false;
198197
if (is_string($file)) {
199198
$filename = $file;
200199
File::assertFile($file);
@@ -204,6 +203,8 @@ protected function openFile($file): void
204203
} elseif (is_resource($file)) {
205204
$filename = 'stream';
206205
$fileHandle = $file;
206+
} else {
207+
throw new ReaderException('invalid type for file. Only file path or a stream resource is allowed');
207208
}
208209
if ($fileHandle === false) {
209210
throw new ReaderException('Could not open file ' . $filename . ' for reading.');

src/PhpSpreadsheet/Reader/Csv.php

+53-27
Original file line numberDiff line numberDiff line change
@@ -281,22 +281,20 @@ public function loadSpreadsheetFromString(string $contents): Spreadsheet
281281
}
282282

283283
/**
284-
* @param resource|string $filename
284+
* @param resource|string $file
285285
*/
286-
private function openFileOrMemory($filename): void
286+
private function openFileOrMemory($file): void
287287
{
288288
// Open file
289-
$fhandle = $this->canRead($filename);
290-
if (!$fhandle) {
291-
throw new Exception($filename . ' is an Invalid Spreadsheet file.');
289+
if (!$this->canRead($file)) {
290+
throw new Exception($file . ' is an Invalid Spreadsheet file.');
292291
}
293292
if ($this->inputEncoding === self::GUESS_ENCODING) {
294-
$this->inputEncoding = self::guessEncoding($filename, $this->fallbackEncoding);
293+
$this->inputEncoding = self::guessEncoding($file, $this->fallbackEncoding);
295294
}
296-
$this->openFile($filename);
295+
$this->openFile($file);
297296
if ($this->inputEncoding !== 'UTF-8') {
298-
fclose($this->fileHandle);
299-
$entireFile = file_get_contents($filename);
297+
$entireFile = stream_get_contents($this->fileHandle);
300298
$fileHandle = fopen('php://memory', 'r+b');
301299
if ($fileHandle !== false && $entireFile !== false) {
302300
$this->fileHandle = $fileHandle;
@@ -355,26 +353,31 @@ private function openDataUri(string $filename): void
355353
*
356354
* @param resource|string $file
357355
*/
358-
public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet): Spreadsheet
356+
public function loadIntoExisting($file, Spreadsheet $spreadsheet): Spreadsheet
359357
{
360-
return $this->loadStringOrFile($filename, $spreadsheet, false);
358+
return $this->loadStringOrFile($file, $spreadsheet, false);
361359
}
362360

363361
/**
364362
* Loads PhpSpreadsheet from file into PhpSpreadsheet instance.
365363
*
366364
* @param resource|string $file
367365
*/
368-
private function loadStringOrFile(string $filename, Spreadsheet $spreadsheet, bool $dataUri): Spreadsheet
366+
private function loadStringOrFile($file, Spreadsheet $spreadsheet, bool $dataUri): Spreadsheet
369367
{
370368
// Deprecated in Php8.1
371369
$iniset = $this->setAutoDetect('1');
372370

373371
// Open file
374372
if ($dataUri) {
375-
$this->openDataUri($filename);
373+
if (!is_string($file)) {
374+
throw new \Exception('$file must be an uri');
375+
}
376+
$this->openDataUri($file);
377+
$filename = $file;
376378
} else {
377-
$this->openFileOrMemory($filename);
379+
$this->openFileOrMemory($file);
380+
$filename = 'escape';
378381
}
379382
$fileHandle = $this->fileHandle;
380383

@@ -559,23 +562,33 @@ public function canRead($file): bool
559562
return false;
560563
}
561564

562-
fclose($this->fileHandle);
565+
rewind($this->fileHandle);
563566

564-
// Trust file extension if any
565-
$extension = strtolower(pathinfo($file, PATHINFO_EXTENSION));
566-
if (in_array($extension, ['csv', 'tsv'])) {
567-
return true;
567+
if (is_string($file)) {
568+
// Trust file extension if any
569+
$extension = strtolower(pathinfo($file, PATHINFO_EXTENSION));
570+
if (in_array($extension, ['csv', 'tsv'])) {
571+
return true;
572+
}
568573
}
569574

570575
// Attempt to guess mimetype
571-
$type = mime_content_type($file);
576+
$type = mime_content_type($this->fileHandle);
572577
$supportedTypes = [
573578
'application/csv',
574579
'text/csv',
575580
'text/plain',
576581
'inode/x-empty',
577582
];
578583

584+
if (is_resource($file)) {
585+
// reading mime types from a stream causes sometimes different results
586+
$supportedTypes[] = 'application/x-empty';
587+
$supportedTypes[] = 'text/html';
588+
}
589+
590+
rewind($this->fileHandle);
591+
579592
return in_array($type, $supportedTypes, true);
580593
}
581594

@@ -589,10 +602,13 @@ private static function guessEncodingTestNoBom(string &$encoding, string &$conte
589602
}
590603
}
591604

592-
private static function guessEncodingNoBom(string $filename): string
605+
/**
606+
* @param resource|string $file
607+
*/
608+
private static function guessEncodingNoBom($file): string
593609
{
610+
$contents = is_resource($file) ? stream_get_contents($file) : file_get_contents($file);
594611
$encoding = '';
595-
$contents = file_get_contents($filename);
596612
self::guessEncodingTestNoBom($encoding, $contents, self::UTF32BE_LF, 'UTF-32BE');
597613
self::guessEncodingTestNoBom($encoding, $contents, self::UTF32LE_LF, 'UTF-32LE');
598614
self::guessEncodingTestNoBom($encoding, $contents, self::UTF16BE_LF, 'UTF-16BE');
@@ -613,10 +629,17 @@ private static function guessEncodingTestBom(string &$encoding, string $first4,
613629
}
614630
}
615631

616-
private static function guessEncodingBom(string $filename): string
632+
/**
633+
* @param resource|string $file
634+
*/
635+
private static function guessEncodingBom($file): string
617636
{
618637
$encoding = '';
619-
$first4 = file_get_contents($filename, false, null, 0, 4);
638+
if (is_resource($file)) {
639+
$first4 = stream_get_contents($file, 0, 4);
640+
} else {
641+
$first4 = file_get_contents($file, false, null, 0, 4);
642+
}
620643
if ($first4 !== false) {
621644
self::guessEncodingTestBom($encoding, $first4, self::UTF8_BOM, 'UTF-8');
622645
self::guessEncodingTestBom($encoding, $first4, self::UTF16BE_BOM, 'UTF-16BE');
@@ -628,11 +651,14 @@ private static function guessEncodingBom(string $filename): string
628651
return $encoding;
629652
}
630653

631-
public static function guessEncoding(string $filename, string $dflt = self::DEFAULT_FALLBACK_ENCODING): string
654+
/**
655+
* @param resource|string $file
656+
*/
657+
public static function guessEncoding($file, string $dflt = self::DEFAULT_FALLBACK_ENCODING): string
632658
{
633-
$encoding = self::guessEncodingBom($filename);
659+
$encoding = self::guessEncodingBom($file);
634660
if ($encoding === '') {
635-
$encoding = self::guessEncodingNoBom($filename);
661+
$encoding = self::guessEncodingNoBom($file);
636662
}
637663

638664
return ($encoding === '') ? $dflt : $encoding;

tests/PhpSpreadsheetTests/Reader/Csv/CsvLoadFromStringTest.php

+19
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,23 @@ public function testLoadFromString(): void
2525
self::AssertSame('7 , 8', $sheet->getCell('A3')->getValue());
2626
self::AssertSame("12\n13", $sheet->getCell('B4')->getValue());
2727
}
28+
29+
public function testLoadFromStream(): void
30+
{
31+
$data = <<<EOF
32+
1,2,3
33+
4,2+3,6
34+
"7 , 8", 9, 10
35+
11,"12
36+
13",14
37+
EOF;
38+
$stream = fopen('data://text/plain;base64,' . base64_encode($data), 'rb');
39+
self::assertNotFalse($stream);
40+
$reader = new Csv();
41+
$spreadsheet = $reader->load($stream);
42+
$sheet = $spreadsheet->getActiveSheet();
43+
self::AssertSame('2+3', $sheet->getCell('B2')->getValue());
44+
self::AssertSame('7 , 8', $sheet->getCell('A3')->getValue());
45+
self::AssertSame("12\n13", $sheet->getCell('B4')->getValue());
46+
}
2847
}

tests/PhpSpreadsheetTests/Reader/Csv/CsvTest.php

+17
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,23 @@ public function testCanLoad(bool $expected, string $filename): void
9696
self::assertSame($expected, $reader->canRead($filename));
9797
}
9898

99+
/**
100+
* @dataProvider providerCanLoad
101+
*/
102+
public function testCanLoadFromStream(bool $expected, string $filename): void
103+
{
104+
$reader = new Csv();
105+
$stream = fopen('php://memory', 'r+b');
106+
self::assertNotFalse($stream);
107+
108+
$contents = file_get_contents($filename);
109+
self::assertNotFalse($contents);
110+
fwrite($stream, $contents);
111+
rewind($stream);
112+
113+
self::assertSame($expected, $reader->canRead($stream));
114+
}
115+
99116
public static function providerCanLoad(): array
100117
{
101118
return [

0 commit comments

Comments
 (0)