Skip to content

The \Drupal Global Helper

When to Use

When you understand when the \Drupal static helper is appropriate versus when dependency injection is required.

Decision

If you need... Use... Why
Service access in class (controller, form, plugin) Constructor injection Testable, explicit dependencies, follows best practices
Service access in hook (procedural .module code) \Drupal::service() No other option in procedural context
Quick prototyping or debugging \Drupal::service() Fast, but refactor to DI before committing
Legacy code modernization \Drupal::service() to DI refactor Temporary bridge pattern
Service access in static utility class \Drupal::service() If class is truly stateless helpers, acceptable compromise

Pattern

Acceptable \Drupal Usage (Procedural Code):

// my_module.module
function my_module_cron() {
  $database = \Drupal::database();
  $database->delete('my_table')
    ->condition('created', strtotime('-30 days'), '<')
    ->execute();
}

Refactoring to DI:

// Before: Controller using \Drupal
class MyController extends ControllerBase {
  public function content() {
    $database = \Drupal::database();
    $results = $database->query('SELECT * FROM {my_table}')->fetchAll();
    return ['#markup' => count($results)];
  }
}

// After: Controller with DI
class MyController extends ControllerBase {
  use AutowireTrait;

  public function __construct(
    protected Connection $database,
  ) {}

  public function content() {
    $results = $this->database->query('SELECT * FROM {my_table}')->fetchAll();
    return ['#markup' => count($results)];
  }
}

Reference: /core/lib/Drupal.php (lines 1-73 explain the philosophy)

Common Mistakes

  • Using \Drupal in __construct() — Constructor runs before container is available; inject dependencies instead
  • Using \Drupal:: in tests — Mocking \Drupal is difficult; inject services for testability
  • Cargo-culting \Drupal usage — Just because you see it in legacy code doesn't mean it's the right pattern today
  • Not understanding the performance cost — \Drupal::service() is fast enough, but explicit injection is clearer and eliminates lookups
  • Defending \Drupal as "simpler" — Short-term simplicity creates long-term technical debt; DI is worth the small upfront cost

Anti-Pattern Examples

Don't do this:

// Anti-pattern: \Drupal in a service
class MyService {
  public function doSomething() {
    $config = \Drupal::config('my_module.settings');  // BAD
    $database = \Drupal::database();  // BAD
  }
}

// Correct: Inject dependencies
class MyService {
  public function __construct(
    protected ConfigFactoryInterface $configFactory,
    protected Connection $database,
  ) {}

  public function doSomething() {
    $config = $this->configFactory->get('my_module.settings');
    // Use $this->database
  }
}

See Also