Search by

palantirnet / drupal-rector

palantirnetshaal

Instant fixes for your Drupal code by using Rector.

Package info

github.com/palantirnet/drupal-rector

Type:rector-extension

pkg:composer/palantirnet/drupal-rector

Statistics

Installs: 5 614 222

Dependents: 34

Suggesters: 1

Stars: 161

Open Issues: 21

1.2.0 2026-11-08 12:57 UTC

This package is auto-updated.

Last update: 2026-11-08 12:59:19 UTC


README

Automate fixing deprecated Drupal code.

Status

Packagist Version Functional tests Unit tests PHPStan

If upgrading from an older version, refresh rector.php by copying from the vendor copy: cp vendor/palantirnet/drupal-rector/rector.php . for a site, or cp vendor/palantirnet/drupal-rector/rector.contrib.php rector.php for a contrib module or theme.

Introduction

Originally created to automate Drupal 9 upgrades; Drupal 8 and 9 rules are still included for legacy projects. You can read more details in the following blog post:

https://www.palantir.net/blog/jumpstart-your-drupal-9-upgrade-drupal-rector

Documentation

Development guides and other resources:

https://www.palantir.net/rector

Changelog and release history:

https://github.com/palantirnet/drupal-rector/releases

Scope and limitations

Drupal 10 and 11 are the primary targets — deprecation coverage spans Drupal 10.0 through 11.5 (Drupal 8/9 rules are included for legacy projects). The development of this tool is prioritized by the perceived impact of the deprecations and updates. There are many deprecations that often involve several components and for each of these there are several ways to address the deprecation.

We've tried to determine impact based on:

  • The use of the deprecated functionality in the contributed modules on Drupal.org
  • If there are simple to develop ways to fix the deprecation

So, high impact (the code works in newer versions of Drupal for a large number of people), low effort (we can develop the rule based on our knowledge of Rector).

Common limitations

Known limitations are listed in the comment documentation for each rule.

Common limitations include:

  • Using static calls like Drupal->service('my_service')->myMethod(); rather than injecting the service into the class
  • Skipping complex use cases, such as when optional arguments are passed as variables
  • Handling use statements in weird ways. Rector has a global option to handle use statements and we think the benefits outweigh the drawbacks such as weird placement or lack of handling of less common patterns.
  • Handling doc comments in weird ways, particularly around spacing. Rector uses dependencies that sometimes delete empty comments or remove white space. At this point, Drupal Rector does not intend to modify any doc comments, but Rector ends up doing this.

Our hope is that as we learn more about Rector, we may be able to update these rules to add these features.

Issues are managed on drupal.org

https://www.drupal.org/project/issues/rector

For contribution suggestions, please see the later section of this document.

Installation

NOTE: To have the best experience with Drupal Rector, your Drupal site should be running Drupal 10 or higher.

On Drupal 10 preparing for Drupal 11? You can run Drupal Rector against a D10 site to clean up deprecations before upgrading. If your project pins PHPStan 1 (e.g. via mglaman/phpstan-drupal) the direct install below will conflict — see Running against a Drupal 10 project for the standalone-runner recipe that avoids it.

Install Drupal Rector inside a Drupal project.

$ composer require --dev palantirnet/drupal-rector

Trying the 1.0 pre-release? The 1.0.x line is currently published as a beta. Until a stable 1.0.0 is tagged, the command above resolves to the latest stable release — request the pre-release explicitly instead:

$ composer require --dev "palantirnet/drupal-rector:^1.0@beta"

Create a configuration file in your project

You will need to have a rector.php configuration in the root of your repository. This should sit beside your document root such as web or docroot.

This project uses webflo/drupal-finder to find your document root that contains Drupal.

This package ships two starting points. Copy the one that matches what you are upgrading.

Sites. The code only has to run on the Drupal version you have installed:

cp vendor/palantirnet/drupal-rector/rector.php .

It loads the composer-based set, which fixes what is deprecated on your installed drupal/core and nothing newer, without backward-compatibility wrappers.

Contrib modules and themes. The code has to keep working on every Drupal version in core_version_requirement:

cp vendor/palantirnet/drupal-rector/rector.contrib.php rector.php

It lists the Drupal10SetList::DRUPAL_10 and Drupal11SetList::DRUPAL_11 sets by hand and turns on backward-compatibility wrapping. Set setMinimumCoreVersionSupported() to the lowest Drupal version your module supports, and run it against the newest Drupal version you support: a rule is skipped on a core older than its deprecation.

To pick the sets yourself, for example to stop at Drupal 10.3 and leave the deprecations made in Drupal 10.4 alone:

return RectorConfig::configure()
    ->withSets([
        Drupal10SetList::DRUPAL_100,
        Drupal10SetList::DRUPAL_101,
        Drupal10SetList::DRUPAL_102,
        Drupal10SetList::DRUPAL_103,
    ]);

This is more granular than the Drupal10SetList::DRUPAL_10 set. Since Drupal 10.1 there is not real reason not to include later versions. It will detect the installed Drupal version and supply BC wrappers as needed if you enable it in the config.

Note

The Drupal sets contain Drupal rules only. Up to 1.1.3 the Drupal 8, 9 and 10 sets also pulled in Rector's Symfony, PHPUnit and Twig sets; Rector 2.6.2 removed the per-version sets they referenced, so those references are gone. Add the upstream rules yourself if you want them — bearing in mind they follow the installed Symfony/PHPUnit/Twig version, not the Drupal one:

return RectorConfig::configure()
    ->withComposerBased(phpunit: true, symfony: true, twig: true)
    ->withSets([Drupal10SetList::DRUPAL_10]);

On a Drupal 11 site that means Symfony 7 rules, which break code that still has to run on Drupal 10. If your module supports both, register the individual upstream rules you need instead of the whole composer-based set.

Composer-based sets (automatic version selection)

Instead of listing sets by hand, you can let Rector pick the rules from the installed drupal/core version:

return RectorConfig::configure()
    ->withComposerBased(drupal: true);

Note

withComposerBased(drupal: true) needs Rector 2.8.4 or newer. Earlier releases prefix the class that toggle refers to, and fail with Class "RectorPrefix…\DrupalRector\Set\DrupalSetList" not found (rectorphp/rector-src#8897). A bare ->withComposerBased() enables the same thing from Rector 2.8.2 on any project with drupal/core installed. ->withSets([DrupalSetList::COMPOSER_BASED]) is equivalent and works on every supported version.

DrupalSetList::COMPOSER_BASED registers every Drupal 10 and later rule at once, each one bound to the drupal/core versions its deprecation applies to: from the version it was introduced in up to, but not including, the major after the one the API is removed in — >=11.4.0 <14.0.0 for a deprecation removed in 13.0.0. Rector activates only the rules whose constraint the installed core satisfies, so a site on 11.2 gets every rule whose deprecation landed in 11.2 or earlier and whose removal is not more than one major behind it, and never a later minor's. Because the installed version is known exactly, the otherwise opt-in breaking renames (whose replacement only exists from a given minor onward) are included — they cannot fatal on a core that is guaranteed to have the replacement.

To see the rules that carry their configuration in the set, and whether the installed core activates them:

vendor/bin/rector composer-based

A rule that takes no configuration states its version on the rule class instead, through Rector's ComposerPackageConstraintInterface, so it is skipped on a core outside it too: below the lower bound, past the upper bound, or when drupal/core is installed as a branch version such as dev-main. That filter is global, not per set: those rules stay off even when you load a Drupal11SetList set by hand. Configurable rules carry their bound only in the composer-based set, so the per-minor sets run them on any core.

The Drupal 8 and 9 rules are not part of this set. Every API they rewrite was removed by Drupal 10 or 11, so on a supported core they have nothing left to fix. They stay available for older code through Drupal8SetList and Drupal9SetList. Their rule classes keep only the lower bound, the version the deprecation landed in, so those sets apply them on any core from then on.

This is the backward-compatibility-safe counterpart to listing sets manually: it fixes what is deprecated on your installed core.

Requires rector/rector ^2.8.4. RectorConfig::ruleWithConfigurationComposerVersionBound() landed in 2.6 (rectorphp/rector-src#8244), and from 2.6.5 withSetProviders() ignores its arguments, so the deprecated DrupalSetProvider can stay an empty class (see rectorphp/rector#9778).

DrupalRectorSettings

Both shipped configs include a DrupalRectorSettings block that controls two behaviours:

Backward-compatibility wrapping — when enabled, rule results are wrapped in DeprecationHelper::backwardsCompatibleCall() so the code works on both the old and new Drupal API simultaneously. rector.php disables it, rector.contrib.php enables it. Enable it when you need the output to run on multiple Drupal versions at the same time:

$rectorConfig->singleton(DrupalRectorSettings::class, fn () =>
    (new DrupalRectorSettings())
        ->enableBackwardCompatibility()
);

Minimum supported Drupal version (contrib modules) — if you are running Rector against a contrib module that must stay compatible with an older Drupal release, set minimumCoreVersionSupported so BC wrappers are emitted correctly even when your development environment runs a newer Drupal:

$rectorConfig->singleton(DrupalRectorSettings::class, fn () =>
    (new DrupalRectorSettings())
        ->enableBackwardCompatibility()
        ->setMinimumCoreVersionSupported('10.5.0')
);

Note

These settings only take effect with sets you list yourself. The composer-based set registers its own DrupalRectorSettings with backward-compatibility wrapping off, and Rector keeps the first registration, so the one in your rector.php is ignored.

Cleaning up BC wrappers (contrib modules)

If you previously used backward-compatibility wrapping and have since raised your module's minimum supported Drupal version, use DeprecationHelperRemoveRector to strip the now-redundant wrappers. It replaces each DeprecationHelper::backwardsCompatibleCall() with the new API call directly, for any deprecation introduced before your configured minimum version.

use DrupalRector\Rector\Deprecation\DeprecationHelperRemoveRector;
use DrupalRector\Rector\ValueObject\DeprecationHelperRemoveConfiguration;

$rectorConfig->ruleWithConfiguration(DeprecationHelperRemoveRector::class, [
    new DeprecationHelperRemoveConfiguration('10.3.0'),
]);

With the above, a wrapper like:

DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '9.1.0',
    fn() => \Drupal::service('password_generator')->generate(),
    fn() => user_password()
);

becomes:

\Drupal::service('password_generator')->generate();

Wrappers for deprecations introduced at or after your minimum version are left untouched. The rule is commented out in rector.contrib.php — uncomment and set the version when you are ready to clean up.

Suggested workflow

  1. Analyze your code with Rector and review suggested changes:
$ vendor/bin/rector process web/modules/contrib/[YOUR_MODULE] --dry-run
  1. Apply suggested changes:
$ vendor/bin/rector process web/modules/contrib/[YOUR_MODULE]

You can find more information about Rector here.

Converting hooks to OOP hook classes (run as a separate pass)

Drupal Rector ships HookConvertRector, which converts procedural hook implementations (mymodule_form_alter(), …) into a #[Hook]-attributed class under src/Hook/. This rule must be run on its own, as a second pass, after your normal deprecation run — never in the same configuration as the deprecation sets.

To convert hooks, run your deprecation pass first, then point Rector at the dedicated config that registers only HookConvertRector:

# Pass 1 — fix deprecations in place (your normal config)
$ vendor/bin/rector process web/modules/contrib/[YOUR_MODULE]

# Pass 2 — convert hooks to OOP hook classes
$ vendor/bin/rector process web/modules/contrib/[YOUR_MODULE] \
    --config=vendor/palantirnet/drupal-rector/rector-hook-convert.php

Why two passes? HookConvertRector moves each hook body into a brand-new src/Hook/*Hooks.php file that it writes directly to disk. Rector (2.x) cannot feed a newly-created file back through the rule pipeline within the same run, so a deprecated API call that lives inside a hook body would be copied into the new class unchanged if hook conversion ran together with the deprecation sets. Running deprecations first means the bodies are already fixed before they are moved. For this reason the example rector.php above intentionally does not register HookConvertRector.

Development and contribution suggestions

Thanks for your interest in contributing!

Our goal is to make contributing to this project easy for people. While we've made certain architectural decisions here to hopefully achieve that goal, it's a work in progress and feedback is appreciated.

Adding a Rector rule

If you would like to submit a Rector rule, we are looking for the following:

  • A Rector rule class, see src/Rector/Deprecation for existing rules. Copy an existing class as a starting point.
  • A test class in tests/src/Drupal{8,9,10,11}/Rector/ and fixture files in tests/src/Drupal*/Rector/**/fixture/
  • An updated configuration file that registers the Rector rule, see config/drupal-{8,9,10,11}/

Guides

A few guides are currently available and we encourage people to create additional guides to provide their perspective and help us better understand this tool together.

Additional documentation and links

https://www.palantir.net/rector

Quick overview

Create a Rector rule class

Rector rules should be named after the deprecation, including the class name.

Drupal::url() -> DrupalUrlRector.php drupal_set_message() -> DrupalSetMessageRector.php

We would like one Rector rule per deprecation. Some deprecations include updating multiple things and those would be separate rules.

All drupal-rector rules extend AbstractDrupalCoreRector (found in src/Rector/AbstractDrupalCoreRector.php) rather than Rector's own AbstractRector. This base class provides three things automatically:

  • Version gating — skips the rule if the installed Drupal version predates the deprecation via rectorShouldApplyToDrupalVersion()
  • BC wrapping — when backward-compatibility mode is enabled, wraps Expr→Expr results in DeprecationHelper::backwardsCompatibleCall() automatically
  • Configuration pattern — you implement refactorWithConfiguration(Node $node, VersionedConfigurationInterface $configuration) instead of refactor()

To avoid duplication, we have created base classes for simple repeated patterns where possible. These end in Base.php and are located in src/Rector/Deprecation/Base. In many of these rules, you will extend the base class, define class properties, add a class comment, and define the definition.

Rector supports passing parameters to rules and you can also define your rules in a variety of ways. To avoid confusion for new developers, we're trying to avoid these advanced features so that someone with limited familiarity with the tool can easily determine where things are located and what they are doing. If the copy & paste challenge isn't worth this trade-off, we can re-evaluate it as we go. Suggestions appreciated.

Create / Update a configuration file

The configuration files in config/drupal-{8,9,10,11}/ are broken down by Drupal minor versions.

Add your Rector rule to the relevant file. Always add a comment with a link to the issue and change record.

The key is the fully qualified class name of the Rector rule. The key is the yaml null value ~.

Pinning dev dependencies

If there are conflicts with Rector, the package version can be conflicted with conflict on rector/rector and phpstan/phpstan.

Credits

Current development is sponsored by SWIS.nl.
Current development is sponsored by Palantir.net.
Initial development is sponsored by Pronovix.