Further Drupal 8.6.4 changes. Some core files were not committed before a commit...
[yaffs-website] / web / core / modules / migrate / src / Plugin / Discovery / AnnotatedClassDiscoveryAutomatedProviders.php
1 <?php
2
3 namespace Drupal\migrate\Plugin\Discovery;
4
5 use Doctrine\Common\Annotations\AnnotationRegistry;
6 use Doctrine\Common\Reflection\StaticReflectionParser as BaseStaticReflectionParser;
7 use Drupal\Component\Annotation\AnnotationInterface;
8 use Drupal\Component\Annotation\Reflection\MockFileFinder;
9 use Drupal\Component\ClassFinder\ClassFinder;
10 use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
11 use Drupal\migrate\Annotation\MultipleProviderAnnotationInterface;
12
13 /**
14  * Determines providers based on a class's and its parent's namespaces.
15  *
16  * @internal
17  *   This is a temporary solution to the fact that migration source plugins have
18  *   more than one provider. This functionality will be moved to core in
19  *   https://www.drupal.org/node/2786355.
20  */
21 class AnnotatedClassDiscoveryAutomatedProviders extends AnnotatedClassDiscovery {
22
23   /**
24    * A utility object that can use active autoloaders to find files for classes.
25    *
26    * @var \Doctrine\Common\Reflection\ClassFinderInterface
27    */
28   protected $finder;
29
30   /**
31    * Constructs an AnnotatedClassDiscoveryAutomatedProviders 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    *   The name of the annotation that contains the plugin definition.
42    *   Defaults to 'Drupal\Component\Annotation\Plugin'.
43    * @param string[] $annotation_namespaces
44    *   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     parent::__construct($subdir, $root_namespaces, $plugin_definition_annotation_name, $annotation_namespaces);
48     $this->finder = new ClassFinder();
49   }
50
51   /**
52    * {@inheritdoc}
53    */
54   protected function prepareAnnotationDefinition(AnnotationInterface $annotation, $class, BaseStaticReflectionParser $parser = NULL) {
55     if (!($annotation instanceof MultipleProviderAnnotationInterface)) {
56       throw new \LogicException('AnnotatedClassDiscoveryAutomatedProviders annotations must implement \Drupal\migrate\Annotation\MultipleProviderAnnotationInterface');
57     }
58     $annotation->setClass($class);
59     $providers = $annotation->getProviders();
60     // Loop through all the parent classes and add their providers (which we
61     // infer by parsing their namespaces) to the $providers array.
62     do {
63       $providers[] = $this->getProviderFromNamespace($parser->getNamespaceName());
64     } while ($parser = StaticReflectionParser::getParentParser($parser, $this->finder));
65     $providers = array_unique(array_filter($providers, function ($provider) {
66       return $provider && $provider !== 'component';
67     }));
68     $annotation->setProviders($providers);
69   }
70
71   /**
72    * {@inheritdoc}
73    */
74   public function getDefinitions() {
75     $definitions = [];
76
77     $reader = $this->getAnnotationReader();
78
79     // Clear the annotation loaders of any previous annotation classes.
80     AnnotationRegistry::reset();
81     // Register the namespaces of classes that can be used for annotations.
82     AnnotationRegistry::registerLoader('class_exists');
83
84     // Search for classes within all PSR-0 namespace locations.
85     foreach ($this->getPluginNamespaces() as $namespace => $dirs) {
86       foreach ($dirs as $dir) {
87         if (file_exists($dir)) {
88           $iterator = new \RecursiveIteratorIterator(
89             new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS)
90           );
91           foreach ($iterator as $fileinfo) {
92             if ($fileinfo->getExtension() == 'php') {
93               if ($cached = $this->fileCache->get($fileinfo->getPathName())) {
94                 if (isset($cached['id'])) {
95                   // Explicitly unserialize this to create a new object instance.
96                   $definitions[$cached['id']] = unserialize($cached['content']);
97                 }
98                 continue;
99               }
100
101               $sub_path = $iterator->getSubIterator()->getSubPath();
102               $sub_path = $sub_path ? str_replace(DIRECTORY_SEPARATOR, '\\', $sub_path) . '\\' : '';
103               $class = $namespace . '\\' . $sub_path . $fileinfo->getBasename('.php');
104
105               // The filename is already known, so there is no need to find the
106               // file. However, StaticReflectionParser needs a finder, so use a
107               // mock version.
108               $finder = MockFileFinder::create($fileinfo->getPathName());
109               $parser = new BaseStaticReflectionParser($class, $finder, FALSE);
110
111               /** @var $annotation \Drupal\Component\Annotation\AnnotationInterface */
112               if ($annotation = $reader->getClassAnnotation($parser->getReflectionClass(), $this->pluginDefinitionAnnotationName)) {
113                 $this->prepareAnnotationDefinition($annotation, $class, $parser);
114
115                 $id = $annotation->getId();
116                 $content = $annotation->get();
117                 $definitions[$id] = $content;
118                 // Explicitly serialize this to create a new object instance.
119                 $this->fileCache->set($fileinfo->getPathName(), ['id' => $id, 'content' => serialize($content)]);
120               }
121               else {
122                 // Store a NULL object, so the file is not reparsed again.
123                 $this->fileCache->set($fileinfo->getPathName(), [NULL]);
124               }
125             }
126           }
127         }
128       }
129     }
130
131     // Don't let annotation loaders pile up.
132     AnnotationRegistry::reset();
133
134     return $definitions;
135   }
136
137 }