Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IOFactory::identify and Custom Reader/Writer #4361

Merged
merged 5 commits into from
Feb 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
### Changed

- Start migration to Phpstan 2. [PR #4359](https://github.com/PHPOffice/PhpSpreadsheet/pull/4359)
- IOFactory identify can return, and createReader and CreateWriter can accept, a class name rather than a file type. [Issue #4357](https://github.com/PHPOffice/PhpSpreadsheet/issues/4357) [PR #4361](https://github.com/PHPOffice/PhpSpreadsheet/pull/4361)

### Moved

Expand Down
12 changes: 11 additions & 1 deletion docs/topics/reading-files.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,10 @@ method to identify the reader that you need, before using the
```php
$inputFileName = './sampleData/example1.xls';

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

Prior to release 4.1.0, `identify` returns a file type.
It may be more useful to return a fully-qualified class name,
which can be accomplished using a parameter introduced in 4.1.0:
```php
$inputFileType = \PhpOffice\PhpSpreadsheet\IOFactory::identify($inputFileName, null, true);
```

As with the IOFactory `load()` method, you can also pass an array of formats
for the `identify()` method to check against if you know that it will only
be in a subset of the possible formats that PhpSpreadsheet supports.
Expand Down
36 changes: 24 additions & 12 deletions src/PhpSpreadsheet/IOFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,16 @@ abstract class IOFactory
*/
public static function createWriter(Spreadsheet $spreadsheet, string $writerType): IWriter
{
if (!isset(self::$writers[$writerType])) {
throw new Writer\Exception("No writer found for type $writerType");
}
/** @var class-string<IWriter> */
$className = $writerType;
if (!in_array($writerType, self::$writers, true)) {
if (!isset(self::$writers[$writerType])) {
throw new Writer\Exception("No writer found for type $writerType");
}

// Instantiate writer
$className = self::$writers[$writerType];
// Instantiate writer
$className = self::$writers[$writerType];
}

return new $className($spreadsheet);
}
Expand All @@ -74,12 +78,16 @@ public static function createWriter(Spreadsheet $spreadsheet, string $writerType
*/
public static function createReader(string $readerType): IReader
{
if (!isset(self::$readers[$readerType])) {
throw new Reader\Exception("No reader found for type $readerType");
}
/** @var class-string<IReader> */
$className = $readerType;
if (!in_array($readerType, self::$readers, true)) {
if (!isset(self::$readers[$readerType])) {
throw new Reader\Exception("No reader found for type $readerType");
}

// Instantiate reader
$className = self::$readers[$readerType];
// Instantiate reader
$className = self::$readers[$readerType];
}

return new $className();
}
Expand Down Expand Up @@ -109,12 +117,14 @@ public static function load(string $filename, int $flags = 0, ?array $readers =
/**
* Identify file type using automatic IReader resolution.
*/
public static function identify(string $filename, ?array $readers = null): string
public static function identify(string $filename, ?array $readers = null, bool $fullClassName = false): string
{
$reader = self::createReaderForFile($filename, $readers);
$className = $reader::class;
if ($fullClassName) {
return $className;
}
$classType = explode('\\', $className);
unset($reader);

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

/**
* Register a reader with its type and class name.
*
* @param class-string<IReader> $readerClass
*/
public static function registerReader(string $readerType, string $readerClass): void
{
Expand Down
12 changes: 12 additions & 0 deletions tests/PhpSpreadsheetTests/CustomReader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace PhpOffice\PhpSpreadsheetTests;

use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader;

// Used in IOFactoryRegister tests
class CustomReader extends XlsxReader
{
}
12 changes: 12 additions & 0 deletions tests/PhpSpreadsheetTests/CustomWriter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace PhpOffice\PhpSpreadsheetTests;

use PhpOffice\PhpSpreadsheet\Writer\Html as HtmlWriter;

// Used in IOFactoryRegister tests
class CustomWriter extends HtmlWriter
{
}
87 changes: 87 additions & 0 deletions tests/PhpSpreadsheetTests/IOFactoryRegisterTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?php

declare(strict_types=1);

namespace PhpOffice\PhpSpreadsheetTests;

use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Reader;
use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer;
use PHPUnit\Framework\Attributes;
use PHPUnit\Framework\TestCase;

// Separate processes because register arrays are static
#[Attributes\RunTestsInSeparateProcesses]
class IOFactoryRegisterTest extends TestCase
{
public function testRegisterWriter(): void
{
IOFactory::registerWriter('Pdf', Writer\Pdf\Mpdf::class);
$spreadsheet = new Spreadsheet();
$actual = IOFactory::createWriter($spreadsheet, 'Pdf');
self::assertInstanceOf(Writer\Pdf\Mpdf::class, $actual);
}

public function testRegisterReader(): void
{
IOFactory::registerReader('Custom', Reader\Html::class);
$actual = IOFactory::createReader('Custom');
self::assertInstanceOf(Reader\Html::class, $actual);
}

public function testRegisterInvalidWriter(): void
{
$this->expectException(Writer\Exception::class);
$this->expectExceptionMessage('writers must implement');
IOFactory::registerWriter('foo', 'bar'); // @phpstan-ignore-line
}

public function testRegisterInvalidReader(): void
{
$this->expectException(ReaderException::class);
$this->expectExceptionMessage('readers must implement');
IOFactory::registerReader('foo', 'bar'); // @phpstan-ignore-line
}

public static function testRegisterCustomReader(): void
{
IOFactory::registerReader(IOFactory::READER_XLSX, CustomReader::class);
$inputFileName = 'tests/data/Reader/XLSX/1900_Calendar.xlsx';
$loadSpreadsheet = IOFactory::load($inputFileName);
$sheet = $loadSpreadsheet->getActiveSheet();
self::assertSame('2022-01-01', $sheet->getCell('A1')->getFormattedValue());
$loadSpreadsheet->disconnectWorksheets();

$reader = new CustomReader();
$newSpreadsheet = $reader->load($inputFileName);
$newSheet = $newSpreadsheet->getActiveSheet();
self::assertSame('2022-01-01', $newSheet->getCell('A1')->getFormattedValue());
$newSpreadsheet->disconnectWorksheets();

$inputFileType = IOFactory::identify($inputFileName, null, true);
$objReader = IOFactory::createReader($inputFileType);
self::assertInstanceOf(CustomReader::class, $objReader);
$objSpreadsheet = $objReader->load($inputFileName);
$objSheet = $objSpreadsheet->getActiveSheet();
self::assertSame('2022-01-01', $objSheet->getCell('A1')->getFormattedValue());
$objSpreadsheet->disconnectWorksheets();
}

public static function testRegisterCustomWriter(): void
{
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->setCellValue('A1', 1);
$writer = new CustomWriter($spreadsheet);
$html = $writer->generateHtmlAll();
self::assertStringContainsString('<td class="column0 style0 n">1</td>', $html);
IOFactory::registerWriter(IOFactory::WRITER_HTML, CustomWriter::class);
$objWriter = IOFactory::createWriter($spreadsheet, CustomWriter::class);
self::assertInstanceOf(CustomWriter::class, $objWriter);
$html2 = $objWriter->generateHtmlAll();
self::assertStringContainsString('<td class="column0 style0 n">1</td>', $html2);
$spreadsheet->disconnectWorksheets();
}
}
15 changes: 0 additions & 15 deletions tests/PhpSpreadsheetTests/IOFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -159,21 +159,6 @@ public function testIdentifyExistingDirectoryThrowExceptions(): void
IOFactory::identify('.');
}

public function testRegisterInvalidWriter(): void
{
$this->expectException(Writer\Exception::class);

// @phpstan-ignore-next-line
IOFactory::registerWriter('foo', 'bar');
}

public function testRegisterInvalidReader(): void
{
$this->expectException(ReaderException::class);

IOFactory::registerReader('foo', 'bar');
}

public function testCreateInvalidWriter(): void
{
$this->expectException(Writer\Exception::class);
Expand Down