AnnotatedClassDiscovery.php 4.7 KB

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