3 namespace Drupal\migrate\Plugin;
5 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
6 use Drupal\Core\Plugin\PluginBase;
7 use Drupal\migrate\Exception\RequirementsException;
8 use Drupal\migrate\MigrateException;
9 use Drupal\migrate\MigrateSkipRowException;
10 use Drupal\Component\Utility\NestedArray;
11 use Symfony\Component\DependencyInjection\ContainerInterface;
14 * Defines the Migration plugin.
16 * The migration process plugin represents one single migration and acts like a
17 * container for the information about a single migration such as the source,
18 * process and destination plugins.
20 class Migration extends PluginBase implements MigrationInterface, RequirementsInterface, ContainerFactoryPluginInterface {
23 * The migration ID (machine name).
30 * The human-readable label for the migration.
37 * The plugin ID for the row.
44 * The source configuration, with at least a 'plugin' key.
46 * Used to initialize the $sourcePlugin.
55 * @var \Drupal\migrate\Plugin\MigrateSourceInterface
57 protected $sourcePlugin;
60 * The configuration describing the process plugins.
62 * This is a strictly internal property and should not returned to calling
63 * code, use getProcess() instead.
67 protected $process = [];
70 * The cached process plugins.
74 protected $processPlugins = [];
77 * The destination configuration, with at least a 'plugin' key.
79 * Used to initialize $destinationPlugin.
83 protected $destination;
86 * The destination plugin.
88 * @var \Drupal\migrate\Plugin\MigrateDestinationInterface
90 protected $destinationPlugin;
93 * The identifier map data.
95 * Used to initialize $idMapPlugin.
99 protected $idMap = [];
102 * The identifier map.
104 * @var \Drupal\migrate\Plugin\MigrateIdMapInterface
106 protected $idMapPlugin;
109 * The source identifiers.
111 * An array of source identifiers: the keys are the name of the properties,
112 * the values are dependent on the ID map plugin.
116 protected $sourceIds = [];
119 * The destination identifiers.
121 * An array of destination identifiers: the keys are the name of the
122 * properties, the values are dependent on the ID map plugin.
126 protected $destinationIds = [];
129 * Specify value of source_row_status for current map row. Usually set by
130 * MigrateFieldHandler implementations.
134 protected $sourceRowStatus = MigrateIdMapInterface::STATUS_IMPORTED;
137 * Track time of last import if TRUE.
141 protected $trackLastImported = FALSE;
144 * These migrations must be already executed before this migration can run.
148 protected $requirements = [];
151 * An optional list of tags, used by the plugin manager for filtering.
155 protected $migration_tags = [];
158 * Whether the migration is auditable.
160 * If set to TRUE, the migration's IDs will be audited. This means that, if
161 * the highest destination ID is greater than the highest source ID, a warning
162 * will be displayed that entities might be overwritten.
166 protected $audit = FALSE;
169 * These migrations, if run, must be executed before this migration.
171 * These are different from the configuration dependencies. Migration
172 * dependencies are only used to store relationships between migrations.
174 * The migration_dependencies value is structured like this:
177 * 'required' => array(
178 * // An array of migration IDs that must be run before this migration.
180 * 'optional' => array(
181 * // An array of migration IDs that, if they exist, must be run before
189 protected $migration_dependencies = [];
192 * The migration's configuration dependencies.
194 * These store any dependencies on modules or other configuration (including
195 * other migrations) that must be available before the migration can be
198 * @see \Drupal\Core\Config\Entity\ConfigDependencyManager
202 protected $dependencies = [];
205 * The migration plugin manager for loading other migration plugins.
207 * @var \Drupal\migrate\Plugin\MigrationPluginManagerInterface
209 protected $migrationPluginManager;
212 * The source plugin manager.
214 * @var \Drupal\migrate\Plugin\MigratePluginManager
216 protected $sourcePluginManager;
219 * Thep process plugin manager.
221 * @var \Drupal\migrate\Plugin\MigratePluginManager
223 protected $processPluginManager;
226 * The destination plugin manager.
228 * @var \Drupal\migrate\Plugin\MigrateDestinationPluginManager
230 protected $destinationPluginManager;
233 * The ID map plugin manager.
235 * @var \Drupal\migrate\Plugin\MigratePluginManager
237 protected $idMapPluginManager;
240 * Labels corresponding to each defined status.
244 protected $statusLabels = [
245 self::STATUS_IDLE => 'Idle',
246 self::STATUS_IMPORTING => 'Importing',
247 self::STATUS_ROLLING_BACK => 'Rolling back',
248 self::STATUS_STOPPING => 'Stopping',
249 self::STATUS_DISABLED => 'Disabled',
253 * Constructs a Migration.
255 * @param array $configuration
256 * Plugin configuration.
257 * @param string $plugin_id
259 * @param mixed $plugin_definition
260 * The plugin definition.
261 * @param \Drupal\migrate\Plugin\MigrationPluginManagerInterface $migration_plugin_manager
262 * The migration plugin manager.
263 * @param \Drupal\migrate\Plugin\MigratePluginManagerInterface $source_plugin_manager
264 * The source migration plugin manager.
265 * @param \Drupal\migrate\Plugin\MigratePluginManagerInterface $process_plugin_manager
266 * The process migration plugin manager.
267 * @param \Drupal\migrate\Plugin\MigrateDestinationPluginManager $destination_plugin_manager
268 * The destination migration plugin manager.
269 * @param \Drupal\migrate\Plugin\MigratePluginManagerInterface $idmap_plugin_manager
270 * The ID map migration plugin manager.
272 public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationPluginManagerInterface $migration_plugin_manager, MigratePluginManagerInterface $source_plugin_manager, MigratePluginManagerInterface $process_plugin_manager, MigrateDestinationPluginManager $destination_plugin_manager, MigratePluginManagerInterface $idmap_plugin_manager) {
273 parent::__construct($configuration, $plugin_id, $plugin_definition);
274 $this->migrationPluginManager = $migration_plugin_manager;
275 $this->sourcePluginManager = $source_plugin_manager;
276 $this->processPluginManager = $process_plugin_manager;
277 $this->destinationPluginManager = $destination_plugin_manager;
278 $this->idMapPluginManager = $idmap_plugin_manager;
280 foreach (NestedArray::mergeDeep($plugin_definition, $configuration) as $key => $value) {
281 $this->$key = $value;
288 public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
293 $container->get('plugin.manager.migration'),
294 $container->get('plugin.manager.migrate.source'),
295 $container->get('plugin.manager.migrate.process'),
296 $container->get('plugin.manager.migrate.destination'),
297 $container->get('plugin.manager.migrate.id_map')
304 public function id() {
305 return $this->pluginId;
311 public function label() {
316 * Gets any arbitrary property's value.
318 * @param string $property
319 * The property to retrieve.
322 * The value for that property, or NULL if the property does not exist.
324 * @deprecated in Drupal 8.1.x, will be removed before Drupal 9.0.x. Use
325 * more specific getters instead.
327 * @see https://www.drupal.org/node/2873795
329 public function get($property) {
330 return isset($this->$property) ? $this->$property : NULL;
334 * Retrieves the ID map plugin.
336 * @return \Drupal\migrate\Plugin\MigrateIdMapInterface
339 public function getIdMapPlugin() {
340 return $this->idMapPlugin;
346 public function getSourcePlugin() {
347 if (!isset($this->sourcePlugin)) {
348 $this->sourcePlugin = $this->sourcePluginManager->createInstance($this->source['plugin'], $this->source, $this);
350 return $this->sourcePlugin;
356 public function getProcessPlugins(array $process = NULL) {
357 if (!isset($process)) {
358 $process = $this->getProcess();
360 $index = serialize($process);
361 if (!isset($this->processPlugins[$index])) {
362 $this->processPlugins[$index] = [];
363 foreach ($this->getProcessNormalized($process) as $property => $configurations) {
364 $this->processPlugins[$index][$property] = [];
365 foreach ($configurations as $configuration) {
366 if (isset($configuration['source'])) {
367 $this->processPlugins[$index][$property][] = $this->processPluginManager->createInstance('get', $configuration, $this);
369 // Get is already handled.
370 if ($configuration['plugin'] != 'get') {
371 $this->processPlugins[$index][$property][] = $this->processPluginManager->createInstance($configuration['plugin'], $configuration, $this);
373 if (!$this->processPlugins[$index][$property]) {
374 throw new MigrateException("Invalid process configuration for $property");
379 return $this->processPlugins[$index];
383 * Resolve shorthands into a list of plugin configurations.
385 * @param array $process
386 * A process configuration array.
389 * The normalized process configuration.
391 protected function getProcessNormalized(array $process) {
392 $normalized_configurations = [];
393 foreach ($process as $destination => $configuration) {
394 if (is_string($configuration)) {
397 'source' => $configuration,
400 if (isset($configuration['plugin'])) {
401 $configuration = [$configuration];
403 $normalized_configurations[$destination] = $configuration;
405 return $normalized_configurations;
411 public function getDestinationPlugin($stub_being_requested = FALSE) {
412 if ($stub_being_requested && !empty($this->destination['no_stub'])) {
413 throw new MigrateSkipRowException();
415 if (!isset($this->destinationPlugin)) {
416 $this->destinationPlugin = $this->destinationPluginManager->createInstance($this->destination['plugin'], $this->destination, $this);
418 return $this->destinationPlugin;
424 public function getIdMap() {
425 if (!isset($this->idMapPlugin)) {
426 $configuration = $this->idMap;
427 $plugin = isset($configuration['plugin']) ? $configuration['plugin'] : 'sql';
428 $this->idMapPlugin = $this->idMapPluginManager->createInstance($plugin, $configuration, $this);
430 return $this->idMapPlugin;
436 public function checkRequirements() {
437 // Check whether the current migration source and destination plugin
438 // requirements are met or not.
439 if ($this->getSourcePlugin() instanceof RequirementsInterface) {
440 $this->getSourcePlugin()->checkRequirements();
442 if ($this->getDestinationPlugin() instanceof RequirementsInterface) {
443 $this->getDestinationPlugin()->checkRequirements();
446 if (empty($this->requirements)) {
447 // There are no requirements to check.
450 /** @var \Drupal\migrate\Plugin\MigrationInterface[] $required_migrations */
451 $required_migrations = $this->getMigrationPluginManager()->createInstances($this->requirements);
453 $missing_migrations = array_diff($this->requirements, array_keys($required_migrations));
454 // Check if the dependencies are in good shape.
455 foreach ($required_migrations as $migration_id => $required_migration) {
456 if (!$required_migration->allRowsProcessed()) {
457 $missing_migrations[] = $migration_id;
460 if ($missing_migrations) {
461 throw new RequirementsException('Missing migrations ' . implode(', ', $missing_migrations) . '.', ['requirements' => $missing_migrations]);
466 * Gets the migration plugin manager.
468 * @return \Drupal\migrate\Plugin\MigratePluginManager
469 * The plugin manager.
471 protected function getMigrationPluginManager() {
472 return $this->migrationPluginManager;
478 public function setStatus($status) {
479 \Drupal::keyValue('migrate_status')->set($this->id(), $status);
485 public function getStatus() {
486 return \Drupal::keyValue('migrate_status')->get($this->id(), static::STATUS_IDLE);
492 public function getStatusLabel() {
493 $status = $this->getStatus();
494 if (isset($this->statusLabels[$status])) {
495 return $this->statusLabels[$status];
505 public function getInterruptionResult() {
506 return \Drupal::keyValue('migrate_interruption_result')->get($this->id(), static::RESULT_INCOMPLETE);
512 public function clearInterruptionResult() {
513 \Drupal::keyValue('migrate_interruption_result')->delete($this->id());
519 public function interruptMigration($result) {
520 $this->setStatus(MigrationInterface::STATUS_STOPPING);
521 \Drupal::keyValue('migrate_interruption_result')->set($this->id(), $result);
527 public function allRowsProcessed() {
528 $source_count = $this->getSourcePlugin()->count();
529 // If the source is uncountable, we have no way of knowing if it's
530 // complete, so stipulate that it is.
531 if ($source_count < 0) {
534 $processed_count = $this->getIdMap()->processedCount();
535 // We don't use == because in some circumstances (like unresolved stubs
536 // being created), the processed count may be higher than the available
538 return $source_count <= $processed_count;
544 public function set($property_name, $value) {
545 if ($property_name == 'source') {
546 // Invalidate the source plugin.
547 unset($this->sourcePlugin);
549 elseif ($property_name === 'destination') {
550 // Invalidate the destination plugin.
551 unset($this->destinationPlugin);
553 $this->{$property_name} = $value;
561 public function getProcess() {
562 return $this->getProcessNormalized($this->process);
568 public function setProcess(array $process) {
569 $this->process = $process;
576 public function setProcessOfProperty($property, $process_of_property) {
577 $this->process[$property] = $process_of_property;
584 public function mergeProcessOfProperty($property, array $process_of_property) {
585 // If we already have a process value then merge the incoming process array
586 // otherwise simply set it.
587 $current_process = $this->getProcess();
588 if (isset($current_process[$property])) {
589 $this->process = NestedArray::mergeDeepArray([$current_process, $this->getProcessNormalized([$property => $process_of_property])], TRUE);
592 $this->setProcessOfProperty($property, $process_of_property);
601 public function isTrackLastImported() {
602 return $this->trackLastImported;
608 public function setTrackLastImported($track_last_imported) {
609 $this->trackLastImported = (bool) $track_last_imported;
616 public function getMigrationDependencies() {
617 $this->migration_dependencies = ($this->migration_dependencies ?: []) + ['required' => [], 'optional' => []];
618 $this->migration_dependencies['optional'] = array_unique(array_merge($this->migration_dependencies['optional'], $this->findMigrationDependencies($this->process)));
619 return $this->migration_dependencies;
623 * Find migration dependencies from the migration and the iterator plugins.
628 protected function findMigrationDependencies($process) {
630 foreach ($this->getProcessNormalized($process) as $process_pipeline) {
631 foreach ($process_pipeline as $plugin_configuration) {
632 if ($plugin_configuration['plugin'] == 'migration') {
633 $return = array_merge($return, (array) $plugin_configuration['migration']);
635 if ($plugin_configuration['plugin'] == 'sub_process') {
636 $return = array_merge($return, $this->findMigrationDependencies($plugin_configuration['process']));
646 public function getPluginDefinition() {
648 // While normal plugins do not change their definitions on the fly, this
649 // one does so accommodate for that.
650 foreach (parent::getPluginDefinition() as $key => $value) {
651 $definition[$key] = isset($this->$key) ? $this->$key : $value;
659 public function getDestinationConfiguration() {
660 return $this->destination;
666 public function getSourceConfiguration() {
667 return $this->source;
673 public function getTrackLastImported() {
674 return $this->trackLastImported;
680 public function getDestinationIds() {
681 return $this->destinationIds;
687 public function getMigrationTags() {
688 return $this->migration_tags;
694 public function isAuditable() {
695 return (bool) $this->audit;