comparison core/modules/migrate/src/Plugin/MigrationPluginManager.php @ 0:4c8ae668cc8c

Initial import (non-working)
author Chris Cannam
date Wed, 29 Nov 2017 16:09:58 +0000
parents
children 1fec387a4317
comparison
equal deleted inserted replaced
-1:000000000000 0:4c8ae668cc8c
1 <?php
2
3 namespace Drupal\migrate\Plugin;
4
5 use Drupal\Component\Graph\Graph;
6 use Drupal\Component\Plugin\PluginBase;
7 use Drupal\Core\Cache\CacheBackendInterface;
8 use Drupal\Core\Extension\ModuleHandlerInterface;
9 use Drupal\Core\Language\LanguageManagerInterface;
10 use Drupal\Core\Plugin\DefaultPluginManager;
11 use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
12 use Drupal\migrate\Plugin\Discovery\ProviderFilterDecorator;
13 use Drupal\Core\Plugin\Discovery\YamlDirectoryDiscovery;
14 use Drupal\Core\Plugin\Factory\ContainerFactory;
15 use Drupal\migrate\MigrateBuildDependencyInterface;
16
17 /**
18 * Plugin manager for migration plugins.
19 */
20 class MigrationPluginManager extends DefaultPluginManager implements MigrationPluginManagerInterface, MigrateBuildDependencyInterface {
21
22 /**
23 * Provides default values for migrations.
24 *
25 * @var array
26 */
27 protected $defaults = [
28 'class' => '\Drupal\migrate\Plugin\Migration',
29 ];
30
31 /**
32 * The interface the plugins should implement.
33 *
34 * @var string
35 */
36 protected $pluginInterface = 'Drupal\migrate\Plugin\MigrationInterface';
37
38 /**
39 * The module handler.
40 *
41 * @var \Drupal\Core\Extension\ModuleHandlerInterface
42 */
43 protected $moduleHandler;
44
45 /**
46 * Construct a migration plugin manager.
47 *
48 * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
49 * The module handler.
50 * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
51 * The cache backend for the definitions.
52 * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
53 * The language manager.
54 */
55 public function __construct(ModuleHandlerInterface $module_handler, CacheBackendInterface $cache_backend, LanguageManagerInterface $language_manager) {
56 $this->factory = new ContainerFactory($this, $this->pluginInterface);
57 $this->alterInfo('migration_plugins');
58 $this->setCacheBackend($cache_backend, 'migration_plugins', ['migration_plugins']);
59 $this->moduleHandler = $module_handler;
60 }
61
62 /**
63 * {@inheritdoc}
64 */
65 protected function getDiscovery() {
66 if (!isset($this->discovery)) {
67 $directories = array_map(function ($directory) {
68 return [$directory . '/migration_templates', $directory . '/migrations'];
69 }, $this->moduleHandler->getModuleDirectories());
70
71 $yaml_discovery = new YamlDirectoryDiscovery($directories, 'migrate');
72 // This gets rid of migrations which try to use a non-existent source
73 // plugin. The common case for this is if the source plugin has, or
74 // specifies, a non-existent provider.
75 $only_with_source_discovery = new NoSourcePluginDecorator($yaml_discovery);
76 // This gets rid of migrations with explicit providers set if one of the
77 // providers do not exist before we try to use a potentially non-existing
78 // deriver. This is a rare case.
79 $filtered_discovery = new ProviderFilterDecorator($only_with_source_discovery, [$this->moduleHandler, 'moduleExists']);
80 $this->discovery = new ContainerDerivativeDiscoveryDecorator($filtered_discovery);
81 }
82 return $this->discovery;
83 }
84
85 /**
86 * {@inheritdoc}
87 */
88 public function createInstance($plugin_id, array $configuration = []) {
89 $instances = $this->createInstances([$plugin_id], [$plugin_id => $configuration]);
90 return reset($instances);
91 }
92
93 /**
94 * {@inheritdoc}
95 */
96 public function createInstances($migration_id, array $configuration = []) {
97 if (empty($migration_id)) {
98 $migration_id = array_keys($this->getDefinitions());
99 }
100
101 $factory = $this->getFactory();
102 $migration_ids = (array) $migration_id;
103 $plugin_ids = $this->expandPluginIds($migration_ids);
104
105 $instances = [];
106 foreach ($plugin_ids as $plugin_id) {
107 $instances[$plugin_id] = $factory->createInstance($plugin_id, isset($configuration[$plugin_id]) ? $configuration[$plugin_id] : []);
108 }
109
110 foreach ($instances as $migration) {
111 $migration->set('migration_dependencies', array_map([$this, 'expandPluginIds'], $migration->getMigrationDependencies()));
112 }
113
114 // Sort the migrations based on their dependencies.
115 return $this->buildDependencyMigration($instances, []);
116 }
117
118 /**
119 * Create migrations given a tag.
120 *
121 * @param string $tag
122 * A migration tag we want to filter by.
123 *
124 * @return array|\Drupal\migrate\Plugin\MigrationInterface[]
125 * An array of migration objects with the given tag.
126 */
127 public function createInstancesByTag($tag) {
128 $migrations = array_filter($this->getDefinitions(), function ($migration) use ($tag) {
129 return !empty($migration['migration_tags']) && in_array($tag, $migration['migration_tags']);
130 });
131 return $this->createInstances(array_keys($migrations));
132 }
133
134 /**
135 * Expand derivative migration dependencies.
136 *
137 * We need to expand any derivative migrations. Derivative migrations are
138 * calculated by migration derivers such as D6NodeDeriver. This allows
139 * migrations to depend on the base id and then have a dependency on all
140 * derivative migrations. For example, d6_comment depends on d6_node but after
141 * we've expanded the dependencies it will depend on d6_node:page,
142 * d6_node:story and so on, for other derivative migrations.
143 *
144 * @return array
145 * An array of expanded plugin ids.
146 */
147 protected function expandPluginIds(array $migration_ids) {
148 $plugin_ids = [];
149 foreach ($migration_ids as $id) {
150 $plugin_ids += preg_grep('/^' . preg_quote($id, '/') . PluginBase::DERIVATIVE_SEPARATOR . '/', array_keys($this->getDefinitions()));
151 if ($this->hasDefinition($id)) {
152 $plugin_ids[] = $id;
153 }
154 }
155 return $plugin_ids;
156 }
157
158
159 /**
160 * {@inheritdoc}
161 */
162 public function buildDependencyMigration(array $migrations, array $dynamic_ids) {
163 // Migration dependencies can be optional or required. If an optional
164 // dependency does not run, the current migration is still OK to go. Both
165 // optional and required dependencies (if run at all) must run before the
166 // current migration.
167 $dependency_graph = [];
168 $required_dependency_graph = [];
169 $have_optional = FALSE;
170 foreach ($migrations as $migration) {
171 /** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
172 $id = $migration->id();
173 $requirements[$id] = [];
174 $dependency_graph[$id]['edges'] = [];
175 $migration_dependencies = $migration->getMigrationDependencies();
176
177 if (isset($migration_dependencies['required'])) {
178 foreach ($migration_dependencies['required'] as $dependency) {
179 if (!isset($dynamic_ids[$dependency])) {
180 $this->addDependency($required_dependency_graph, $id, $dependency, $dynamic_ids);
181 }
182 $this->addDependency($dependency_graph, $id, $dependency, $dynamic_ids);
183 }
184 }
185 if (!empty($migration_dependencies['optional'])) {
186 foreach ($migration_dependencies['optional'] as $dependency) {
187 $this->addDependency($dependency_graph, $id, $dependency, $dynamic_ids);
188 }
189 $have_optional = TRUE;
190 }
191 }
192 $dependency_graph = (new Graph($dependency_graph))->searchAndSort();
193 if ($have_optional) {
194 $required_dependency_graph = (new Graph($required_dependency_graph))->searchAndSort();
195 }
196 else {
197 $required_dependency_graph = $dependency_graph;
198 }
199 $weights = [];
200 foreach ($migrations as $migration_id => $migration) {
201 // Populate a weights array to use with array_multisort() later.
202 $weights[] = $dependency_graph[$migration_id]['weight'];
203 if (!empty($required_dependency_graph[$migration_id]['paths'])) {
204 $migration->set('requirements', $required_dependency_graph[$migration_id]['paths']);
205 }
206 }
207 array_multisort($weights, SORT_DESC, SORT_NUMERIC, $migrations);
208
209 return $migrations;
210 }
211
212 /**
213 * Add one or more dependencies to a graph.
214 *
215 * @param array $graph
216 * The graph so far, passed by reference.
217 * @param int $id
218 * The migration ID.
219 * @param string $dependency
220 * The dependency string.
221 * @param array $dynamic_ids
222 * The dynamic ID mapping.
223 */
224 protected function addDependency(array &$graph, $id, $dependency, $dynamic_ids) {
225 $dependencies = isset($dynamic_ids[$dependency]) ? $dynamic_ids[$dependency] : [$dependency];
226 if (!isset($graph[$id]['edges'])) {
227 $graph[$id]['edges'] = [];
228 }
229 $graph[$id]['edges'] += array_combine($dependencies, $dependencies);
230 }
231
232 /**
233 * {@inheritdoc}
234 */
235 public function createStubMigration(array $definition) {
236 $id = isset($definition['id']) ? $definition['id'] : uniqid();
237 return Migration::create(\Drupal::getContainer(), [], $id, $definition);
238 }
239
240 /**
241 * Finds plugin definitions.
242 *
243 * @return array
244 * List of definitions to store in cache.
245 *
246 * @todo This is a temporary solution to the fact that migration source
247 * plugins have more than one provider. This functionality will be moved to
248 * core in https://www.drupal.org/node/2786355.
249 */
250 protected function findDefinitions() {
251 $definitions = $this->getDiscovery()->getDefinitions();
252 foreach ($definitions as $plugin_id => &$definition) {
253 $this->processDefinition($definition, $plugin_id);
254 }
255 $this->alterDefinitions($definitions);
256 return ProviderFilterDecorator::filterDefinitions($definitions, function ($provider) {
257 return $this->providerExists($provider);
258 });
259 }
260
261 }