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