docs: prepare CHANGELOG and README for 1.0.0-beta1 (#362)
Pre-release[1.0.0-beta1] — 2026-06-11
Adding a load of new rectors, and releasing a few bugfixes that were present in alpha1. Rector has been running against contrib very succesfully.
Added
-
template_preprocess_*()family (Drupal 11.3, #3504125) —
extendedFunctionToServiceRectorcoverage with 14 moretemplate_preprocess_*()
functions deprecated in drupal:11.3.0 and removed in drupal:12.0.0. Each is
rewritten (BC-wrapped viaDeprecationHelper) to the equivalent*Preprocess
service method:table,tablesort_indicator,item_list,region,
maintenance_page,maintenance_task_list,install_page→ThemePreprocess;
image→ImagePreprocess;breadcrumb→BreadcrumbPreprocess;
pager→PagerPreprocess;field,field_multiple_value_form→
FieldPreprocess;menu_local_task,menu_local_action→MenuPreprocess.
(template_preprocess_authorize_report()has no replacement and is
intentionally excluded.) -
RemoveSourceModuleFromMigrateSourceAttributeRector— removes the
source_modulenamed argument from#[MigrateSource]attribute usages. Only
theDrupal\migrate\Attribute\MigrateSourceattribute is targeted (an
attribute of the same short name from another namespace is left untouched).
Thesource_moduleconstructor parameter was removed from the attribute in
drupal:11.2.0; passing#[MigrateSource(source_module: '...')]now raises an
"Unknown named parameter" error at plugin discovery time. The rewrite cannot
be BC-wrapped (an attribute is not anExpr → Exprtransformation) and the
argument is mutually exclusive across minors, so the rule lives in the opt-in
Drupal11SetList::DRUPAL_112_BREAKINGset, not the default deprecation set.
For plugins extendingDrupalSqlBasethesource_modulevalue must be
re-declared via the@MigrateSourceannotation or the migration YAML after
removal — a manual follow-up this rule does not automate. Apply only after
dropping support for Drupal minors that predate 11.2.0.
#3009349 /
change record -
UploadedFileConstraintArrayOptionsToNamedArgsRector— replaces the
deprecated options-array argument ofUploadedFileConstraintwith explicit
named constructor arguments (e.g.new UploadedFileConstraint(['maxSize' => 1024000])
→new UploadedFileConstraint(maxSize: 1024000)). Passing an options array is
deprecated in drupal:11.4.0 and removed in drupal:12.0.0. The transformation is
BC-wrapped: the named-argument constructor was introduced alongside the
deprecation, so the new form would fatal (Unknown named parameter) on
Drupal < 11.4. The rector therefore wraps thenewexpression in
DeprecationHelper::backwardsCompatibleCall(), using named arguments on
Drupal ≥ 11.4 and the original options array on older versions. See
#3561135 and the
change record. -
DRUPAL_114_BREAKING:HelpSearch→SearchHelpSearchclass rename.
Drupal\help\Plugin\Search\HelpSearchwas moved out of thehelpmodule and
renamed toDrupal\search_help\Plugin\Search\SearchHelpSearchin the new
search_helpcore sub-module (system_update_11400(), drupal:11.4.0). Added
as aRenameClassRectorentry todrupal-11.4-breaking.php: the
SearchHelpSearchclass does not exist on any Drupal minor below 11.4, and a
use/::classrename is structural and cannot be BC-wrapped, so applying
it against code that still needs to run on an older minor would fatal there.
Opt in viaDrupal11SetList::DRUPAL_114_BREAKINGonly after dropping support
for Drupal < 11.4 (#3581109). -
CommentLinkBuilderConstructorRector— rewrites the deprecated
5-argumentnew \Drupal\comment\CommentLinkBuilder(...)constructor call to
the new 3-argument form, dropping the$module_handlerand
$entity_type_managerarguments (deprecated in drupal:11.3.0, removed in
drupal:12.0.0). Because the 3-argument signature only exists on Drupal >=
11.3.0, the rewrite is BC-wrapped withDeprecationHelper::backwardsCompatibleCall()
so the original 5-argument call still runs on older Drupal. Only calls with
exactly 5 positional arguments are rewritten. -
ReplaceItemAttributesWithAttributesRector— replaces the deprecated
#item_attributeskey with#attributesin render arrays whose#themeis
image_formatterorresponsive_image_formatter. The#item_attributes
property is deprecated in drupal:11.4.0 and removed in drupal:12.0.0. The
transformation is BC-wrapped: the#attributesvariable was only added to
these theme hooks in 11.4.0, so a plain rename would silently drop the
attributes on Drupal < 11.4. The rector therefore wraps the array literal in
DeprecationHelper::backwardsCompatibleCall(), using#attributeson
Drupal ≥ 11.4 and the original#item_attributeson older versions. Arrays
with an unrelated (or absent)#theme, and arrays already using
#attributes, are left untouched.
#3554447 /
CR. -
HookRequirementsAlterRenameRector— renames procedural
{module}_requirements_alter()hook implementations to
{module}_runtime_requirements_alter(), deprecated in drupal:11.3.0 and
removed in drupal:13.0.0. The runtime hook is only invoked on Drupal minors
where it exists, so the renamed function is a silent no-op on older Drupal;
this is a non-BC rewrite and ships in the opt-inDRUPAL_113_BREAKINGset, not
the default deprecation set. The rule only renames functions with a single
by-reference parameter, skips thehook_requirements_alter()API-doc function,
and is idempotent — the_runtime_/_update_requirements_alter()hooks are
left untouched. -
ReplaceDrupalStaticResetFileReferencesRector— rewrites
drupal_static_reset('file_get_file_references')and
drupal_static_reset('file_get_file_references:field_columns')to
\Drupal::service('cache.memory')->invalidateTags(['file_references']).
Both static-cache keys were deprecated in drupal:11.4.0 (removed in
drupal:13.0.0) when the file-reference lookup moved to the new
FileReferenceResolverservice, which uses thefile_references
memory-cache tag instead ofdrupal_static(). Only those two literal keys
are matched; otherdrupal_static_reset()calls, calls to
file_get_file_references()itself, and named/unpacked argument forms are
intentionally left for manual review. BC-wrapped viaDeprecationHelper:
thefile_referencescache tag does not exist before drupal:11.4.0, so the
new call would be a silent no-op there — the wrapper keeps the original
drupal_static_reset()on older versions and only switches to the
cache.memoryinvalidation on drupal:11.4.0 and above. -
ReplaceNodeViewControllerRector(+ a companionRenameClassRector
entry) — migrates the deprecated
Drupal\node\Controller\NodeViewControllerto
Drupal\Core\Entity\Controller\EntityViewController(deprecated in
drupal:11.4.0, removed in drupal:13.0.0). The custom rector rewrites
new NodeViewController($etm, $renderer, $currentUser, $entityRepository)
tonew EntityViewController($etm, $renderer), dropping the two extra
constructor arguments thatEntityViewControllerdoes not accept; it matches
both the old and new class names so the argument trim is order-independent
w.r.t. the rename pass. TheRenameClassRectorentry handles the structural
references (use/extends/::class/ type hints). Ships in the opt-in
DRUPAL_114_BREAKINGset: unlike the other entries there, the replacement
class exists on every supported minor (so it never fatals on a missing
symbol), but reparentingextends NodeViewControllerto
extends EntityViewControlleris behaviorally breaking on every minor —
subclasses lose the node-specificcreate()/currentUser/
entityRepository/title()/view()members and can throw an
ArgumentCountErroror call an undefinedtitle(), so it needs manual
review. A subclass's ownparent::__construct($a, $b, $c, $d)is not
rewritten (PHP silently discards the extra arguments).
#3589630 /
CR. -
RenameHookRankingRector— renames the deprecated OOP hook attribute
#[Hook('ranking')]to#[Hook('node_search_ranking')]. Only the
Drupal\Core\Hook\Attribute\Hookattribute is targeted (an attribute of the
same short name from another namespace is left untouched), and only the
'ranking'argument is rewritten — the implementing method name and any
docblocks are unchanged.hook_ranking()is deprecated in drupal:11.3.0 and
removed in drupal:12.0.0; usehook_node_search_ranking()instead. Because
thenode_search_rankinghook is only invoked on Drupal minors where it
exists, a plain rename is a silent no-op on older Drupal, and an attribute is
not anExpr → Exprtransformation so it cannot be BC-wrapped. The rule
therefore lives in the opt-inDrupal11SetList::DRUPAL_113_BREAKINGset, not
the default deprecation set. #1019966 /
change record -
BlockContentSelectionExtendsRector— reparents entity reference
selection plugins for theblock_contententity type from
Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelectionto
Drupal\block_content\Plugin\EntityReferenceSelection\BlockContentSelection.
The hook that automatically filtered non-reusable blocks out of those
selections (block_content_query_entity_reference_alter()) is deprecated and
removed in drupal:12.0.0;BlockContentSelectionperforms that filtering
itself. The rewrite is gated on theEntityReferenceSelectionattribute
carryingentity_types: ["block_content"], soDefaultSelectionsubclasses
for other entity types are left untouched, and the canonical core
BlockContentSelectionis skipped so it is not made to extend itself. Ships
in the opt-inDRUPAL_114_BREAKINGset:BlockContentSelectionwas added to
core alongside the deprecation (a new class, so 11.4.0) and does not exist on
any minor below 11.4, so reparenting onto it would fatal there, and a
class … extends …declaration cannot be BC-wrapped.
#2987159 /
CR. -
RemoveRouteBuilderDeprecatedArgsRector— rewrites the deprecated
6-argumentnew \Drupal\Core\Routing\RouteBuilder(...)instantiation to the
new 4-argument form (deprecated in drupal:11.4.0, removed in drupal:12.0.0).
The$module_handler(arg 3) and$controller_resolver(arg 4) arguments
were removed and$check_providershifted from position 5 to position 3;
YAML route discovery moved to the newYamlRouteDiscoveryservice. Only
6-argument positional calls toRouteBuilderare matched; the new signature
only exists on Drupal ≥ 11.4, so the change is BC-wrapped via
DeprecationHelper::backwardsCompatibleCall(). -
RemoveDrupalToStringTraitRector— removes
use Drupal\Component\Utility\ToStringTrait;from a class body and inserts
an inlinepublic function __toString(): string { return (string) $this->render(); }in its place. The trait was a PHP 7.x workaround for
fatal errors thrown from__toString(); on PHP 8+ exceptions inside
__toString()propagate normally, so the trait is deprecated in
drupal:11.4.0 and removed in drupal:13.0.0. The rector preserves an
existing__toString()if the class already defines one (only the
composition is removed) and keeps any sibling traits in a comma-separated
uselist. The file-leveluse Drupal\Component\Utility\ToStringTrait;
import is intentionally left in place — Rector's generic unused-import
cleanup is a separate concern. The replacement body is pure PHP, so the
transformed code runs on every Drupal version — no BC wrapping needed.
#3548957 /
CR. -
RemoveInstallSchemaSystemSequencesRector— removes deprecated
KernelTestBase::installSchema('system', 'sequences')calls in test
classes. Thesequencestable was deprecated in drupal:10.2.0 and
fully removed in drupal:12.0.0 via #3335756;
the call now throws aLogicExceptionon Drupal 12. The rule removes
the entire statement when the second argument is the string
'sequences'or an array containing only'sequences'; when the
array also lists other tables, only the'sequences'entry is
stripped. Receiver is type-guarded toDrupal\KernelTests\KernelTestBase,
so unrelatedinstallSchema()methods on other classes are left
untouched. See change record
#3349345. -
GroupLegacyToIgnoreDeprecationsRector— replaces the
@group legacyPHPDoc annotation with PHPUnit 10's native
#[\PHPUnit\Framework\Attributes\IgnoreDeprecations]attribute on
both method- and class-level test declarations. Drupal 11 dropped the
symfony/phpunit-bridgedependency and adopted PHPUnit 10, whose
attribute supersedes the bridge's docblock annotation. A Drupal shim
preserves the annotation form for BC, so the rewrite is purely a
forward-compatibility cleanup rather than a hard requirement, but
test classes that already declare the attribute are skipped so the
rule is idempotent. The rector strips just the@group legacyline
from the docblock — surrounding annotations (@covers,@dataProvider,
description text) are preserved — and inserts the attribute
immediately above the method or class declaration. PHPStan cannot
surface this deprecation because@group legacyis a docblock
convention, not a code-level@deprecatedsymbol; this rule must be
applied proactively as part of a PHPUnit 10 migration.
#3417066 /
related: #3365413. -
RemoveAliasManagerCacheMethodCallsRector— deletes calls to
AliasManager::setCacheKey()andAliasManager::writeCache(). Both
methods were deprecated in drupal:11.3.0 and are removed in
drupal:13.0.0 with no replacement — they became no-ops when the path
alias preload cache was replaced by a Fiber-based bulk-lookup strategy.
The receiver must be typed as\Drupal\path_alias\AliasManageror
AliasManagerInterface; this guard prevents accidentally removing
unrelated methods that share the name (notably
ModuleHandler::writeCache()). Removes the entire expression
statement, leaving surrounding code intact. No BC wrapping is needed
since dropping a no-op call is safe on every Drupal version.
#3496369 /
CR. -
EntityFormModeEmptyDescriptionToNullRector— rewrites
EntityFormMode::create([..., 'description' => '', ...])to useNULL
instead of the empty string. Setting the description property of an
EntityFormModeto''was deprecated in drupal:11.2.0 and must beNULL
in drupal:12.0.0. Matches both the short class name (use-imported) and
the fully-qualified\Drupal\Core\Entity\Entity\EntityFormMode::create()
form, and leaves unrelated classes (e.g.EntityViewMode), non-empty
descriptions, and already-migrated NULL values untouched. The replacement
is plain PHP, so no BC wrapping is needed.
#3448457 /
CR. -
ViewsBlockItemsPerPageNoneToNullRector— rewrites
ViewsBlockBase::setConfigurationValue('items_per_page', 'none')to
setConfigurationValue('items_per_page', NULL). The string'none'
was deprecated in drupal:11.2.0 and is removed in drupal:12.0.0;NULL
is the canonical value for inheriting the items-per-page setting from
the underlying view. The receiver is type-guarded toViewsBlockBase
(or any subclass), so unrelatedsetConfigurationValue()calls on
other plugin types are left untouched. The replacement is plain PHP,
so no BC wrapping is needed.
#3520946 /
CR. -
TaxonomyTermPageVariableToViewModeRector— replaces reads of
$variables['page']with$variables['view_mode'] === 'full'inside
taxonomy term preprocess hooks (procedural
*_preprocess_taxonomy_term()functions and class-based
preprocessTaxonomyTerm()methods). The$variables['page']template
variable for taxonomy terms was deprecated in drupal:11.3.0 and is
removed in drupal:13.0.0. Assignment targets ($variables['page'] = …)
are intentionally left untouched so legacy initialisation continues to
work, and unrelated preprocess hooks are not scanned. The replacement
is a pure-PHP===comparison, so the transformed code runs on every
Drupal version — no BC wrapping needed.
#3535439 /
CR. -
ReplaceNonBoolAccessRector— rewrites integer-literal#access
values inside render arrays to proper booleans:1(or any non-zero
integer) becomestrue, and0becomesfalse. Passing non-boolean,
non-AccessResultInterfacevalues to#accesswas deprecated in
drupal:11.4.0 and will be removed in drupal:13.0.0. The rule matches
onArrayItemnodes whose key is the string literal'#access'and
whose value is an integer literal — it deliberately ignores booleans,
variables, function/method calls, and any other expression, because
the correct boolean replacement for those cannot be determined
statically. The replacement is pure PHP (true/false), so no BC
wrapping is needed; the transformed code runs on every Drupal version.
#3526250. -
ReplaceDialogClassOptionRector— rewrites the removed
$dialog_options['dialogClass']key to
$dialog_options['classes']['ui-dialog']innew OpenDialogCommand(...),
new OpenModalDialogCommand(...), andnew OpenOffCanvasDialogCommand(...)
calls.dialogClasswas deprecated in drupal:11.3.0 and removed in
drupal:12.0.0. Receiver narrowing is by FQCN match on the resolved
New_->class(Drupal\Core\Ajax\OpenDialogCommand,
Drupal\Core\Ajax\OpenModalDialogCommand, or
Drupal\Core\Ajax\OpenOffCanvasDialogCommand), and the$dialog_options
argument is located per-class (4th argument forOpenDialogCommand, 3rd for
the modal/off-canvas commands), so unrelated constructors with
similarly-shaped option arrays are left alone. Handles three array
shapes: (a) no existingclasseskey → adds'classes' => ['ui-dialog' => $value];
(b)classesexists withoutui-dialog→ adds'ui-dialog' => $valueinside
it; (c)classes['ui-dialog']already present and both old/new values are
string literals → concatenates with a space. Non-literal values (variables,
function calls, dynamic arrays) at any position cause the rule to skip
rather than guess. The replacement formclasses['ui-dialog']has existed
in core since 10.3.x, so the transformed output runs on every
drupal-rector–supported Drupal minor (D10.3+) — no BC wrapping needed.
#3571054 /
CR. -
RemoveToolkitArgFromImageToolkitOperationConstructorRector—
removes the deprecatedImageToolkitInterface $toolkit4th argument
fromImageToolkitOperationBasesubclass constructors and strips it
from the matchingparent::__construct()call. The parameter was
deprecated in drupal:11.4.0 and will be removed in drupal:13.0.0; the
plugin manager now injects the toolkit viasetToolkit()after
instantiation, enabling constructor autowiring. The rector only fires
when the subclass directly extendsImageToolkitOperationBase, the
constructor has at least five parameters, the 4th is typed exactly as
\Drupal\Core\ImageToolkit\ImageToolkitInterface, and$toolkit
appears exactly once in the constructor body (as the 4th argument of
parent::__construct()). The usage-count walk skips nested closures
and arrow-functions so a$toolkit-shadowing inner scope cannot
inflate the count. No BC wrapping is needed: the parent signature
accepts the unionLoggerInterface|ImageToolkitInterface, so the
transformed code runs on every drupal:11.4.x+ version.
#3559481 /
CR. -
RemoveRendererAddCacheableDependencyNonObjectRector— deletes calls
toRendererInterface::addCacheableDependency($elements, $dependency)
whose second argument is statically provable to be a non-object
(bool,int,float,string,null, orarray). Passing such
values was deprecated in drupal:11.3.0 and will throw in
drupal:13.0.0; at runtime it silently setsmax-age = 0on the
render array, making the page uncacheable for no useful gain. The
rector matches at statement level so the entire expression is
removed, never partially rewritten. The receiver is type-guarded to
\Drupal\Core\Render\RendererInterface(catching the concrete
Rendererand any other implementer), and the argument count must
be exactly two — this distinguishes the call from the single-argument
addCacheableDependency()defined onBubbleableMetadataand
RefinableCacheableDependencyInterface. The PHPStan type of the
dependency must satisfyisObject()->no(), so any call where the
argument might be an object at runtime (variables typed as configs,
entities, ormixed) is left untouched — the rector targets only
the unambiguous primitive-passing mistake the deprecation was added
to flag. No BC wrapping is needed because the removed call is a
silent uncacheability bug on every Drupal version.
#3525388 /
CR. -
DrupalGetHeadersAssocArrayRector— converts the two deprecated
UiHelperTrait::drupalGet()$headersargument shapes to the documented
associative format: integer-keyed colon-separated strings
(['X-Requested-With: XMLHttpRequest']) are split to
['X-Requested-With' => 'XMLHttpRequest'], andnullvalues
(['Accept-Language' => NULL]) become empty strings
(['Accept-Language' => '']). The integer-keyed path requires the
conventionalName: value(colon-space) form and a name part matching
[A-Za-z][A-Za-z0-9-]*, so incidental colon-containing strings (URLs,
paths) are not silently split. Guarded againstDrupal\Tests\BrowserTestBase
soKernelTestBase(which usesHttpKernelUiHelperTraitand does not
emit this deprecation) is left alone. Deprecated in drupal:11.1.0,
removed in drupal:12.0.0; replacement is plain PHP so no BC wrapping
is needed. Live-tested againstpager_serializer.
#3440169 /
CR (indexed headers) /
CR (null values). -
ReplaceHideShowWithPrintedRector— replaces statement-level calls to the
deprecated globalhide()andshow()functions (deprecated in drupal:11.4.0,
removed in drupal:13.0.0) with direct$element['#printed'] = TRUE/FALSE
assignment. Expression-context uses (where the return value is captured) are
intentionally skipped because the original returns the element while the
rewrite would not. Live-tested againstfpa,saml_sp,vertical_tabs_config,
andfield_group_background_image.
#2258355 /
CR. -
GetDrupalRootToRootPropertyRector— rewrites calls to
DrupalKernelInterface::getDrupalRoot()to direct access of the
$this->rootproperty on Drupal base test classes (BrowserTestBase,
KernelTestBase, UnitTestCase).getDrupalRoot()was deprecated in
drupal:11.4.0 and removed in drupal:13.0.0. The receiver is
type-guarded to the listed base classes (and their subclasses), so
unrelatedgetDrupalRoot()methods on other classes are left alone.
No BC wrapping is needed — the$rootproperty has existed on the
trait since Drupal 10.x.
#3589047 /
CR. -
ReplaceLocaleTranslationPathConfigRector— rewrites chained
\Drupal::config('locale.settings')->get('translation.path')(and
equivalents viaconfigFactory()->get('locale.settings')->get(...),
$this->config('locale.settings')->get(...), and similar) to
\Drupal\Core\Site\Settings::get('locale_translation_path', 'public://translations').
Thelocale.settings:translation.pathconfig key was deprecated in
drupal:11.4.0 and is removed in drupal:13.0.0; the interface
translations directory path must now be set as
$settings['locale_translation_path']insettings.php. On older
Drupal the value still lives in config, so the replacement is
BC-wrapped withDeprecationHelper::backwardsCompatibleCall().
Matching is purely structural — two literal keys
('locale.settings'and'translation.path') must both appear in the
expected positions, so unrelated config reads and standalone
$config->get('translation.path')calls are left untouched.
Caveat: the BC wrapper gates on\Drupal::VERSION, not on where
the value is stored. Before running this rule, confirm that any
customised translation path has been moved to
$settings['locale_translation_path']insettings.php; otherwise
the new branch silently returns the default
'public://translations'even when the config still holds the
customised value. PHPStan / upgrade_status cannot detect this
deprecation — the deprecated symbol is the config key, not a PHP API
with@deprecatedortrigger_error(), so this rule must be applied
proactively as part of an 11.4 → 13 migration plan.
#3571593 /
CR. -
ViewsConfigUpdaterClassResolverToServiceRector— rewrites
\Drupal::classResolver(\Drupal\views\ViewsConfigUpdater::class)to
\Drupal::service(\Drupal\views\ViewsConfigUpdater::class). In
drupal:11.3.0ViewsConfigUpdaterwas registered as a service;
classResolver()returns a fresh instance on each call, so state set via
setDeprecationsEnabled(FALSE)was lost across hook invocations. The new
call only resolves on Drupal ≥ 11.3.0 (the service isn't registered on
older versions), so the replacement is BC-wrapped with
DeprecationHelper::backwardsCompatibleCall(). Three layered guards
ensure only the targeted call shape is touched: receiver must be
\Drupal, method must beclassResolver, and the single argument must be
\Drupal\views\ViewsConfigUpdater::class.
#3529274 /
CR. -
ReplaceExpectDeprecationRector— migrates removed test framework methods
to their PHPUnit 11+ replacements. Renames are BC-wrapped with
DeprecationHelper::backwardsCompatibleCall()so tests keep passing on both
pre-11.4 (old methods) and 11.4+ (new methods). Covers:
$this->expectDeprecation($msg)and$this->expectDeprecationMessage($msg)→
$this->expectUserDeprecationMessage($msg);
$this->expectDeprecationMessageMatches($p)→
$this->expectUserDeprecationMessageMatches($p); bare
$this->expectDeprecation()(no-arg PHPUnit form) → removed.
ExpectDeprecationTraitis deprecated in drupal:11.4.0 and removed in
drupal:12.0.0.
#3550268 /
CR. -
ReplaceCommentPreviewConstantsRector— rewrites the legacy
DRUPAL_DISABLED/DRUPAL_OPTIONAL/DRUPAL_REQUIREDconstant arguments
toCommentTestBase::setCommentPreview()to the corresponding
Drupal\comment\CommentPreviewModeenum case. Only these named constants are
matched (ConstFetchnodes) — a bare integer literal such as
setCommentPreview(0)is left untouched. Passing the constants was
deprecated in drupal:11.3.0 and is removed in drupal:13.0.0. BC-wrapped
viaDeprecationHelper::backwardsCompatibleCall()so the rewrite still
runs on pre-11.3 Drupal where the enum doesn't yet exist.
#3538660 /
CR. -
RemovePhpUnitCompatibilityTraitRector— removes
use Drupal\Tests\PhpUnitCompatibilityTrait;from test class
declarations. The trait was a forward-compatibility shim for PHPUnit
API differences across versions; it is deleted from Drupal core in
Drupal 12 via #3582118, at which
point any test class still composing the trait fatal-errors at
autoload time because the trait class no longer exists.Gated to Drupal 12 only — and deliberately off by default. The
trait still exists on Drupal 10 (and may still hold shim methods that
tests depend on) and is an empty no-op on Drupal 11. Because the
trait composition is a structuralClass_change, not an Expr → Expr
rewrite, it cannot be BC-wrapped withDeprecationHelper. Running
the rule prematurely on a D10-only codebase risks silently stripping
a composition that the tests still rely on. It is registered in the
defaultdrupal-11.4-deprecations.phpset but gated with
DrupalIntroducedVersionConfiguration('12.0.0'), so it never fires unless
the consumer explicitly sets the target Drupal version to12.0.0or higher
viaDrupalRectorSettings::setDrupalVersion('12.0.0'). The orphan
top-of-fileuse Drupal\Tests\PhpUnitCompatibilityTrait;import is
left in place — PHP never resolves an unused alias, so it remains
harmless on D12; cleanup is optional and out of scope.
#3582118. -
New opt-in "breaking" sets, one per Drupal 11 minor:
Drupal11SetList::DRUPAL_111_BREAKING,DRUPAL_112_BREAKING,
DRUPAL_113_BREAKING,DRUPAL_114_BREAKING. Each is loaded from
config/drupal-11/drupal-11.X-breaking.php. Rules in these sets rewrite
code into a form that does NOT run on every drupal-rector-supported minor —
typically because the replacement symbol was introduced together with the
deprecation and does not exist on older minors, and the rewrite (class
rename, trait composition, etc.) is structural and cannot be BC-wrapped.
None of the breaking sets is included inDRUPAL_11XorDRUPAL_11;
consumers must load each one explicitly after committing to drop the older
minor(s) named in that file's docblock. -
Reclassified existing
RenameClassRectorentries as breaking, moved
out of the defaultdrupal-11.X-deprecations.phpfiles and into the new
per-minor breaking sets. Targets verified missing on Drupal 10.6.x:DRUPAL_111_BREAKING:path_alias\AliasWhitelist[Interface]→
path_alias\AliasPrefixList[Interface]
(#3151086 /
CR).DRUPAL_112_BREAKING:Core\Entity\Query\Sql\pgsql\{QueryFactory,Condition}
→pgsql\EntityQuery\*
(#3488572 /
CR);
migrate_drupal\Plugin\migrate\source\{ContentEntity,ContentEntityDeriver}
→migrate\Plugin\migrate\source\*
(#3498915 /
CR).DRUPAL_113_BREAKING:workspaces\WorkspaceAssociation[Interface]→
workspaces\WorkspaceTracker[Interface]
(#3551446 /
CR); the four
block_content\Access\*aliases (AccessGroupAnd,
DependentAccessInterface,RefinableDependentAccessInterface,
RefinableDependentAccessTrait) → their canonicalCore\Access\*homes.
The canonical classes were introduced in drupal:11.3.0 (not earlier) and
do not exist on any Drupal 10 branch, andRenameClassRectorrewrites
structuraluse/extends/implements/::classnodes that cannot be
BC-wrapped, so the rewrite fatals on Drupal 10
(#3571874 /
CR).DRUPAL_114_BREAKING:menu_link_content\Plugin\migrate\process\{LinkOptions,LinkUri}
→migrate\Plugin\migrate\process\*
(#3560075 /
CR).
-
Dropped: the planned
Drupal\jsonapi\EventSubscriber\ResourceResponseValidator
→Drupal\jsonapi_response_validator\…rename (#3472008) is not shipped
in any set. The replacement lives incore/modules/jsonapi/tests/modules/,
i.e. a core test module that production code cannot rely on being loaded;
rewriting the production FQCN to that test-module FQCN would fatal on D10
AND on any production D11 site that does not enable that test module. -
Class-rename entry for
LibraryDiscovery:
Drupal\Core\Asset\LibraryDiscovery→Drupal\Core\Asset\LibraryDiscoveryInterface.
The concrete class was deprecated in drupal:11.1.0 and removed in drupal:12.0.0;
thelibrary.discoveryservice is now backed byLibraryDiscoveryCollector,
so consumer code should type-hint the interface. Registered via Rector's
built-inRenameClassRectorindrupal-11.1-deprecations.php.
LibraryDiscoveryInterfacehas existed since Drupal 10.0.x, so the rewrite is
safe across all supported Drupal 10 and 11 minors.
#3462871 (deprecation) /
#3571057 (removal) /
CR. -
Class-rename entry for
EntityPermissionsRouteProviderWithCheck:
Drupal\user\Entity\EntityPermissionsRouteProviderWithCheck→
Drupal\user\Entity\EntityPermissionsRouteProvider. TheWithCheckvariant was
deprecated in drupal:11.1.0 and removed in drupal:12.0.0; the base provider has
existed since Drupal 10.0.x, so the rewrite is safe across all supported Drupal
10 and 11 minors. Registered via Rector's built-inRenameClassRectorin
drupal-11.1-deprecations.php. Access-check semantics: theWithCheck
variant layered a_custom_accessrequirement (EntityPermissionsForm::access,
also removed) on top of the base route to deny access when an entity type had
no entity-specific permissions; the base provider already enforces
_permission: administer permissions, so the security boundary is preserved
and only the "no permissions defined → deny" convenience check is dropped.
Subclass overrides that re-added the custom check are NOT rewritten —
RenameClassRectoronly updates the parent class reference, so owners of such
subclasses must port any remaining access logic into the route definition
(the new model adds permission requirements directly on the route).
Limitation — doctrine annotation strings are not rewritten. Real-world
contrib usage is almost exclusively the entity-annotation form
("permissions" = "Drupal\user\Entity\EntityPermissionsRouteProviderWithCheck"
inside a@ContentEntityType/@ConfigEntityTypedocblock).
RenameClassRectoronly touches PHPNamenodes (use,extends,
implements,::class, typehints,instanceof) — it does not scan
string literals inside doctrine annotations. Audited against Drupal contrib
(api.tresbien.tech, 2026-05-27): three modules reference the class — all via
the annotation form — and zero contrib modules use it in PHP code. Owners of
those modules must hand-edit the annotation string; this rector is a safety
net foruse/extendspatterns and for entity types that switch to
PHP-attribute syntax.
#3573870 /
CR.
Changed
-
Migration note for existing consumers of
Drupal11SetList::DRUPAL_111/
DRUPAL_112/DRUPAL_113: the class-rename entries listed above have
been moved out of the per-minorDRUPAL_11Xaggregates into new opt-in
*_BREAKINGsets. If you currently rely on rector rewriting any of these
symbols, add the matching*_BREAKINGset to your rector config alongside
your existingDRUPAL_11Xinclude — otherwise the rewrites silently stop:DRUPAL_111:AliasWhitelist[Interface]→AliasPrefixList[Interface]
now requiresDRUPAL_111_BREAKING.DRUPAL_112:Core\Entity\Query\Sql\pgsql\{QueryFactory,Condition}→
pgsql\EntityQuery\*and themigrate_drupal→migratesource-plugin
moves now requireDRUPAL_112_BREAKING.DRUPAL_113:WorkspaceAssociation[Interface]→
WorkspaceTracker[Interface]now requiresDRUPAL_113_BREAKING.
The breaking sets are not transitively included by
DRUPAL_11Xor
DRUPAL_11; consumers must load each one explicitly after committing to
drop the older minor(s) named in that file's docblock. -
Guide: Running against a Drupal 10 project — covers the
direct install and a standalone-runner recipe for sites whose PHPStan 1 tooling conflicts with
Rector 2's PHPStan 2 requirement. -
HookConvertRectornow produces lint-clean hook classes:- Methods whose body never references
$thisare declaredstatic
(satisfies thecanvas.requireStaticMethods/ "method does not use$this
and should be declared static" check). The body originates from a
procedural function, so this is safe by construction. - Global
t()calls are rewritten to$this->t()and the generated class
gainsuse Drupal\Core\StringTranslation\StringTranslationTrait;(clears
theDrupalPractice.Objects.GlobalFunction.GlobalFunctionwarning). Because
$this->t()introduces$this, those methods correctly remain
non-static — the two rules compose rather than conflict.
- Methods whose body never references
Fixed
-
ReplaceEntityOriginalPropertyRectornow handlesisset()andunset()
correctly instead of producing a parse-time fatal. Those constructs accept
only a variable, so blindly rewriting$entity->originalto the
$entity->getOriginal()method call (e.g.isset($entity->getOriginal()))
was invalid PHP. MirroringEntityBase's magic methods:isset($entity->original)→$entity->getOriginal() !== NULL
(__isset()returnsgetOriginal()), BC-wrapped inDeprecationHelper.unset($entity->original)→$entity->setOriginal(NULL)(__unset()calls
setOriginal(NULL)), BC-wrapped with$entity->original = NULLas the
pre-11.2 path. In a multi-operandunset(), only the->originaloperand
is rewritten; the rest stay in a residualunset().
Only the direct/outermost operand is fatal as a method call, so nested
fetches are rewritten normally:isset($entity->original->field)→
isset($entity->getOriginal()->field)and likewise forunset()(both parse
fine — only a bare method call as the outermost operand is fatal). A fetch
used as an array key, e.g.isset($map[$entity->original]), andempty()
(which accepts arbitrary expressions) are also rewritten. The only form left
untouched is the direct operand of a multi-operandisset()—
isset($entity->original, $other)— where rewriting->originalwould
produce the fatalisset($entity->getOriginal(), $other). -
Loading the Drupal 9 and Drupal 11 sets together no longer crashes at
container-build time. The Drupal 9FunctionToFirstArgMethodRector(and the
Drupal 8DrupalServiceRenameRector) subclass the generic rule, so Rector
delivered the generic rule's configuration to the subclass instance as well
(afterResolvingcallbacks match byinstanceof); the subclass' strict type
guard then threw. The subclasses now ignore configuration that is not their own.
This unblocks running the full, bundled rule set — including the D10-era
deprecations that live in the Drupal 11 set — against Drupal 10 sites.