function AutowireTest::testCoreServiceAliases

Same name and namespace in other branches
  1. 10 core/tests/Drupal/KernelTests/Core/DependencyInjection/AutowireTest.php \Drupal\KernelTests\Core\DependencyInjection\AutowireTest::testCoreServiceAliases()

Tests that core services have aliases correctly defined where possible.

File

core/tests/Drupal/KernelTests/Core/DependencyInjection/AutowireTest.php, line 53

Class

AutowireTest
Tests auto-wiring services.

Namespace

Drupal\KernelTests\Core\DependencyInjection

Code

public function testCoreServiceAliases() : void {
  $services = [];
  $aliases = [];
  $filenames = array_map(fn($module) => "core/modules/{$module[0]}/{$module[0]}.services.yml", $this->coreModuleListDataProvider());
  $filenames[] = 'core/core.services.yml';
  foreach (array_filter($filenames, 'file_exists') as $filename) {
    foreach (Yaml::decode(file_get_contents($filename))['services'] ?? [] as $id => $service) {
      if (is_string($service)) {
        $aliases[$id] = substr($service, 1);
      }
      elseif (isset($service['class']) && class_exists($service['class'])) {
        // Ignore services named by their own class.
        if ($id === $service['class']) {
          continue;
        }
        // Ignore certain tagged services.
        if (isset($service['tags'])) {
          foreach ($service['tags'] as $tag) {
            if (in_array($tag['name'], [
              'access_check',
              'cache.context',
              'context_provider',
              'event_subscriber',
            ])) {
              continue 2;
            }
          }
        }
        $services[$id] = $service['class'];
      }
    }
  }
  $interfaces = [];
  foreach (get_declared_classes() as $class) {
    // Ignore proxy classes for autowiring purposes.
    if (str_contains($class, '\\ProxyClass\\')) {
      continue;
    }
    foreach (class_implements($class) as $interface) {
      $interfaces[$interface][] = $class;
    }
  }
  $expected = [];
  foreach ($services as $id => $class) {
    // Skip services that share a class.
    if (count(array_keys($services, $class)) > 1) {
      continue;
    }
    // Skip IDs that are interfaces already.
    if (interface_exists($id)) {
      continue;
    }
    // Expect standalone classes to be aliased.
    $implements = class_implements($class);
    if (!$implements) {
      $expected[$class] = $id;
    }
    elseif (count($implements) === 1 && TrustedCallbackInterface::class === reset($implements)) {
      // Classes implementing only TrustedCallbackInterface should be aliased.
      $expected[$class] = $id;
    }
    // Expect classes that are the only implementation of their interface to
    // be aliased.
    foreach ($implements as $interface) {
      if (count($interfaces[$interface]) === 1) {
        $expected[$interface] = $id;
      }
    }
  }
  $missing = array_diff($expected, $aliases);
  $formatted = Yaml::encode(array_map(fn($alias) => sprintf('@%s', $alias), $missing));
  $this->assertSame($expected, array_intersect($expected, $aliases), sprintf('The following core services do not have map the class name to an alias. Add the following to core.services.yml in the appropriate place: %s%s%s', \PHP_EOL, \PHP_EOL, $formatted));
}

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.