Skip to content

Commit e9a0893

Browse files
committed
IOFactory::identify and Custom Reader/Writer
Fix #4357.`identify` returns a type, not a class name, and this result is not always usable for createReader. The docs say that it is usable in that manner. Changing the behavior of `identify` would be a breaking change, but adding an optional parameter (defaulting to current behavior) allowing it to return a class name rather than a type would not. This PR adds that parameter and updates the documentation to suggest its use for the code in question. To complete this change, `createReader` now accepts either a type (current behavior) or class name (new). Although it is not particularly identified as a possible problem by the issue, `createWriter` is similarly changed for consistency's sake.
1 parent 9d1ad14 commit e9a0893

File tree

6 files changed

+152
-48
lines changed

6 files changed

+152
-48
lines changed

docs/topics/reading-files.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,10 @@ method to identify the reader that you need, before using the
123123
```php
124124
$inputFileName = './sampleData/example1.xls';
125125

126-
/** Identify the type of $inputFileName **/
126+
/**
127+
* Identify the type of $inputFileName.
128+
* See below for a possible improvement for release 4.1.0+.
129+
*/
127130
$inputFileType = \PhpOffice\PhpSpreadsheet\IOFactory::identify($inputFileName);
128131
/** Create a new Reader of the type that has been identified **/
129132
$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader($inputFileType);
@@ -134,6 +137,13 @@ $spreadsheet = $reader->load($inputFileName);
134137
See `samples/Reader/04_Simple_file_reader_using_the_IOFactory_to_identify_a_reader_to_use.php`
135138
for a working example of this code.
136139

140+
Prior to release 4.1.0, `identify` returns a file type.
141+
It may be more useful to return a fully-qualified class name,
142+
which can be accomplished using a parameter introduced in 4.1.0:
143+
```php
144+
$inputFileType = \PhpOffice\PhpSpreadsheet\IOFactory::identify($inputFileName, null, true);
145+
```
146+
137147
As with the IOFactory `load()` method, you can also pass an array of formats
138148
for the `identify()` method to check against if you know that it will only
139149
be in a subset of the possible formats that PhpSpreadsheet supports.

src/PhpSpreadsheet/IOFactory.php

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,16 @@ abstract class IOFactory
5959
*/
6060
public static function createWriter(Spreadsheet $spreadsheet, string $writerType): IWriter
6161
{
62-
if (!isset(self::$writers[$writerType])) {
63-
throw new Writer\Exception("No writer found for type $writerType");
64-
}
62+
/** @var class-string<IWriter> */
63+
$className = $writerType;
64+
if (!in_array($writerType, self::$writers, true)) {
65+
if (!isset(self::$writers[$writerType])) {
66+
throw new Writer\Exception("No writer found for type $writerType");
67+
}
6568

66-
// Instantiate writer
67-
$className = self::$writers[$writerType];
69+
// Instantiate writer
70+
$className = self::$writers[$writerType];
71+
}
6872

6973
return new $className($spreadsheet);
7074
}
@@ -74,12 +78,16 @@ public static function createWriter(Spreadsheet $spreadsheet, string $writerType
7478
*/
7579
public static function createReader(string $readerType): IReader
7680
{
77-
if (!isset(self::$readers[$readerType])) {
78-
throw new Reader\Exception("No reader found for type $readerType");
79-
}
81+
/** @var class-string<IReader> */
82+
$className = $readerType;
83+
if (!in_array($readerType, self::$readers, true)) {
84+
if (!isset(self::$readers[$readerType])) {
85+
throw new Reader\Exception("No reader found for type $readerType");
86+
}
8087

81-
// Instantiate reader
82-
$className = self::$readers[$readerType];
88+
// Instantiate reader
89+
$className = self::$readers[$readerType];
90+
}
8391

8492
return new $className();
8593
}
@@ -109,12 +117,14 @@ public static function load(string $filename, int $flags = 0, ?array $readers =
109117
/**
110118
* Identify file type using automatic IReader resolution.
111119
*/
112-
public static function identify(string $filename, ?array $readers = null): string
120+
public static function identify(string $filename, ?array $readers = null, bool $fullClassName = false): string
113121
{
114122
$reader = self::createReaderForFile($filename, $readers);
115123
$className = $reader::class;
124+
if ($fullClassName) {
125+
return $className;
126+
}
116127
$classType = explode('\\', $className);
117-
unset($reader);
118128

119129
return array_pop($classType);
120130
}
@@ -224,6 +234,8 @@ public static function registerWriter(string $writerType, string $writerClass):
224234

225235
/**
226236
* Register a reader with its type and class name.
237+
*
238+
* @param class-string<IReader> $readerClass
227239
*/
228240
public static function registerReader(string $readerType, string $readerClass): void
229241
{
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpOffice\PhpSpreadsheetTests;
6+
7+
use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader;
8+
9+
// Used in IOFactoryRegister tests
10+
class CustomReader extends XlsxReader
11+
{
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpOffice\PhpSpreadsheetTests;
6+
7+
use PhpOffice\PhpSpreadsheet\Writer\Html as HtmlWriter;
8+
9+
// Used in IOFactoryRegister tests
10+
class CustomWriter extends HtmlWriter
11+
{
12+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpOffice\PhpSpreadsheetTests;
6+
7+
use PhpOffice\PhpSpreadsheet\IOFactory;
8+
use PhpOffice\PhpSpreadsheet\Reader;
9+
use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
10+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
11+
use PhpOffice\PhpSpreadsheet\Writer;
12+
use PHPUnit\Framework\Attributes;
13+
use PHPUnit\Framework\TestCase;
14+
15+
// Separate processes because register arrays are static
16+
#[Attributes\RunTestsInSeparateProcesses]
17+
class IOFactoryRegisterTest extends TestCase
18+
{
19+
public function testRegisterWriter(): void
20+
{
21+
IOFactory::registerWriter('Pdf', Writer\Pdf\Mpdf::class);
22+
$spreadsheet = new Spreadsheet();
23+
$actual = IOFactory::createWriter($spreadsheet, 'Pdf');
24+
self::assertInstanceOf(Writer\Pdf\Mpdf::class, $actual);
25+
}
26+
27+
public function testRegisterReader(): void
28+
{
29+
IOFactory::registerReader('Custom', Reader\Html::class);
30+
$actual = IOFactory::createReader('Custom');
31+
self::assertInstanceOf(Reader\Html::class, $actual);
32+
}
33+
34+
public function testRegisterInvalidWriter(): void
35+
{
36+
$this->expectException(Writer\Exception::class);
37+
$this->expectExceptionMessage('writers must implement');
38+
IOFactory::registerWriter('foo', 'bar'); // @phpstan-ignore-line
39+
}
40+
41+
public function testRegisterInvalidReader(): void
42+
{
43+
$this->expectException(ReaderException::class);
44+
$this->expectExceptionMessage('readers must implement');
45+
IOFactory::registerReader('foo', 'bar'); // @phpstan-ignore-line
46+
}
47+
48+
public static function testRegisterCustomReader(): void
49+
{
50+
IOFactory::registerReader(IOFactory::READER_XLSX, CustomReader::class);
51+
$inputFileName = 'tests/data/Reader/Xlsx/1900_Calendar.xlsx';
52+
$loadSpreadsheet = IOFactory::load($inputFileName);
53+
$sheet = $loadSpreadsheet->getActiveSheet();
54+
self::assertSame('2022-01-01', $sheet->getCell('A1')->getFormattedValue());
55+
$loadSpreadsheet->disconnectWorksheets();
56+
57+
$reader = new CustomReader();
58+
$newSpreadsheet = $reader->load($inputFileName);
59+
$newSheet = $newSpreadsheet->getActiveSheet();
60+
self::assertSame('2022-01-01', $newSheet->getCell('A1')->getFormattedValue());
61+
$newSpreadsheet->disconnectWorksheets();
62+
63+
$inputFileType = IOFactory::identify($inputFileName, null, true);
64+
$objReader = IOFactory::createReader($inputFileType);
65+
self::assertInstanceOf(CustomReader::class, $objReader);
66+
$objSpreadsheet = $objReader->load($inputFileName);
67+
$objSheet = $objSpreadsheet->getActiveSheet();
68+
self::assertSame('2022-01-01', $objSheet->getCell('A1')->getFormattedValue());
69+
$objSpreadsheet->disconnectWorksheets();
70+
}
71+
72+
public static function testRegisterCustomWriter(): void
73+
{
74+
$spreadsheet = new Spreadsheet();
75+
$sheet = $spreadsheet->getActiveSheet();
76+
$sheet->setCellValue('A1', 1);
77+
$writer = new CustomWriter($spreadsheet);
78+
$html = $writer->generateHtmlAll();
79+
self::assertStringContainsString('<td class="column0 style0 n">1</td>', $html);
80+
IOFactory::registerWriter(IOFactory::WRITER_HTML, CustomWriter::class);
81+
$objWriter = IOFactory::createWriter($spreadsheet, CustomWriter::class);
82+
self::assertInstanceOf(CustomWriter::class, $objWriter);
83+
self::assertTrue(method_exists($objWriter, 'generateHtmlAll'));
84+
$html2 = $objWriter->generateHtmlAll();
85+
self::assertStringContainsString('<td class="column0 style0 n">1</td>', $html2);
86+
$spreadsheet->disconnectWorksheets();
87+
}
88+
}

tests/PhpSpreadsheetTests/IOFactoryTest.php

Lines changed: 5 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@
99
use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
1010
use PhpOffice\PhpSpreadsheet\Spreadsheet;
1111
use PhpOffice\PhpSpreadsheet\Writer;
12+
use PHPUnit\Framework\Attributes\DataProvider;
1213
use PHPUnit\Framework\TestCase;
1314

1415
class IOFactoryTest extends TestCase
1516
{
16-
#[\PHPUnit\Framework\Attributes\DataProvider('providerCreateWriter')]
17+
#[DataProvider('providerCreateWriter')]
1718
public function testCreateWriter(string $name, string $expected): void
1819
{
1920
$spreadsheet = new Spreadsheet();
@@ -35,15 +36,7 @@ public static function providerCreateWriter(): array
3536
];
3637
}
3738

38-
public function testRegisterWriter(): void
39-
{
40-
IOFactory::registerWriter('Pdf', Writer\Pdf\Mpdf::class);
41-
$spreadsheet = new Spreadsheet();
42-
$actual = IOFactory::createWriter($spreadsheet, 'Pdf');
43-
self::assertInstanceOf(Writer\Pdf\Mpdf::class, $actual);
44-
}
45-
46-
#[\PHPUnit\Framework\Attributes\DataProvider('providerCreateReader')]
39+
#[DataProvider('providerCreateReader')]
4740
public function testCreateReader(string $name, string $expected): void
4841
{
4942
$actual = IOFactory::createReader($name);
@@ -64,22 +57,14 @@ public static function providerCreateReader(): array
6457
];
6558
}
6659

67-
public function testRegisterReader(): void
68-
{
69-
IOFactory::registerReader('Custom', Reader\Html::class);
70-
$actual = IOFactory::createReader('Custom');
71-
self::assertInstanceOf(Reader\Html::class, $actual);
72-
}
73-
74-
#[\PHPUnit\Framework\Attributes\DataProvider('providerIdentify')]
60+
#[DataProvider('providerIdentify')]
7561
public function testIdentifyCreateLoad(string $file, string $expectedName, string $expectedClass): void
7662
{
7763
$actual = IOFactory::identify($file);
7864
self::assertSame($expectedName, $actual);
7965
$actual = IOFactory::createReaderForFile($file);
8066
self::assertSame($expectedClass, $actual::class);
81-
$actual = IOFactory::load($file);
82-
self::assertInstanceOf(Spreadsheet::class, $actual);
67+
IOFactory::load($file);
8368
}
8469

8570
public static function providerIdentify(): array
@@ -159,21 +144,6 @@ public function testIdentifyExistingDirectoryThrowExceptions(): void
159144
IOFactory::identify('.');
160145
}
161146

162-
public function testRegisterInvalidWriter(): void
163-
{
164-
$this->expectException(Writer\Exception::class);
165-
166-
// @phpstan-ignore-next-line
167-
IOFactory::registerWriter('foo', 'bar');
168-
}
169-
170-
public function testRegisterInvalidReader(): void
171-
{
172-
$this->expectException(ReaderException::class);
173-
174-
IOFactory::registerReader('foo', 'bar');
175-
}
176-
177147
public function testCreateInvalidWriter(): void
178148
{
179149
$this->expectException(Writer\Exception::class);

0 commit comments

Comments
 (0)