Replace deprecated and static API calls
Two kinds of call age a module. The first is the \Drupal:: static accessor used inside a class, where a constructor-injected service belongs instead. The second is a function or method that core has deprecated and will remove in a future major version. Both keep working today, which is exactly why they are easy to leave in place until the version that drops them breaks your site. This page finds and replaces both.
Class code that reaches services through injected properties rather than \Drupal::service(), and a module with no calls to APIs scheduled for removal in the Drupal version you target. The behavior does not change; what changes is that the code is testable, and it will survive the next major release.
Prerequisites
Section titled “Prerequisites”- Services and dependency injection for how a class receives services and which base classes provide a factory.
- A Drupal 11 site, and Composer and the core CLI available for the analysis tooling below.
Replace static service calls with injection
Section titled “Replace static service calls with injection”-
Find the static calls inside your classes:
grep -rn "\\\\Drupal::" web/modules/custom/my_module/src. Each hit in a service, controller, form, plugin, or hook class is a candidate. Entity classes and static methods (such asbaseFieldDefinitions()andcreate()factories) are expected exceptions: the container does not build entities, and a static method has no instance to inject into. The same call in a.module,.install,.theme, ortheme-settings.phpfile is not: those are procedural files where injection does not exist, and the static accessor is the correct form there. -
Inject the service through the constructor instead, and use the property. The pattern depends on how the class is built:
- A service (defined in
*.services.yml) lists the dependency as a constructor argument, or relies on autowiring by type. - A hook class under
src/Hook/is autowired automatically: just type-hint the constructor. - A controller, form, block, or other plugin receives services through a
create()factory method (fromContainerInjectionInterfaceorContainerFactoryPluginInterface), which hands them to the constructor.
The body change is the same in every case. Before, a static call buried in a method:
public function build(): array {$count = \Drupal::entityTypeManager()->getStorage('node')->getQuery()->accessCheck()->count()->execute();// ...}After, the same manager injected once and reused:
public function __construct(private readonly EntityTypeManagerInterface $entityTypeManager,) {}public function build(): array {$count = $this->entityTypeManager->getStorage('node')->getQuery()->accessCheck()->count()->execute();// ...}Services and dependency injection shows the full wiring for each class type, including the
create()factory. - A service (defined in
-
Rebuild so the container picks up changed constructors or service definitions:
ddev drush cache:rebuild.
Find and replace deprecated calls
Section titled “Find and replace deprecated calls”-
Run static analysis that knows Drupal’s deprecations. PHPStan with the
phpstan-drupaland deprecation-rules extensions (both pulled in bydrupal/core-dev) reports every call to an API core has marked@deprecated, with the version it is removed in and the replacement to use. Point it at your module:Terminal window ddev exec vendor/bin/phpstan analyse web/modules/custom/my_module -
Read each report against its change record. A deprecation notice names its replacement; the linked change record on drupal.org explains the move. For example, reading a theme setting used to mean the global
theme_get_setting()function, now replaced by a service: -
Apply the replacements and rerun the analysis until it reports no deprecations for the Drupal version you target. Commit the analysis command to your CI so a future core update cannot reintroduce one unnoticed.
Verify
Section titled “Verify”grep -rn "\\\\Drupal::" web/modules/custom/my_module/srcreturns hits only in entity classes and static methods.ddev exec vendor/bin/phpstan analyse web/modules/custom/my_modulereports no deprecation errors.- The module behaves exactly as before: the page, block, or form you changed still renders and saves.
Variations
Section titled “Variations”When a static call is correct
Section titled “When a static call is correct”Procedural code has no container to inject from, so \Drupal::service() stays the right form in .install update functions, .theme preprocess functions, theme-settings.php, Drush scripts, and one-off drush php:eval commands. Converting those to injection is not possible and not the goal. The rule is narrow: static accessors inside a container-built class are the thing to replace.
Running the check without all of core-dev
Section titled “Running the check without all of core-dev”If you do not want the whole of drupal/core-dev in a project, ddev composer require --dev mglaman/phpstan-drupal phpstan/phpstan-deprecation-rules phpstan/extension-installer installs the same deprecation checks on their own. For a report in the admin UI, install the Upgrade Status module, which runs the same analysis per project.
Automating the replacements
Section titled “Automating the replacements”drupal-rector rewrites many deprecated calls for you, using the same deprecation data. Run it, review the diff, then rerun PHPStan to catch what it left.
Related
Section titled “Related”- Services and dependency injection for the injection patterns each class type uses.
- Convert procedural hooks to classes, whose hook bodies often hold the static calls this guide replaces.
- Debug a Drupal site for turning on deprecation logging while you work.
Was this page helpful?
Drupal is a registered trademark of Dries Buytaert.