PHPOffice/PhpSpreadsheet · error · PhpOffice\PhpSpreadsheet\Reader\Exception
Registered readers must implement PhpOffice\PhpSpreadsheet\R
Error message
Registered readers must implement PhpOffice\PhpSpreadsheet\Reader\IReader
What it means
IOFactory::registerReader() validates the class-string with is_a($readerClass, IReader::class, true) before storing it. Registering a class that does not implement PhpOffice\PhpSpreadsheet\Reader\IReader - nonexistent class names, namespace typos, wrong base class - throws at registration time rather than at load time.
Source
Thrown at src/PhpSpreadsheet/IOFactory.php:283
*/
public static function registerWriter(string $writerType, string $writerClass): void
{
if (!is_a($writerClass, IWriter::class, true)) {
throw new Writer\Exception('Registered writers must implement ' . IWriter::class);
}
self::$writers[$writerType] = $writerClass;
}
/**
* Register a reader with its type and class name.
*
* @param class-string<IReader> $readerClass
*/
public static function registerReader(string $readerType, string $readerClass): void
{
if (!is_a($readerClass, IReader::class, true)) {
throw new Reader\Exception('Registered readers must implement ' . IReader::class);
}
self::$readers[$readerType] = $readerClass;
}
/**
* @return array<string, class-string<IReader>>
*
* @internal
*
* @codeCoverageIgnore
*/
public static function getReaders(): array
{
return self::$readers;
}
}
View on GitHub (pinned to 65b080eef4)
Solutions
- Implement the full IReader interface (canRead(), load(), listWorksheetInfo(), etc.) or extend Reader\BaseReader
- Fix the namespace/class-string and pass the ::class constant
- Pre-check with is_a($class, IReader::class, true) when the class name comes from user/config input
Example fix
// before
class MyReader { /* no IReader */ }
IOFactory::registerReader('MyFormat', MyReader::class);
// after
class MyReader extends \PhpOffice\PhpSpreadsheet\Reader\BaseReader
{
// implement canRead() and loadSpreadsheetFromFile()
}
IOFactory::registerReader('MyFormat', MyReader::class); Defensive patterns
Strategy: type-guard
Type guard
/** Guard before IOFactory::registerReader(). */
function isRegisterableReader(string $class): bool
{
return class_exists($class)
&& is_a($class, \PhpOffice\PhpSpreadsheet\Reader\IReader::class, true);
}
if (!isRegisterableReader($readerClass)) {
throw new InvalidArgumentException("$readerClass must implement IReader");
}
IOFactory::registerReader('MyFormat', $readerClass); Try / catch
try {
IOFactory::registerReader('MyFormat', $configuredReaderClass);
} catch (\PhpOffice\PhpSpreadsheet\Reader\Exception $e) {
error_log("Skipping reader registration: {$e->getMessage()}");
} Prevention
- Extend Reader\BaseReader rather than implementing IReader piecemeal
- Use ClassName::class for registrations; validate config-sourced names with is_a() first
- During PHPExcel migrations, remember interface names changed (IReader lives in the Reader namespace)
When it happens
Trigger: IOFactory::registerReader('MyFormat', MyReader::class) where MyReader is a plain class; passing an instance instead of the class-string; migrating PHPExcel-era readers that implemented PHPExcel_Reader_IAbstract.
Common situations: Custom readers for in-house file formats; registrations driven by plugin configuration; upgrades from PHPExcel where interface names changed.
Related errors
- Registered writers must implement PhpOffice\PhpSpreadsheet\W
- No reader found for type $readerType
- Unable to identify a reader for this file
- Cannot load invalid XML ${fileOrString}: ${filename}
- No writer found for type $writerType
AI-assisted analysis of PHPOffice/PhpSpreadsheet@65b080eef4 (2026-08-17).
Data as JSON: /api/errors/5f221b612b78a91d.
Report an issue: GitHub.