#3581133: Automate MR review using PHPCS: disallow `\Drupal::service('something')`, require `\Drupal::service(Something::class)` whenever possible, with automatic fixing

Problem

It's easy to use magic strings like 'entity_type.manager' when calling \Drupal::service() or $this->container->get(). These magic strings:

  • defeat static analysis (PHPStan, IDE refactoring)
  • are typo-prone with zero compile-time feedback
  • obscure the actual type being retrieved

Solution

Add a new PHPCS sniff Canvas.Services.ClassServiceId that:

  1. Forbids string-based service IDs:

    // ❌ Forbidden:
    $this->container->get('entity_type.manager')
    \Drupal::service('entity_type.manager')
  2. Requires the equivalent ::class constant:

    // ✅ Required:
    $this->container->get(EntityTypeManagerInterface::class)
    \Drupal::service(EntityTypeManagerInterface::class)
  3. Auto-fixes violations via phpcbf:

    • Replaces the string with ShortName::class
    • Adds the use import in alphabetical order (respects AlphabeticallySortedUses)
    • Falls back to \Full\Qualified\Name::class on short-name collisions

How it works

The sniff parses *.services.yml files at runtime to build a mapping from string service IDs to their FQCN. It prefers interface aliases (e.g. EntityTypeManagerInterface) over concrete classes (e.g. EntityTypeManager).

Scanned files:

  • core/core.services.yml
  • core/modules/**/*.services.yml (all core modules and their test modules)
  • canvas.services.yml
  • modules/**/*.services.yml (all Canvas submodules and their test modules)

Detected patterns:

  • $this->container->get('service.id')
  • $container->get('service.id')
  • \Drupal::service('service.id')

Skipped (no false positives):

  • Services with no known FQCN mapping (e.g. kernel, uuid)
  • ->get() calls on non-container objects (e.g. $entity->get(...), $config->get(...))
  • Strings that already use ::class

Limitations

  • Services registered dynamically via ServiceProviderInterface::register() or altered via ServiceModifierInterface::alter() are NOT detected — they don't appear in any *.services.yml file.
  • Services from contrib/custom modules outside of Canvas are not scanned. andards/Canvas/ruleset.xml` | Register the new rule |

AI-Generated: Yes

Testing instructions

  • CI passes
  • Changes made to /src and /tests in MR look reasonable

Closes #3581133

Edited by Wim Leers

Merge request reports

Loading