PageRenderTime 45ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/core/lib/Drupal/Component/Annotation/Plugin/Discovery/AnnotatedClassDiscovery.php

http://github.com/drupal/drupal
PHP | 191 lines | 106 code | 21 blank | 64 comment | 5 complexity | 95f386597ae5a4738e3127ff1fb83640 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  1. <?php
  2. namespace Drupal\Component\Annotation\Plugin\Discovery;
  3. use Drupal\Component\Annotation\AnnotationInterface;
  4. use Drupal\Component\FileCache\FileCacheFactory;
  5. use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
  6. use Drupal\Component\Annotation\Doctrine\SimpleAnnotationReader;
  7. use Drupal\Component\Annotation\Reflection\MockFileFinder;
  8. use Doctrine\Common\Annotations\AnnotationRegistry;
  9. use Doctrine\Common\Reflection\StaticReflectionParser;
  10. use Drupal\Component\Plugin\Discovery\DiscoveryTrait;
  11. use Drupal\Component\Utility\Crypt;
  12. /**
  13. * Defines a discovery mechanism to find annotated plugins in PSR-4 namespaces.
  14. */
  15. class AnnotatedClassDiscovery implements DiscoveryInterface {
  16. use DiscoveryTrait;
  17. /**
  18. * The namespaces within which to find plugin classes.
  19. *
  20. * @var string[]
  21. */
  22. protected $pluginNamespaces;
  23. /**
  24. * The name of the annotation that contains the plugin definition.
  25. *
  26. * The class corresponding to this name must implement
  27. * \Drupal\Component\Annotation\AnnotationInterface.
  28. *
  29. * @var string
  30. */
  31. protected $pluginDefinitionAnnotationName;
  32. /**
  33. * The doctrine annotation reader.
  34. *
  35. * @var \Doctrine\Common\Annotations\Reader
  36. */
  37. protected $annotationReader;
  38. /**
  39. * Additional namespaces to be scanned for annotation classes.
  40. *
  41. * @var string[]
  42. */
  43. protected $annotationNamespaces = [];
  44. /**
  45. * The file cache object.
  46. *
  47. * @var \Drupal\Component\FileCache\FileCacheInterface
  48. */
  49. protected $fileCache;
  50. /**
  51. * Constructs a new instance.
  52. *
  53. * @param string[] $plugin_namespaces
  54. * (optional) An array of namespace that may contain plugin implementations.
  55. * Defaults to an empty array.
  56. * @param string $plugin_definition_annotation_name
  57. * (optional) The name of the annotation that contains the plugin definition.
  58. * Defaults to 'Drupal\Component\Annotation\Plugin'.
  59. * @param string[] $annotation_namespaces
  60. * (optional) Additional namespaces to be scanned for annotation classes.
  61. */
  62. public function __construct($plugin_namespaces = [], $plugin_definition_annotation_name = 'Drupal\Component\Annotation\Plugin', array $annotation_namespaces = []) {
  63. $this->pluginNamespaces = $plugin_namespaces;
  64. $this->pluginDefinitionAnnotationName = $plugin_definition_annotation_name;
  65. $this->annotationNamespaces = $annotation_namespaces;
  66. $file_cache_suffix = str_replace('\\', '_', $plugin_definition_annotation_name);
  67. $file_cache_suffix .= ':' . Crypt::hashBase64(serialize($annotation_namespaces));
  68. $this->fileCache = FileCacheFactory::get('annotation_discovery:' . $file_cache_suffix);
  69. }
  70. /**
  71. * Gets the used doctrine annotation reader.
  72. *
  73. * @return \Doctrine\Common\Annotations\Reader
  74. * The annotation reader.
  75. */
  76. protected function getAnnotationReader() {
  77. if (!isset($this->annotationReader)) {
  78. $this->annotationReader = new SimpleAnnotationReader();
  79. // Add the namespaces from the main plugin annotation, like @EntityType.
  80. $namespace = substr($this->pluginDefinitionAnnotationName, 0, strrpos($this->pluginDefinitionAnnotationName, '\\'));
  81. $this->annotationReader->addNamespace($namespace);
  82. // Register additional namespaces to be scanned for annotations.
  83. foreach ($this->annotationNamespaces as $namespace) {
  84. $this->annotationReader->addNamespace($namespace);
  85. }
  86. }
  87. return $this->annotationReader;
  88. }
  89. /**
  90. * {@inheritdoc}
  91. */
  92. public function getDefinitions() {
  93. $definitions = [];
  94. $reader = $this->getAnnotationReader();
  95. // Clear the annotation loaders of any previous annotation classes.
  96. AnnotationRegistry::reset();
  97. // Register the namespaces of classes that can be used for annotations.
  98. AnnotationRegistry::registerLoader('class_exists');
  99. // Search for classes within all PSR-4 namespace locations.
  100. foreach ($this->getPluginNamespaces() as $namespace => $dirs) {
  101. foreach ($dirs as $dir) {
  102. if (file_exists($dir)) {
  103. $iterator = new \RecursiveIteratorIterator(
  104. new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS)
  105. );
  106. foreach ($iterator as $fileinfo) {
  107. if ($fileinfo->getExtension() == 'php') {
  108. if ($cached = $this->fileCache->get($fileinfo->getPathName())) {
  109. if (isset($cached['id'])) {
  110. // Explicitly unserialize this to create a new object instance.
  111. $definitions[$cached['id']] = unserialize($cached['content']);
  112. }
  113. continue;
  114. }
  115. $sub_path = $iterator->getSubIterator()->getSubPath();
  116. $sub_path = $sub_path ? str_replace(DIRECTORY_SEPARATOR, '\\', $sub_path) . '\\' : '';
  117. $class = $namespace . '\\' . $sub_path . $fileinfo->getBasename('.php');
  118. // The filename is already known, so there is no need to find the
  119. // file. However, StaticReflectionParser needs a finder, so use a
  120. // mock version.
  121. $finder = MockFileFinder::create($fileinfo->getPathName());
  122. $parser = new StaticReflectionParser($class, $finder, TRUE);
  123. /** @var $annotation \Drupal\Component\Annotation\AnnotationInterface */
  124. if ($annotation = $reader->getClassAnnotation($parser->getReflectionClass(), $this->pluginDefinitionAnnotationName)) {
  125. $this->prepareAnnotationDefinition($annotation, $class);
  126. $id = $annotation->getId();
  127. $content = $annotation->get();
  128. $definitions[$id] = $content;
  129. // Explicitly serialize this to create a new object instance.
  130. $this->fileCache->set($fileinfo->getPathName(), ['id' => $id, 'content' => serialize($content)]);
  131. }
  132. else {
  133. // Store a NULL object, so the file is not reparsed again.
  134. $this->fileCache->set($fileinfo->getPathName(), [NULL]);
  135. }
  136. }
  137. }
  138. }
  139. }
  140. }
  141. // Don't let annotation loaders pile up.
  142. AnnotationRegistry::reset();
  143. return $definitions;
  144. }
  145. /**
  146. * Prepares the annotation definition.
  147. *
  148. * @param \Drupal\Component\Annotation\AnnotationInterface $annotation
  149. * The annotation derived from the plugin.
  150. * @param string $class
  151. * The class used for the plugin.
  152. */
  153. protected function prepareAnnotationDefinition(AnnotationInterface $annotation, $class) {
  154. $annotation->setClass($class);
  155. }
  156. /**
  157. * Gets an array of PSR-4 namespaces to search for plugin classes.
  158. *
  159. * @return string[]
  160. */
  161. protected function getPluginNamespaces() {
  162. return $this->pluginNamespaces;
  163. }
  164. }