Chris@0: '\Drupal\migrate\Plugin\Migration', Chris@0: ]; Chris@0: Chris@0: /** Chris@0: * The interface the plugins should implement. Chris@0: * Chris@0: * @var string Chris@0: */ Chris@0: protected $pluginInterface = 'Drupal\migrate\Plugin\MigrationInterface'; Chris@0: Chris@0: /** Chris@0: * The module handler. Chris@0: * Chris@0: * @var \Drupal\Core\Extension\ModuleHandlerInterface Chris@0: */ Chris@0: protected $moduleHandler; Chris@0: Chris@0: /** Chris@0: * Construct a migration plugin manager. Chris@0: * Chris@0: * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler Chris@0: * The module handler. Chris@0: * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend Chris@0: * The cache backend for the definitions. Chris@0: * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager Chris@0: * The language manager. Chris@0: */ Chris@0: public function __construct(ModuleHandlerInterface $module_handler, CacheBackendInterface $cache_backend, LanguageManagerInterface $language_manager) { Chris@0: $this->factory = new ContainerFactory($this, $this->pluginInterface); Chris@0: $this->alterInfo('migration_plugins'); Chris@0: $this->setCacheBackend($cache_backend, 'migration_plugins', ['migration_plugins']); Chris@0: $this->moduleHandler = $module_handler; Chris@0: } Chris@0: Chris@0: /** Chris@14: * Gets the plugin discovery. Chris@14: * Chris@14: * This method overrides DefaultPluginManager::getDiscovery() in order to Chris@14: * search for migration configurations in the MODULENAME/migrations and Chris@14: * MODULENAME/migration_templates directories. Throws a deprecation notice if Chris@14: * the MODULENAME/migration_templates directory exists. Chris@0: */ Chris@0: protected function getDiscovery() { Chris@0: if (!isset($this->discovery)) { Chris@0: $directories = array_map(function ($directory) { Chris@14: // Check for use of the @deprecated /migration_templates directory. Chris@14: // @todo Remove use of /migration_templates in Drupal 9.0.0. Chris@14: if (is_dir($directory . '/migration_templates')) { Chris@14: @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: } Chris@14: // But still accept configurations found in /migration_templates. Chris@0: return [$directory . '/migration_templates', $directory . '/migrations']; Chris@0: }, $this->moduleHandler->getModuleDirectories()); Chris@0: Chris@0: $yaml_discovery = new YamlDirectoryDiscovery($directories, 'migrate'); Chris@0: // This gets rid of migrations which try to use a non-existent source Chris@0: // plugin. The common case for this is if the source plugin has, or Chris@0: // specifies, a non-existent provider. Chris@17: $only_with_source_discovery = new NoSourcePluginDecorator($yaml_discovery); Chris@0: // This gets rid of migrations with explicit providers set if one of the Chris@0: // providers do not exist before we try to use a potentially non-existing Chris@0: // deriver. This is a rare case. Chris@0: $filtered_discovery = new ProviderFilterDecorator($only_with_source_discovery, [$this->moduleHandler, 'moduleExists']); Chris@0: $this->discovery = new ContainerDerivativeDiscoveryDecorator($filtered_discovery); Chris@0: } Chris@0: return $this->discovery; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function createInstance($plugin_id, array $configuration = []) { Chris@0: $instances = $this->createInstances([$plugin_id], [$plugin_id => $configuration]); Chris@0: return reset($instances); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function createInstances($migration_id, array $configuration = []) { Chris@0: if (empty($migration_id)) { Chris@0: $migration_id = array_keys($this->getDefinitions()); Chris@0: } Chris@0: Chris@0: $factory = $this->getFactory(); Chris@0: $migration_ids = (array) $migration_id; Chris@0: $plugin_ids = $this->expandPluginIds($migration_ids); Chris@0: Chris@0: $instances = []; Chris@0: foreach ($plugin_ids as $plugin_id) { Chris@0: $instances[$plugin_id] = $factory->createInstance($plugin_id, isset($configuration[$plugin_id]) ? $configuration[$plugin_id] : []); Chris@0: } Chris@0: Chris@0: foreach ($instances as $migration) { Chris@0: $migration->set('migration_dependencies', array_map([$this, 'expandPluginIds'], $migration->getMigrationDependencies())); Chris@0: } Chris@0: Chris@0: // Sort the migrations based on their dependencies. Chris@0: return $this->buildDependencyMigration($instances, []); Chris@0: } Chris@0: Chris@0: /** Chris@14: * {@inheritdoc} Chris@0: */ Chris@0: public function createInstancesByTag($tag) { Chris@0: $migrations = array_filter($this->getDefinitions(), function ($migration) use ($tag) { Chris@0: return !empty($migration['migration_tags']) && in_array($tag, $migration['migration_tags']); Chris@0: }); Chris@0: return $this->createInstances(array_keys($migrations)); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Expand derivative migration dependencies. Chris@0: * Chris@0: * We need to expand any derivative migrations. Derivative migrations are Chris@0: * calculated by migration derivers such as D6NodeDeriver. This allows Chris@0: * migrations to depend on the base id and then have a dependency on all Chris@0: * derivative migrations. For example, d6_comment depends on d6_node but after Chris@0: * we've expanded the dependencies it will depend on d6_node:page, Chris@0: * d6_node:story and so on, for other derivative migrations. Chris@0: * Chris@0: * @return array Chris@0: * An array of expanded plugin ids. Chris@0: */ Chris@0: protected function expandPluginIds(array $migration_ids) { Chris@0: $plugin_ids = []; Chris@0: foreach ($migration_ids as $id) { Chris@0: $plugin_ids += preg_grep('/^' . preg_quote($id, '/') . PluginBase::DERIVATIVE_SEPARATOR . '/', array_keys($this->getDefinitions())); Chris@0: if ($this->hasDefinition($id)) { Chris@0: $plugin_ids[] = $id; Chris@0: } Chris@0: } Chris@0: return $plugin_ids; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function buildDependencyMigration(array $migrations, array $dynamic_ids) { Chris@0: // Migration dependencies can be optional or required. If an optional Chris@0: // dependency does not run, the current migration is still OK to go. Both Chris@0: // optional and required dependencies (if run at all) must run before the Chris@0: // current migration. Chris@0: $dependency_graph = []; Chris@0: $required_dependency_graph = []; Chris@0: $have_optional = FALSE; Chris@0: foreach ($migrations as $migration) { Chris@0: /** @var \Drupal\migrate\Plugin\MigrationInterface $migration */ Chris@0: $id = $migration->id(); Chris@0: $requirements[$id] = []; Chris@0: $dependency_graph[$id]['edges'] = []; Chris@0: $migration_dependencies = $migration->getMigrationDependencies(); Chris@0: Chris@0: if (isset($migration_dependencies['required'])) { Chris@0: foreach ($migration_dependencies['required'] as $dependency) { Chris@0: if (!isset($dynamic_ids[$dependency])) { Chris@0: $this->addDependency($required_dependency_graph, $id, $dependency, $dynamic_ids); Chris@0: } Chris@0: $this->addDependency($dependency_graph, $id, $dependency, $dynamic_ids); Chris@0: } Chris@0: } Chris@0: if (!empty($migration_dependencies['optional'])) { Chris@0: foreach ($migration_dependencies['optional'] as $dependency) { Chris@0: $this->addDependency($dependency_graph, $id, $dependency, $dynamic_ids); Chris@0: } Chris@0: $have_optional = TRUE; Chris@0: } Chris@0: } Chris@0: $dependency_graph = (new Graph($dependency_graph))->searchAndSort(); Chris@0: if ($have_optional) { Chris@0: $required_dependency_graph = (new Graph($required_dependency_graph))->searchAndSort(); Chris@0: } Chris@0: else { Chris@0: $required_dependency_graph = $dependency_graph; Chris@0: } Chris@0: $weights = []; Chris@0: foreach ($migrations as $migration_id => $migration) { Chris@0: // Populate a weights array to use with array_multisort() later. Chris@0: $weights[] = $dependency_graph[$migration_id]['weight']; Chris@0: if (!empty($required_dependency_graph[$migration_id]['paths'])) { Chris@0: $migration->set('requirements', $required_dependency_graph[$migration_id]['paths']); Chris@0: } Chris@0: } Chris@17: // Sort weights, labels, and keys in the same order as each other. Chris@17: array_multisort( Chris@17: // Use the numerical weight as the primary sort. Chris@17: $weights, SORT_DESC, SORT_NUMERIC, Chris@17: // When migrations have the same weight, sort them alphabetically by ID. Chris@17: array_keys($migrations), SORT_ASC, SORT_NATURAL, Chris@17: $migrations Chris@17: ); Chris@0: Chris@0: return $migrations; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Add one or more dependencies to a graph. Chris@0: * Chris@0: * @param array $graph Chris@0: * The graph so far, passed by reference. Chris@0: * @param int $id Chris@0: * The migration ID. Chris@0: * @param string $dependency Chris@0: * The dependency string. Chris@0: * @param array $dynamic_ids Chris@0: * The dynamic ID mapping. Chris@0: */ Chris@0: protected function addDependency(array &$graph, $id, $dependency, $dynamic_ids) { Chris@0: $dependencies = isset($dynamic_ids[$dependency]) ? $dynamic_ids[$dependency] : [$dependency]; Chris@0: if (!isset($graph[$id]['edges'])) { Chris@0: $graph[$id]['edges'] = []; Chris@0: } Chris@0: $graph[$id]['edges'] += array_combine($dependencies, $dependencies); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function createStubMigration(array $definition) { Chris@0: $id = isset($definition['id']) ? $definition['id'] : uniqid(); Chris@0: return Migration::create(\Drupal::getContainer(), [], $id, $definition); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Finds plugin definitions. Chris@0: * Chris@0: * @return array Chris@0: * List of definitions to store in cache. Chris@0: * Chris@0: * @todo This is a temporary solution to the fact that migration source Chris@0: * plugins have more than one provider. This functionality will be moved to Chris@0: * core in https://www.drupal.org/node/2786355. Chris@0: */ Chris@0: protected function findDefinitions() { Chris@0: $definitions = $this->getDiscovery()->getDefinitions(); Chris@0: foreach ($definitions as $plugin_id => &$definition) { Chris@0: $this->processDefinition($definition, $plugin_id); Chris@0: } Chris@0: $this->alterDefinitions($definitions); Chris@0: return ProviderFilterDecorator::filterDefinitions($definitions, function ($provider) { Chris@0: return $this->providerExists($provider); Chris@0: }); Chris@0: } Chris@0: Chris@0: }