3 namespace Drupal\migrate_tools;
5 use Drupal\migrate\MigrateMessage;
6 use Drupal\migrate\MigrateMessageInterface;
7 use Drupal\migrate\Plugin\Migration;
8 use Drupal\migrate\Plugin\MigrationInterface;
11 * Defines a migrate executable class for batch migrations through UI.
13 class MigrateBatchExecutable extends MigrateExecutable {
16 * Representing a batch import operation.
18 const BATCH_IMPORT = 1;
21 * Indicates if we need to update existing rows or skip them.
25 protected $updateExistingRows = 0;
28 * Indicates if we need import dependent migrations also.
32 protected $checkDependencies = 0;
35 * The current batch context.
39 protected $batchContext = [];
42 * Plugin manager for migration plugins.
44 * @var \Drupal\migrate\Plugin\MigrationPluginManagerInterface
46 protected $migrationPluginManager;
51 public function __construct(MigrationInterface $migration, MigrateMessageInterface $message, array $options = []) {
53 if (isset($options['update'])) {
54 $this->updateExistingRows = $options['update'];
57 if (isset($options['force'])) {
58 $this->checkDependencies = $options['force'];
61 parent::__construct($migration, $message, $options);
62 $this->migrationPluginManager = \Drupal::getContainer()->get('plugin.manager.migration');
66 * Sets the current batch content so listeners can update the messages.
68 * @param array $context
71 public function setBatchContext(array &$context) {
72 $this->batchContext = &$context;
76 * Gets a reference to the current batch context.
81 public function &getBatchContext() {
82 return $this->batchContext;
86 * Setup batch operations for running the migration.
88 public function batchImport() {
89 // Create the batch operations for each migration that needs to be executed.
90 // This includes the migration for this executable, but also the dependent
92 $operations = $this->batchOperations([$this->migration], 'import', [
93 'limit' => $this->itemLimit,
94 'update' => $this->updateExistingRows,
95 'force' => $this->checkDependencies,
98 if (count($operations) > 0) {
100 'operations' => $operations,
101 'title' => t('Migrating %migrate', ['%migrate' => $this->migration->label()]),
102 'init_message' => t('Start migrating %migrate', ['%migrate' => $this->migration->label()]),
103 'progress_message' => t('Migrating %migrate', ['%migrate' => $this->migration->label()]),
104 'error_message' => t('An error occurred while migrating %migrate.', ['%migrate' => $this->migration->label()]),
105 'finished' => '\Drupal\migrate_tools\MigrateBatchExecutable::batchFinishedImport',
113 * Helper to generate the batch operations for importing migrations.
115 * @param \Drupal\migrate\Plugin\MigrationInterface[] $migrations
117 * @param string $operation
118 * The batch operation to perform.
119 * @param array $options
120 * The migration options.
123 * The batch operations to perform.
125 protected function batchOperations(array $migrations, $operation, array $options = []) {
127 foreach ($migrations as $id => $migration) {
129 if (!empty($options['update'])) {
130 $migration->getIdMap()->prepareUpdate();
133 if (!empty($options['force'])) {
134 $migration->set('requirements', []);
137 $dependencies = $migration->getMigrationDependencies();
138 if (!empty($dependencies['required'])) {
139 $required_migrations = $this->migrationPluginManager->createInstances($dependencies['required']);
140 // For dependent migrations will need to be migrate all items.
141 $dependent_options = $options;
142 $dependent_options['limit'] = 0;
143 $operations += $this->batchOperations($required_migrations, $operation, [
145 'update' => $options['update'],
146 'force' => $options['force'],
152 '\Drupal\migrate_tools\MigrateBatchExecutable::batchProcessImport',
153 [$migration->id(), $options],
161 * Batch 'operation' callback.
163 * @param string $migration_id
165 * @param array $options
166 * The batch executable options.
167 * @param array $context
168 * The sandbox context.
170 public static function batchProcessImport($migration_id, array $options, array &$context) {
171 if (empty($context['sandbox'])) {
172 $context['finished'] = 0;
173 $context['sandbox'] = [];
174 $context['sandbox']['total'] = 0;
175 $context['sandbox']['counter'] = 0;
176 $context['sandbox']['batch_limit'] = 0;
177 $context['sandbox']['operation'] = MigrateBatchExecutable::BATCH_IMPORT;
180 // Prepare the migration executable.
181 $message = new MigrateMessage();
182 /** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
183 $migration = \Drupal::getContainer()->get('plugin.manager.migration')->createInstance($migration_id);
184 $executable = new MigrateBatchExecutable($migration, $message, $options);
186 if (empty($context['sandbox']['total'])) {
187 $context['sandbox']['total'] = $executable->getSource()->count();
188 $context['sandbox']['batch_limit'] = $executable->calculateBatchLimit($context);
189 $context['results'][$migration->id()] = [
195 '@name' => $migration->id(),
199 // Every iteration, we reset out batch counter.
200 $context['sandbox']['batch_counter'] = 0;
202 // Make sure we know our batch context.
203 $executable->setBatchContext($context);
206 $result = $executable->import();
208 // Store the result; will need to combine the results of all our iterations.
209 $context['results'][$migration->id()] = [
210 '@numitems' => $context['results'][$migration->id()]['@numitems'] + $executable->getProcessedCount(),
211 '@created' => $context['results'][$migration->id()]['@created'] + $executable->getCreatedCount(),
212 '@updated' => $context['results'][$migration->id()]['@updated'] + $executable->getUpdatedCount(),
213 '@failures' => $context['results'][$migration->id()]['@failures'] + $executable->getFailedCount(),
214 '@ignored' => $context['results'][$migration->id()]['@ignored'] + $executable->getIgnoredCount(),
215 '@name' => $migration->id(),
218 // Do some housekeeping.
220 $result != MigrationInterface::RESULT_INCOMPLETE
222 $context['finished'] = 1;
225 $context['sandbox']['counter'] = $context['results'][$migration->id()]['@numitems'];
226 if ($context['sandbox']['counter'] <= $context['sandbox']['total']) {
227 $context['finished'] = ((float) $context['sandbox']['counter'] / (float) $context['sandbox']['total']);
228 $context['message'] = t('Importing %migration (@percent%).', [
229 '%migration' => $migration->label(),
230 '@percent' => (int) ($context['finished'] * 100),
238 * Finished callback for import batches.
240 * @param bool $success
241 * A boolean indicating whether the batch has completed successfully.
242 * @param array $results
243 * The value set in $context['results'] by callback_batch_operation().
244 * @param array $operations
245 * If $success is FALSE, contains the operations that remained unprocessed.
247 public static function batchFinishedImport($success, array $results, array $operations) {
249 foreach ($results as $migration_id => $result) {
250 $singular_message = "Processed 1 item (@created created, @updated updated, @failures failed, @ignored ignored) - done with '@name'";
251 $plural_message = "Processed @numitems items (@created created, @updated updated, @failures failed, @ignored ignored) - done with '@name'";
252 drupal_set_message(\Drupal::translation()->formatPlural($result['@numitems'],
263 public function checkStatus() {
264 $status = parent::checkStatus();
266 if ($status == MigrationInterface::RESULT_COMPLETED) {
267 // Do some batch housekeeping.
268 $context = $this->getBatchContext();
270 if (!empty($context['sandbox']) && $context['sandbox']['operation'] == MigrateBatchExecutable::BATCH_IMPORT) {
271 $context['sandbox']['batch_counter']++;
272 if ($context['sandbox']['batch_counter'] >= $context['sandbox']['batch_limit']) {
273 $status = MigrationInterface::RESULT_INCOMPLETE;
282 * Calculates how much a single batch iteration will handle.
284 * @param array $context
285 * The sandbox context.
290 public function calculateBatchLimit(array $context) {
291 // TODO Maybe we need some other more sophisticated logic here?
292 return ceil($context['sandbox']['total'] / 100);