AnnotatedClassDiscovery.php 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. <?php
  2. namespace Drupal\Core\Plugin\Discovery;
  3. use Drupal\Component\Annotation\AnnotationInterface;
  4. use Drupal\Component\Annotation\Plugin\Discovery\AnnotatedClassDiscovery as ComponentAnnotatedClassDiscovery;
  5. /**
  6. * Defines a discovery mechanism to find annotated plugins in PSR-4 namespaces.
  7. */
  8. class AnnotatedClassDiscovery extends ComponentAnnotatedClassDiscovery {
  9. /**
  10. * A suffix to append to each PSR-4 directory associated with a base
  11. * namespace, to form the directories where plugins are found.
  12. *
  13. * @var string
  14. */
  15. protected $directorySuffix = '';
  16. /**
  17. * A suffix to append to each base namespace, to obtain the namespaces where
  18. * plugins are found.
  19. *
  20. * @var string
  21. */
  22. protected $namespaceSuffix = '';
  23. /**
  24. * A list of base namespaces with their PSR-4 directories.
  25. *
  26. * @var \Traversable
  27. */
  28. protected $rootNamespacesIterator;
  29. /**
  30. * Constructs an AnnotatedClassDiscovery object.
  31. *
  32. * @param string $subdir
  33. * Either the plugin's subdirectory, for example 'Plugin/views/filter', or
  34. * empty string if plugins are located at the top level of the namespace.
  35. * @param \Traversable $root_namespaces
  36. * An object that implements \Traversable which contains the root paths
  37. * keyed by the corresponding namespace to look for plugin implementations.
  38. * If $subdir is not an empty string, it will be appended to each namespace.
  39. * @param string $plugin_definition_annotation_name
  40. * (optional) The name of the annotation that contains the plugin definition.
  41. * Defaults to 'Drupal\Component\Annotation\Plugin'.
  42. * @param string[] $annotation_namespaces
  43. * (optional) Additional namespaces to scan for annotation definitions.
  44. */
  45. public function __construct($subdir, \Traversable $root_namespaces, $plugin_definition_annotation_name = 'Drupal\Component\Annotation\Plugin', array $annotation_namespaces = []) {
  46. if ($subdir) {
  47. // Prepend a directory separator to $subdir,
  48. // if it does not already have one.
  49. if ('/' !== $subdir[0]) {
  50. $subdir = '/' . $subdir;
  51. }
  52. $this->directorySuffix = $subdir;
  53. $this->namespaceSuffix = str_replace('/', '\\', $subdir);
  54. }
  55. $this->rootNamespacesIterator = $root_namespaces;
  56. $plugin_namespaces = [];
  57. parent::__construct($plugin_namespaces, $plugin_definition_annotation_name, $annotation_namespaces);
  58. }
  59. /**
  60. * {@inheritdoc}
  61. */
  62. protected function getAnnotationReader() {
  63. if (!isset($this->annotationReader)) {
  64. $reader = parent::getAnnotationReader();
  65. // Add the Core annotation classes like @Translation.
  66. $reader->addNamespace('Drupal\Core\Annotation');
  67. $this->annotationReader = $reader;
  68. }
  69. return $this->annotationReader;
  70. }
  71. /**
  72. * {@inheritdoc}
  73. */
  74. protected function prepareAnnotationDefinition(AnnotationInterface $annotation, $class) {
  75. parent::prepareAnnotationDefinition($annotation, $class);
  76. if (!$annotation->getProvider()) {
  77. $annotation->setProvider($this->getProviderFromNamespace($class));
  78. }
  79. }
  80. /**
  81. * Extracts the provider name from a Drupal namespace.
  82. *
  83. * @param string $namespace
  84. * The namespace to extract the provider from.
  85. *
  86. * @return string|null
  87. * The matching provider name, or NULL otherwise.
  88. */
  89. protected function getProviderFromNamespace($namespace) {
  90. preg_match('|^Drupal\\\\(?<provider>[\w]+)\\\\|', $namespace, $matches);
  91. if (isset($matches['provider'])) {
  92. return mb_strtolower($matches['provider']);
  93. }
  94. return NULL;
  95. }
  96. /**
  97. * {@inheritdoc}
  98. */
  99. protected function getPluginNamespaces() {
  100. $plugin_namespaces = [];
  101. if ($this->namespaceSuffix) {
  102. foreach ($this->rootNamespacesIterator as $namespace => $dirs) {
  103. // Append the namespace suffix to the base namespace, to obtain the
  104. // plugin namespace; for example, 'Drupal\Views' may become
  105. // 'Drupal\Views\Plugin\Block'.
  106. $namespace .= $this->namespaceSuffix;
  107. foreach ((array) $dirs as $dir) {
  108. // Append the directory suffix to the PSR-4 base directory, to obtain
  109. // the directory where plugins are found. For example,
  110. // DRUPAL_ROOT . '/core/modules/views/src' may become
  111. // DRUPAL_ROOT . '/core/modules/views/src/Plugin/Block'.
  112. $plugin_namespaces[$namespace][] = $dir . $this->directorySuffix;
  113. }
  114. }
  115. }
  116. else {
  117. // Both the namespace suffix and the directory suffix are empty,
  118. // so the plugin namespaces and directories are the same as the base
  119. // directories.
  120. foreach ($this->rootNamespacesIterator as $namespace => $dirs) {
  121. $plugin_namespaces[$namespace] = (array) $dirs;
  122. }
  123. }
  124. return $plugin_namespaces;
  125. }
  126. }