rector/rector-phpoffice
Rector rules for PhpOffice libraries that automate upgrading PhpSpreadsheet, PhpWord, and related packages. It applies safe, version-aware code transformations so you can modernize legacy PhpOffice code with less manual work and fewer regressions.
This package provides Rector rules that automate the mechanical parts of migrating legacy PHPExcel (PHPExcel_*) code to PhpSpreadsheet (PhpSpreadsheet\*). It is not a runtime library — think of it as a one-shot codemod you run in a branch, review, and discard.
Minimal setup in a Laravel app that still references PHPExcel_* classes (common in older codebases, custom import/export services, or vendored scripts):
composer require --dev rector/rector rector/rector-phpoffice
Create rector.php at the project root:
<?php
use Rector\Config\RectorConfig;
use RectorPhpOffice\Set\PhpOfficeSetList; // verify exact FQCN in the package README
return RectorConfig::configure()
->withPaths([
__DIR__ . '/app',
__DIR__ . '/database',
])
->withSets([
PhpOfficeSetList::PHP_OFFICE_TO_PHPSPREADSHEET,
]);
Then, before touching anything else, run a dry run to see the blast radius:
vendor/bin/rector process --dry-run
Where to look first: read the package's README and config/sets/ directory to confirm the exact set name and which rules are bundled. Stars and dependent counts are effectively zero here, so treat the package source as the source of truth over any blog post. Also grep your codebase for PHPExcel first — if nothing matches, you don't need this package at all (Maatwebsite/Laravel-Excel, for example, already targets PhpSpreadsheet and wouldn't benefit).
First real use case: a legacy import service that still calls PHPExcel_IOFactory::load(), PHPExcel_Cell::columnIndexFromString(), and getActiveSheet(). Run Rector on only that directory with --dry-run, eyeball the diff, then apply.
Branch-per-migration workflow. Because Rector rewrites code in place, do the migration on a dedicated branch and commit the raw Rector output before any manual fixes. That gives you a clean diff to review and makes it trivial to cherry-pick or revert individual files.
Three-phase migration:
phpoffice/phpexcel → phpoffice/phpspreadsheet in composer.json, update the lockfile, and let the autoloader fail loudly. Rector only rewrites source; it can't fix your dependency graph.PHPExcel_Settings constants).Scope it narrow. Point withPaths() at the modules that actually import PHPExcel rather than __DIR__ . '/app'. Rector walks everything you give it, and a broad path set slows runs and produces noise in files that have nothing to do with the migration.
Layer with your existing Rector setup. If you already run Rector for upgrades (e.g. LevelSetList::UP_TO_PHP_82), put the PHPOffice set in the same config so one rector process applies both — but on the migration branch, run the PHPOffice set alone first so you can attribute every change.
Pair with static analysis as a safety net. After running Rector, run PHPStan or Psalm against the changed files. Rector's renames produce code that looks correct but can reference methods that no longer exist with the same signature. Static analysis catches those before tests do.
CI guard (optional). Add a small CI step that runs rector process --dry-run on the legacy paths and fails if it would change anything — this keeps new PHPExcel usage from creeping back in during the migration window.
PHPExcel_IOFactory → PhpSpreadsheet\IOFactory, PHPExcel_Worksheet → PhpSpreadsheet\Worksheet\Worksheet, etc.) are the easy 60%. Writers, readers, IOFactory::createWriter(), chart handling, and Calculation\Calculation all changed signatures. Rector output will still need manual review.PHPExcel_* classes map cleanly. Some were split, renamed into sub-namespaces, or removed. Expect unresolved class references after the run — grep the diff for leftover PHPExcel strings rather than trusting the tool ran to completion.PHPExcel_Foo_Bar convention maps to nested namespaces in PhpSpreadsheet, and edge cases (e.g. PHPExcel_Shared_Date, PHPExcel_Style_*) may or may not be covered depending on the package's rule list. Cross-check each renamed class against the PhpSpreadsheet source.vendor/bin/rector clear-cache if a change seems to be ignored — the cache can mask edits after you tweak rector.php.--dry-run and --diff together. --dry-run shows what would change; without it you're editing files blind. On a legacy codebase the first run can be alarming — start on a single file (vendor/bin/rector process app/Services/LegacyExport.php --dry-run) to calibrate.maatwebsite/excel, that wrapper already targets PhpSpreadsheet and typically exposes an Excel:: facade — don't let a broad Rector path set touch those files. Exclude app/Exports, app/Imports unless you're certain they contain raw PHPExcel.utils/rector/ directory and register them in rector.php for any PHPExcel pattern the package misses (e.g. a project-specific wrapper class). Rector's own docs cover writing AbstractRector subclasses; that's your escape hatch if the package stagnates.PHP_OFFICE_TO_PHPSPREADSHEET vs a set file path like __DIR__ . '/vendor/rector/rector-phpoffice/config/set/php-office.php'). With so little documentation, misreading the constant is the most common first-run failure.require-dev so a future composer update doesn't reintroduce an unmaintained ruleset into your dependency tree.How can I help you explore Laravel packages today?