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

  1. Implement the full IReader interface (canRead(), load(), listWorksheetInfo(), etc.) or extend Reader\BaseReader
  2. Fix the namespace/class-string and pass the ::class constant
  3. 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

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


AI-assisted analysis of PHPOffice/PhpSpreadsheet@65b080eef4 (2026-08-17). Data as JSON: /api/errors/5f221b612b78a91d. Report an issue: GitHub.