ibexa/rector
ibexa/rector provides Rector rules for automatically upgrading Ibexa DXP projects between versions. Install as a dev dependency (composer require --dev ibexa/rector:^5.0), configure rector.php with an IbexaSetList set such as IBEXA_50, then run Rector on your source and tests.
This is not a Laravel package and there is nothing to register in config/app.php. It is a dev-only Rector rule set for Ibexa DXP, so the only reason to install it is that you have Ibexa DXP code (or inherited eZ Platform code) in the repo and an upgrade scheduled. If you are in a Laravel-only codebase, stop here.
Minimal path:
composer require --dev ibexa/rector:^5.0
Create rector.php in the project root. The path list is the part that actually matters — the README's example assumes a standard src/ + tests/ layout, which is often wrong for a real Ibexa install:
declare(strict_types=1);
use Ibexa\Contracts\Rector\Sets\IbexaSetList;
use Rector\Config\RectorConfig;
return RectorConfig::configure()
->withPaths([
__DIR__ . '/src',
__DIR__ . '/tests',
])
->withSets([
IbexaSetList::IBEXA_50->value,
]);
Run it scoped to one directory first, and always as a dry run before writing:
php ./bin/rector process src --dry-run
php ./bin/rector process src
Rector's binary is typically vendor/bin/rector; the README's ./bin/rector reflects the Ibexa DXP skeleton, where the binary lives in the project's own bin/. Check which one exists in your tree before assuming either.
Where to look first. Don't read the rules — read the diffs. Run --dry-run against the smallest module you own and look at what it wants to change. That output is the fastest way to learn (a) whether the rule set actually matches your version, and (b) which parts of your codebase are already on the deprecated path. The set constants themselves (IbexaSetList::IBEXA_50, and the backported 4.6 set) are the real API surface; everything else is Rector.
First use case. The canonical one is a major-version upgrade lane: branch, pin the rule set for the target version, run per-directory, review each diff, commit. The second most valuable use is not an upgrade at all — running the current set against an unmodified codebase to surface code that will break on the next upgrade, while you still have time to fix it deliberately.
One branch, one set, one directory at a time. The rule sets are versioned, so scope a branch to a single target version and a single path. php ./bin/rector process src/Controller then src/Form then src/EventListener produces reviewable commits. A whole-repo run produces a diff nobody can review, which defeats the purpose.
Version-lockstep is the governing rule. The package major tracks the DXP major. ^5.0 against a 4.6 codebase is a misconfiguration, not a downgrade path. Pin to the set that matches the version you are upgrading to, and pin the Composer constraint tightly enough that a composer update on someone else's branch doesn't silently swap rule sets mid-migration.
Run in CI as a drift detector, not a fixer. Two useful jobs:
# informational: fails when new upgrade debt lands
rector:dry-run:
script: vendor/bin/rector process src --dry-run
allow_failure: true
Once the target upgrade is done and merged, flip the job to hard-fail so subsequent PRs can't reintroduce the patterns you just paid to remove.
Use --dry-run output as a migration checklist. Rector's dry-run diff is a per-file inventory of what the upgrade breaks. Diff that list against your open tickets — anything the rules don't touch is manual work, and that gap is your real estimate. Rules found in the changelog give you the shape of what's covered: legacy class alias removal, interface and method implementation removal, ibexa/system-info deprecations, RepositoryConfigurationProvider renames, REST changes, deprecated Twig functions and filters, field type configuration constant changes, magic properties → getters, Cart\MoneyFactory → PC\IntlMoneyFactory, icon renames, Ibexa Migrations step signatures, and DeduplicateStamp moving to the Contracts namespace.
Pair each run with the test suite, not with eyeballing. The risk here is semantic, not syntactic: a rule can produce code that compiles and behaves differently. The loop is rector process → phpunit → commit, one directory at a time. Where coverage is thin, the rule output needs an owner who can reason about equivalence — budget review capacity, not setup time.
Extend with your own rules on top of the set. The most valuable pattern for a shop with its own internal Ibexa conventions is registering the Ibexa set plus your own custom Rectors:
->withSets([IbexaSetList::IBEXA_50->value])
->withRules([App\Rector\MyInternalApiRector::class])
Custom rules go in src under an autoloaded namespace and are excluded from processing via ->withSkip(). Do this once you've run the stock set and noticed the same manual fix appearing in every diff — that repetition is a custom rule waiting to be written.
Treat the changelog as low-signal. A large fraction of releases say "No significant changes" — that's a monorepo pipeline artefact, not a quality signal. Don't infer stability from release cadence. Read the entries that name a rule, ignore the rest, and upgrade deliberately rather than tracking latest.
Licensing is the gating item, not a footnote. The package is dual-licensed GPL-2.0 or the Ibexa Business Use License, and the BUL branch requires a valid Ibexa DXP subscription. It appears on Packagist but is not cleanly OSI-single-licensed. Get legal sign-off before adoption if any artefact is redistributed or if your subscription status is unclear. This is a real blocker, not a formality.
Dev-only means blast radius is zero in production — and also that nothing warns you. There's no runtime, no service provider, no config to break. The entire risk lives in the diffs. That's why the dry run is not optional and why a green test suite is the only meaningful safety net.
Rules encode known deprecations only. They will not catch removed extension points, architectural breaks, database schema changes, or config file migrations. Treat this as one lane of the migration, never the migration. Missing files in the dry-run output is expected, not a bug.
Watch the path list before blaming the rules. A rule that "doesn't fire" is almost always a path that isn't in withPaths(). Ibexa projects frequently have code outside src/ — bundles, config/, custom migration directories, legacy extension/ folders. Add them explicitly and re-run before concluding the rule set is incomplete.
Rector 2.x means PHP 8.1+ baseline, and the 5.0 line aligns with Symfony 7.4. If the application is still on Symfony 5.4/PHP 7.4, you cannot run this tool against it — the migration order becomes "PHP and Symfony first, Rector second," even though Rector is the thing that would help you do the rest.
Version-lockstep confusion is the most common misconfiguration. Installing ^5.0 on a 4.6 codebase produces a wall of diffs that look plausible and are wrong. Confirm the target version against the set constant name before you trust a single diff.
Community support is effectively nonexistent — plan for vendor channel. One star, zero dependents, no Stack Overflow corpus, no third-party writeups. When a rule mangles something, the answer isn't on the internet. It's an Ibexa Jira issue, filed through the vendor relationship. If you don't have that relationship, you own the debugging yourself — which usually means reading the rule's source in vendor/ibexa/rector and stepping through it.
When a rule misfires, skip narrowly rather than disabling the set. Add the specific file or rule to ->withSkip() and leave the rest of the set active. Killing the whole set to work around one bad file trades a small annoyance for losing the entire lane.
Read the rule before you trust a rewrite of code you didn't write. Magic-property-to-getter conversions, interface/method removals, and Twig function renames are the categories where automated output most often looks correct and calls into something subtly different. When a diff touches a class you don't own the mental model for, read the rule, not just the patch.
Commit the rector.php to the repo. It's project config, and the path list encodes decisions about your layout that the next developer won't reconstruct from the README's src/tests example.
How can I help you explore Laravel packages today?