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

/core/modules/migrate/src/Plugin/MigrationPluginManager.php

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