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@14
|
63 * Gets the plugin discovery.
|
Chris@14
|
64 *
|
Chris@14
|
65 * This method overrides DefaultPluginManager::getDiscovery() in order to
|
Chris@14
|
66 * search for migration configurations in the MODULENAME/migrations and
|
Chris@14
|
67 * MODULENAME/migration_templates directories. Throws a deprecation notice if
|
Chris@14
|
68 * the MODULENAME/migration_templates directory exists.
|
Chris@0
|
69 */
|
Chris@0
|
70 protected function getDiscovery() {
|
Chris@0
|
71 if (!isset($this->discovery)) {
|
Chris@0
|
72 $directories = array_map(function ($directory) {
|
Chris@14
|
73 // Check for use of the @deprecated /migration_templates directory.
|
Chris@14
|
74 // @todo Remove use of /migration_templates in Drupal 9.0.0.
|
Chris@14
|
75 if (is_dir($directory . '/migration_templates')) {
|
Chris@14
|
76 @trigger_error('Use of the /migration_templates directory to store migration configuration files is deprecated in Drupal 8.1.0 and will be removed before Drupal 9.0.0. See https://www.drupal.org/node/2920988.', E_USER_DEPRECATED);
|
Chris@14
|
77 }
|
Chris@14
|
78 // But still accept configurations found in /migration_templates.
|
Chris@0
|
79 return [$directory . '/migration_templates', $directory . '/migrations'];
|
Chris@0
|
80 }, $this->moduleHandler->getModuleDirectories());
|
Chris@0
|
81
|
Chris@0
|
82 $yaml_discovery = new YamlDirectoryDiscovery($directories, 'migrate');
|
Chris@0
|
83 // This gets rid of migrations which try to use a non-existent source
|
Chris@0
|
84 // plugin. The common case for this is if the source plugin has, or
|
Chris@0
|
85 // specifies, a non-existent provider.
|
Chris@17
|
86 $only_with_source_discovery = new NoSourcePluginDecorator($yaml_discovery);
|
Chris@0
|
87 // This gets rid of migrations with explicit providers set if one of the
|
Chris@0
|
88 // providers do not exist before we try to use a potentially non-existing
|
Chris@0
|
89 // deriver. This is a rare case.
|
Chris@0
|
90 $filtered_discovery = new ProviderFilterDecorator($only_with_source_discovery, [$this->moduleHandler, 'moduleExists']);
|
Chris@0
|
91 $this->discovery = new ContainerDerivativeDiscoveryDecorator($filtered_discovery);
|
Chris@0
|
92 }
|
Chris@0
|
93 return $this->discovery;
|
Chris@0
|
94 }
|
Chris@0
|
95
|
Chris@0
|
96 /**
|
Chris@0
|
97 * {@inheritdoc}
|
Chris@0
|
98 */
|
Chris@0
|
99 public function createInstance($plugin_id, array $configuration = []) {
|
Chris@0
|
100 $instances = $this->createInstances([$plugin_id], [$plugin_id => $configuration]);
|
Chris@0
|
101 return reset($instances);
|
Chris@0
|
102 }
|
Chris@0
|
103
|
Chris@0
|
104 /**
|
Chris@0
|
105 * {@inheritdoc}
|
Chris@0
|
106 */
|
Chris@0
|
107 public function createInstances($migration_id, array $configuration = []) {
|
Chris@0
|
108 if (empty($migration_id)) {
|
Chris@0
|
109 $migration_id = array_keys($this->getDefinitions());
|
Chris@0
|
110 }
|
Chris@0
|
111
|
Chris@0
|
112 $factory = $this->getFactory();
|
Chris@0
|
113 $migration_ids = (array) $migration_id;
|
Chris@0
|
114 $plugin_ids = $this->expandPluginIds($migration_ids);
|
Chris@0
|
115
|
Chris@0
|
116 $instances = [];
|
Chris@0
|
117 foreach ($plugin_ids as $plugin_id) {
|
Chris@0
|
118 $instances[$plugin_id] = $factory->createInstance($plugin_id, isset($configuration[$plugin_id]) ? $configuration[$plugin_id] : []);
|
Chris@0
|
119 }
|
Chris@0
|
120
|
Chris@0
|
121 foreach ($instances as $migration) {
|
Chris@0
|
122 $migration->set('migration_dependencies', array_map([$this, 'expandPluginIds'], $migration->getMigrationDependencies()));
|
Chris@0
|
123 }
|
Chris@0
|
124
|
Chris@0
|
125 // Sort the migrations based on their dependencies.
|
Chris@0
|
126 return $this->buildDependencyMigration($instances, []);
|
Chris@0
|
127 }
|
Chris@0
|
128
|
Chris@0
|
129 /**
|
Chris@14
|
130 * {@inheritdoc}
|
Chris@0
|
131 */
|
Chris@0
|
132 public function createInstancesByTag($tag) {
|
Chris@0
|
133 $migrations = array_filter($this->getDefinitions(), function ($migration) use ($tag) {
|
Chris@0
|
134 return !empty($migration['migration_tags']) && in_array($tag, $migration['migration_tags']);
|
Chris@0
|
135 });
|
Chris@0
|
136 return $this->createInstances(array_keys($migrations));
|
Chris@0
|
137 }
|
Chris@0
|
138
|
Chris@0
|
139 /**
|
Chris@0
|
140 * Expand derivative migration dependencies.
|
Chris@0
|
141 *
|
Chris@0
|
142 * We need to expand any derivative migrations. Derivative migrations are
|
Chris@0
|
143 * calculated by migration derivers such as D6NodeDeriver. This allows
|
Chris@0
|
144 * migrations to depend on the base id and then have a dependency on all
|
Chris@0
|
145 * derivative migrations. For example, d6_comment depends on d6_node but after
|
Chris@0
|
146 * we've expanded the dependencies it will depend on d6_node:page,
|
Chris@0
|
147 * d6_node:story and so on, for other derivative migrations.
|
Chris@0
|
148 *
|
Chris@0
|
149 * @return array
|
Chris@0
|
150 * An array of expanded plugin ids.
|
Chris@0
|
151 */
|
Chris@0
|
152 protected function expandPluginIds(array $migration_ids) {
|
Chris@0
|
153 $plugin_ids = [];
|
Chris@0
|
154 foreach ($migration_ids as $id) {
|
Chris@0
|
155 $plugin_ids += preg_grep('/^' . preg_quote($id, '/') . PluginBase::DERIVATIVE_SEPARATOR . '/', array_keys($this->getDefinitions()));
|
Chris@0
|
156 if ($this->hasDefinition($id)) {
|
Chris@0
|
157 $plugin_ids[] = $id;
|
Chris@0
|
158 }
|
Chris@0
|
159 }
|
Chris@0
|
160 return $plugin_ids;
|
Chris@0
|
161 }
|
Chris@0
|
162
|
Chris@0
|
163 /**
|
Chris@0
|
164 * {@inheritdoc}
|
Chris@0
|
165 */
|
Chris@0
|
166 public function buildDependencyMigration(array $migrations, array $dynamic_ids) {
|
Chris@0
|
167 // Migration dependencies can be optional or required. If an optional
|
Chris@0
|
168 // dependency does not run, the current migration is still OK to go. Both
|
Chris@0
|
169 // optional and required dependencies (if run at all) must run before the
|
Chris@0
|
170 // current migration.
|
Chris@0
|
171 $dependency_graph = [];
|
Chris@0
|
172 $required_dependency_graph = [];
|
Chris@0
|
173 $have_optional = FALSE;
|
Chris@0
|
174 foreach ($migrations as $migration) {
|
Chris@0
|
175 /** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
|
Chris@0
|
176 $id = $migration->id();
|
Chris@0
|
177 $requirements[$id] = [];
|
Chris@0
|
178 $dependency_graph[$id]['edges'] = [];
|
Chris@0
|
179 $migration_dependencies = $migration->getMigrationDependencies();
|
Chris@0
|
180
|
Chris@0
|
181 if (isset($migration_dependencies['required'])) {
|
Chris@0
|
182 foreach ($migration_dependencies['required'] as $dependency) {
|
Chris@0
|
183 if (!isset($dynamic_ids[$dependency])) {
|
Chris@0
|
184 $this->addDependency($required_dependency_graph, $id, $dependency, $dynamic_ids);
|
Chris@0
|
185 }
|
Chris@0
|
186 $this->addDependency($dependency_graph, $id, $dependency, $dynamic_ids);
|
Chris@0
|
187 }
|
Chris@0
|
188 }
|
Chris@0
|
189 if (!empty($migration_dependencies['optional'])) {
|
Chris@0
|
190 foreach ($migration_dependencies['optional'] as $dependency) {
|
Chris@0
|
191 $this->addDependency($dependency_graph, $id, $dependency, $dynamic_ids);
|
Chris@0
|
192 }
|
Chris@0
|
193 $have_optional = TRUE;
|
Chris@0
|
194 }
|
Chris@0
|
195 }
|
Chris@0
|
196 $dependency_graph = (new Graph($dependency_graph))->searchAndSort();
|
Chris@0
|
197 if ($have_optional) {
|
Chris@0
|
198 $required_dependency_graph = (new Graph($required_dependency_graph))->searchAndSort();
|
Chris@0
|
199 }
|
Chris@0
|
200 else {
|
Chris@0
|
201 $required_dependency_graph = $dependency_graph;
|
Chris@0
|
202 }
|
Chris@0
|
203 $weights = [];
|
Chris@0
|
204 foreach ($migrations as $migration_id => $migration) {
|
Chris@0
|
205 // Populate a weights array to use with array_multisort() later.
|
Chris@0
|
206 $weights[] = $dependency_graph[$migration_id]['weight'];
|
Chris@0
|
207 if (!empty($required_dependency_graph[$migration_id]['paths'])) {
|
Chris@0
|
208 $migration->set('requirements', $required_dependency_graph[$migration_id]['paths']);
|
Chris@0
|
209 }
|
Chris@0
|
210 }
|
Chris@17
|
211 // Sort weights, labels, and keys in the same order as each other.
|
Chris@17
|
212 array_multisort(
|
Chris@17
|
213 // Use the numerical weight as the primary sort.
|
Chris@17
|
214 $weights, SORT_DESC, SORT_NUMERIC,
|
Chris@17
|
215 // When migrations have the same weight, sort them alphabetically by ID.
|
Chris@17
|
216 array_keys($migrations), SORT_ASC, SORT_NATURAL,
|
Chris@17
|
217 $migrations
|
Chris@17
|
218 );
|
Chris@0
|
219
|
Chris@0
|
220 return $migrations;
|
Chris@0
|
221 }
|
Chris@0
|
222
|
Chris@0
|
223 /**
|
Chris@0
|
224 * Add one or more dependencies to a graph.
|
Chris@0
|
225 *
|
Chris@0
|
226 * @param array $graph
|
Chris@0
|
227 * The graph so far, passed by reference.
|
Chris@0
|
228 * @param int $id
|
Chris@0
|
229 * The migration ID.
|
Chris@0
|
230 * @param string $dependency
|
Chris@0
|
231 * The dependency string.
|
Chris@0
|
232 * @param array $dynamic_ids
|
Chris@0
|
233 * The dynamic ID mapping.
|
Chris@0
|
234 */
|
Chris@0
|
235 protected function addDependency(array &$graph, $id, $dependency, $dynamic_ids) {
|
Chris@0
|
236 $dependencies = isset($dynamic_ids[$dependency]) ? $dynamic_ids[$dependency] : [$dependency];
|
Chris@0
|
237 if (!isset($graph[$id]['edges'])) {
|
Chris@0
|
238 $graph[$id]['edges'] = [];
|
Chris@0
|
239 }
|
Chris@0
|
240 $graph[$id]['edges'] += array_combine($dependencies, $dependencies);
|
Chris@0
|
241 }
|
Chris@0
|
242
|
Chris@0
|
243 /**
|
Chris@0
|
244 * {@inheritdoc}
|
Chris@0
|
245 */
|
Chris@0
|
246 public function createStubMigration(array $definition) {
|
Chris@0
|
247 $id = isset($definition['id']) ? $definition['id'] : uniqid();
|
Chris@0
|
248 return Migration::create(\Drupal::getContainer(), [], $id, $definition);
|
Chris@0
|
249 }
|
Chris@0
|
250
|
Chris@0
|
251 /**
|
Chris@0
|
252 * Finds plugin definitions.
|
Chris@0
|
253 *
|
Chris@0
|
254 * @return array
|
Chris@0
|
255 * List of definitions to store in cache.
|
Chris@0
|
256 *
|
Chris@0
|
257 * @todo This is a temporary solution to the fact that migration source
|
Chris@0
|
258 * plugins have more than one provider. This functionality will be moved to
|
Chris@0
|
259 * core in https://www.drupal.org/node/2786355.
|
Chris@0
|
260 */
|
Chris@0
|
261 protected function findDefinitions() {
|
Chris@0
|
262 $definitions = $this->getDiscovery()->getDefinitions();
|
Chris@0
|
263 foreach ($definitions as $plugin_id => &$definition) {
|
Chris@0
|
264 $this->processDefinition($definition, $plugin_id);
|
Chris@0
|
265 }
|
Chris@0
|
266 $this->alterDefinitions($definitions);
|
Chris@0
|
267 return ProviderFilterDecorator::filterDefinitions($definitions, function ($provider) {
|
Chris@0
|
268 return $this->providerExists($provider);
|
Chris@0
|
269 });
|
Chris@0
|
270 }
|
Chris@0
|
271
|
Chris@0
|
272 }
|