annotate core/modules/migrate/src/Plugin/MigrationPluginManager.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents 129ea1e6d783
children
rev   line source
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 }