annotate core/modules/migrate/src/Plugin/MigrationPluginManager.php @ 0:4c8ae668cc8c

Initial import (non-working)
author Chris Cannam
date Wed, 29 Nov 2017 16:09:58 +0000
parents
children 1fec387a4317
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@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 }