Chris@0
|
1 <?php
|
Chris@0
|
2
|
Chris@0
|
3 namespace Drupal\migrate;
|
Chris@0
|
4
|
Chris@0
|
5 use Drupal\Component\Utility\Bytes;
|
Chris@0
|
6 use Drupal\Core\Utility\Error;
|
Chris@0
|
7 use Drupal\Core\StringTranslation\StringTranslationTrait;
|
Chris@0
|
8 use Drupal\migrate\Event\MigrateEvents;
|
Chris@0
|
9 use Drupal\migrate\Event\MigrateImportEvent;
|
Chris@0
|
10 use Drupal\migrate\Event\MigratePostRowSaveEvent;
|
Chris@0
|
11 use Drupal\migrate\Event\MigratePreRowSaveEvent;
|
Chris@0
|
12 use Drupal\migrate\Event\MigrateRollbackEvent;
|
Chris@0
|
13 use Drupal\migrate\Event\MigrateRowDeleteEvent;
|
Chris@0
|
14 use Drupal\migrate\Exception\RequirementsException;
|
Chris@0
|
15 use Drupal\migrate\Plugin\MigrateIdMapInterface;
|
Chris@0
|
16 use Drupal\migrate\Plugin\MigrationInterface;
|
Chris@0
|
17 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
Chris@0
|
18
|
Chris@0
|
19 /**
|
Chris@0
|
20 * Defines a migrate executable class.
|
Chris@0
|
21 */
|
Chris@0
|
22 class MigrateExecutable implements MigrateExecutableInterface {
|
Chris@0
|
23 use StringTranslationTrait;
|
Chris@0
|
24
|
Chris@0
|
25 /**
|
Chris@0
|
26 * The configuration of the migration to do.
|
Chris@0
|
27 *
|
Chris@0
|
28 * @var \Drupal\migrate\Plugin\MigrationInterface
|
Chris@0
|
29 */
|
Chris@0
|
30 protected $migration;
|
Chris@0
|
31
|
Chris@0
|
32 /**
|
Chris@0
|
33 * Status of one row.
|
Chris@0
|
34 *
|
Chris@0
|
35 * The value is a MigrateIdMapInterface::STATUS_* constant, for example:
|
Chris@0
|
36 * STATUS_IMPORTED.
|
Chris@0
|
37 *
|
Chris@0
|
38 * @var int
|
Chris@0
|
39 */
|
Chris@0
|
40 protected $sourceRowStatus;
|
Chris@0
|
41
|
Chris@0
|
42 /**
|
Chris@0
|
43 * The ratio of the memory limit at which an operation will be interrupted.
|
Chris@0
|
44 *
|
Chris@0
|
45 * @var float
|
Chris@0
|
46 */
|
Chris@0
|
47 protected $memoryThreshold = 0.85;
|
Chris@0
|
48
|
Chris@0
|
49 /**
|
Chris@0
|
50 * The PHP memory_limit expressed in bytes.
|
Chris@0
|
51 *
|
Chris@0
|
52 * @var int
|
Chris@0
|
53 */
|
Chris@0
|
54 protected $memoryLimit;
|
Chris@0
|
55
|
Chris@0
|
56 /**
|
Chris@0
|
57 * The configuration values of the source.
|
Chris@0
|
58 *
|
Chris@0
|
59 * @var array
|
Chris@0
|
60 */
|
Chris@0
|
61 protected $sourceIdValues;
|
Chris@0
|
62
|
Chris@0
|
63 /**
|
Chris@0
|
64 * An array of counts. Initially used for cache hit/miss tracking.
|
Chris@0
|
65 *
|
Chris@0
|
66 * @var array
|
Chris@0
|
67 */
|
Chris@0
|
68 protected $counts = [];
|
Chris@0
|
69
|
Chris@0
|
70 /**
|
Chris@0
|
71 * The source.
|
Chris@0
|
72 *
|
Chris@0
|
73 * @var \Drupal\migrate\Plugin\MigrateSourceInterface
|
Chris@0
|
74 */
|
Chris@0
|
75 protected $source;
|
Chris@0
|
76
|
Chris@0
|
77 /**
|
Chris@0
|
78 * The event dispatcher.
|
Chris@0
|
79 *
|
Chris@0
|
80 * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
|
Chris@0
|
81 */
|
Chris@0
|
82 protected $eventDispatcher;
|
Chris@0
|
83
|
Chris@0
|
84 /**
|
Chris@0
|
85 * Migration message service.
|
Chris@0
|
86 *
|
Chris@0
|
87 * @todo https://www.drupal.org/node/2822663 Make this protected.
|
Chris@0
|
88 *
|
Chris@0
|
89 * @var \Drupal\migrate\MigrateMessageInterface
|
Chris@0
|
90 */
|
Chris@0
|
91 public $message;
|
Chris@0
|
92
|
Chris@0
|
93 /**
|
Chris@0
|
94 * Constructs a MigrateExecutable and verifies and sets the memory limit.
|
Chris@0
|
95 *
|
Chris@0
|
96 * @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
Chris@0
|
97 * The migration to run.
|
Chris@0
|
98 * @param \Drupal\migrate\MigrateMessageInterface $message
|
Chris@14
|
99 * (optional) The migrate message service.
|
Chris@0
|
100 * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
|
Chris@14
|
101 * (optional) The event dispatcher.
|
Chris@0
|
102 *
|
Chris@0
|
103 * @throws \Drupal\migrate\MigrateException
|
Chris@0
|
104 */
|
Chris@14
|
105 public function __construct(MigrationInterface $migration, MigrateMessageInterface $message = NULL, EventDispatcherInterface $event_dispatcher = NULL) {
|
Chris@0
|
106 $this->migration = $migration;
|
Chris@14
|
107 $this->message = $message ?: new MigrateMessage();
|
Chris@18
|
108 $this->getIdMap()->setMessage($this->message);
|
Chris@0
|
109 $this->eventDispatcher = $event_dispatcher;
|
Chris@0
|
110 // Record the memory limit in bytes
|
Chris@0
|
111 $limit = trim(ini_get('memory_limit'));
|
Chris@0
|
112 if ($limit == '-1') {
|
Chris@0
|
113 $this->memoryLimit = PHP_INT_MAX;
|
Chris@0
|
114 }
|
Chris@0
|
115 else {
|
Chris@0
|
116 $this->memoryLimit = Bytes::toInt($limit);
|
Chris@0
|
117 }
|
Chris@0
|
118 }
|
Chris@0
|
119
|
Chris@0
|
120 /**
|
Chris@0
|
121 * Returns the source.
|
Chris@0
|
122 *
|
Chris@0
|
123 * Makes sure source is initialized based on migration settings.
|
Chris@0
|
124 *
|
Chris@0
|
125 * @return \Drupal\migrate\Plugin\MigrateSourceInterface
|
Chris@0
|
126 * The source.
|
Chris@0
|
127 */
|
Chris@0
|
128 protected function getSource() {
|
Chris@0
|
129 if (!isset($this->source)) {
|
Chris@0
|
130 $this->source = $this->migration->getSourcePlugin();
|
Chris@0
|
131 }
|
Chris@0
|
132 return $this->source;
|
Chris@0
|
133 }
|
Chris@0
|
134
|
Chris@0
|
135 /**
|
Chris@0
|
136 * Gets the event dispatcher.
|
Chris@0
|
137 *
|
Chris@0
|
138 * @return \Symfony\Component\EventDispatcher\EventDispatcherInterface
|
Chris@0
|
139 */
|
Chris@0
|
140 protected function getEventDispatcher() {
|
Chris@0
|
141 if (!$this->eventDispatcher) {
|
Chris@0
|
142 $this->eventDispatcher = \Drupal::service('event_dispatcher');
|
Chris@0
|
143 }
|
Chris@0
|
144 return $this->eventDispatcher;
|
Chris@0
|
145 }
|
Chris@0
|
146
|
Chris@0
|
147 /**
|
Chris@0
|
148 * {@inheritdoc}
|
Chris@0
|
149 */
|
Chris@0
|
150 public function import() {
|
Chris@0
|
151 // Only begin the import operation if the migration is currently idle.
|
Chris@0
|
152 if ($this->migration->getStatus() !== MigrationInterface::STATUS_IDLE) {
|
Chris@0
|
153 $this->message->display($this->t('Migration @id is busy with another operation: @status',
|
Chris@0
|
154 [
|
Chris@0
|
155 '@id' => $this->migration->id(),
|
Chris@0
|
156 '@status' => $this->t($this->migration->getStatusLabel()),
|
Chris@0
|
157 ]), 'error');
|
Chris@0
|
158 return MigrationInterface::RESULT_FAILED;
|
Chris@0
|
159 }
|
Chris@0
|
160 $this->getEventDispatcher()->dispatch(MigrateEvents::PRE_IMPORT, new MigrateImportEvent($this->migration, $this->message));
|
Chris@0
|
161
|
Chris@0
|
162 // Knock off migration if the requirements haven't been met.
|
Chris@0
|
163 try {
|
Chris@0
|
164 $this->migration->checkRequirements();
|
Chris@0
|
165 }
|
Chris@0
|
166 catch (RequirementsException $e) {
|
Chris@0
|
167 $this->message->display(
|
Chris@0
|
168 $this->t(
|
Chris@0
|
169 'Migration @id did not meet the requirements. @message @requirements',
|
Chris@0
|
170 [
|
Chris@0
|
171 '@id' => $this->migration->id(),
|
Chris@0
|
172 '@message' => $e->getMessage(),
|
Chris@0
|
173 '@requirements' => $e->getRequirementsString(),
|
Chris@0
|
174 ]
|
Chris@0
|
175 ),
|
Chris@0
|
176 'error'
|
Chris@0
|
177 );
|
Chris@0
|
178
|
Chris@0
|
179 return MigrationInterface::RESULT_FAILED;
|
Chris@0
|
180 }
|
Chris@0
|
181
|
Chris@0
|
182 $this->migration->setStatus(MigrationInterface::STATUS_IMPORTING);
|
Chris@0
|
183 $return = MigrationInterface::RESULT_COMPLETED;
|
Chris@0
|
184 $source = $this->getSource();
|
Chris@18
|
185 $id_map = $this->getIdMap();
|
Chris@0
|
186
|
Chris@0
|
187 try {
|
Chris@0
|
188 $source->rewind();
|
Chris@0
|
189 }
|
Chris@0
|
190 catch (\Exception $e) {
|
Chris@0
|
191 $this->message->display(
|
Chris@0
|
192 $this->t('Migration failed with source plugin exception: @e', ['@e' => $e->getMessage()]), 'error');
|
Chris@0
|
193 $this->migration->setStatus(MigrationInterface::STATUS_IDLE);
|
Chris@0
|
194 return MigrationInterface::RESULT_FAILED;
|
Chris@0
|
195 }
|
Chris@0
|
196
|
Chris@0
|
197 $destination = $this->migration->getDestinationPlugin();
|
Chris@0
|
198 while ($source->valid()) {
|
Chris@0
|
199 $row = $source->current();
|
Chris@0
|
200 $this->sourceIdValues = $row->getSourceIdValues();
|
Chris@0
|
201
|
Chris@0
|
202 try {
|
Chris@0
|
203 $this->processRow($row);
|
Chris@0
|
204 $save = TRUE;
|
Chris@0
|
205 }
|
Chris@0
|
206 catch (MigrateException $e) {
|
Chris@18
|
207 $this->getIdMap()->saveIdMapping($row, [], $e->getStatus());
|
Chris@0
|
208 $this->saveMessage($e->getMessage(), $e->getLevel());
|
Chris@0
|
209 $save = FALSE;
|
Chris@0
|
210 }
|
Chris@0
|
211 catch (MigrateSkipRowException $e) {
|
Chris@0
|
212 if ($e->getSaveToMap()) {
|
Chris@0
|
213 $id_map->saveIdMapping($row, [], MigrateIdMapInterface::STATUS_IGNORED);
|
Chris@0
|
214 }
|
Chris@0
|
215 if ($message = trim($e->getMessage())) {
|
Chris@0
|
216 $this->saveMessage($message, MigrationInterface::MESSAGE_INFORMATIONAL);
|
Chris@0
|
217 }
|
Chris@0
|
218 $save = FALSE;
|
Chris@0
|
219 }
|
Chris@0
|
220
|
Chris@0
|
221 if ($save) {
|
Chris@0
|
222 try {
|
Chris@0
|
223 $this->getEventDispatcher()->dispatch(MigrateEvents::PRE_ROW_SAVE, new MigratePreRowSaveEvent($this->migration, $this->message, $row));
|
Chris@17
|
224 $destination_ids = $id_map->lookupDestinationIds($this->sourceIdValues);
|
Chris@17
|
225 $destination_id_values = $destination_ids ? reset($destination_ids) : [];
|
Chris@17
|
226 $destination_id_values = $destination->import($row, $destination_id_values);
|
Chris@0
|
227 $this->getEventDispatcher()->dispatch(MigrateEvents::POST_ROW_SAVE, new MigratePostRowSaveEvent($this->migration, $this->message, $row, $destination_id_values));
|
Chris@0
|
228 if ($destination_id_values) {
|
Chris@0
|
229 // We do not save an idMap entry for config.
|
Chris@0
|
230 if ($destination_id_values !== TRUE) {
|
Chris@0
|
231 $id_map->saveIdMapping($row, $destination_id_values, $this->sourceRowStatus, $destination->rollbackAction());
|
Chris@0
|
232 }
|
Chris@0
|
233 }
|
Chris@0
|
234 else {
|
Chris@0
|
235 $id_map->saveIdMapping($row, [], MigrateIdMapInterface::STATUS_FAILED);
|
Chris@0
|
236 if (!$id_map->messageCount()) {
|
Chris@0
|
237 $message = $this->t('New object was not saved, no error provided');
|
Chris@0
|
238 $this->saveMessage($message);
|
Chris@0
|
239 $this->message->display($message);
|
Chris@0
|
240 }
|
Chris@0
|
241 }
|
Chris@0
|
242 }
|
Chris@0
|
243 catch (MigrateException $e) {
|
Chris@18
|
244 $this->getIdMap()->saveIdMapping($row, [], $e->getStatus());
|
Chris@0
|
245 $this->saveMessage($e->getMessage(), $e->getLevel());
|
Chris@0
|
246 }
|
Chris@0
|
247 catch (\Exception $e) {
|
Chris@18
|
248 $this->getIdMap()->saveIdMapping($row, [], MigrateIdMapInterface::STATUS_FAILED);
|
Chris@0
|
249 $this->handleException($e);
|
Chris@0
|
250 }
|
Chris@0
|
251 }
|
Chris@0
|
252
|
Chris@0
|
253 $this->sourceRowStatus = MigrateIdMapInterface::STATUS_IMPORTED;
|
Chris@0
|
254
|
Chris@0
|
255 // Check for memory exhaustion.
|
Chris@0
|
256 if (($return = $this->checkStatus()) != MigrationInterface::RESULT_COMPLETED) {
|
Chris@0
|
257 break;
|
Chris@0
|
258 }
|
Chris@0
|
259
|
Chris@0
|
260 // If anyone has requested we stop, return the requested result.
|
Chris@0
|
261 if ($this->migration->getStatus() == MigrationInterface::STATUS_STOPPING) {
|
Chris@0
|
262 $return = $this->migration->getInterruptionResult();
|
Chris@0
|
263 $this->migration->clearInterruptionResult();
|
Chris@0
|
264 break;
|
Chris@0
|
265 }
|
Chris@0
|
266
|
Chris@0
|
267 try {
|
Chris@0
|
268 $source->next();
|
Chris@0
|
269 }
|
Chris@0
|
270 catch (\Exception $e) {
|
Chris@0
|
271 $this->message->display(
|
Chris@0
|
272 $this->t('Migration failed with source plugin exception: @e',
|
Chris@0
|
273 ['@e' => $e->getMessage()]), 'error');
|
Chris@0
|
274 $this->migration->setStatus(MigrationInterface::STATUS_IDLE);
|
Chris@0
|
275 return MigrationInterface::RESULT_FAILED;
|
Chris@0
|
276 }
|
Chris@0
|
277 }
|
Chris@0
|
278
|
Chris@0
|
279 $this->getEventDispatcher()->dispatch(MigrateEvents::POST_IMPORT, new MigrateImportEvent($this->migration, $this->message));
|
Chris@0
|
280 $this->migration->setStatus(MigrationInterface::STATUS_IDLE);
|
Chris@0
|
281 return $return;
|
Chris@0
|
282 }
|
Chris@0
|
283
|
Chris@0
|
284 /**
|
Chris@0
|
285 * {@inheritdoc}
|
Chris@0
|
286 */
|
Chris@0
|
287 public function rollback() {
|
Chris@0
|
288 // Only begin the rollback operation if the migration is currently idle.
|
Chris@0
|
289 if ($this->migration->getStatus() !== MigrationInterface::STATUS_IDLE) {
|
Chris@0
|
290 $this->message->display($this->t('Migration @id is busy with another operation: @status', ['@id' => $this->migration->id(), '@status' => $this->t($this->migration->getStatusLabel())]), 'error');
|
Chris@0
|
291 return MigrationInterface::RESULT_FAILED;
|
Chris@0
|
292 }
|
Chris@0
|
293
|
Chris@0
|
294 // Announce that rollback is about to happen.
|
Chris@0
|
295 $this->getEventDispatcher()->dispatch(MigrateEvents::PRE_ROLLBACK, new MigrateRollbackEvent($this->migration));
|
Chris@0
|
296
|
Chris@0
|
297 // Optimistically assume things are going to work out; if not, $return will be
|
Chris@0
|
298 // updated to some other status.
|
Chris@0
|
299 $return = MigrationInterface::RESULT_COMPLETED;
|
Chris@0
|
300
|
Chris@0
|
301 $this->migration->setStatus(MigrationInterface::STATUS_ROLLING_BACK);
|
Chris@18
|
302 $id_map = $this->getIdMap();
|
Chris@0
|
303 $destination = $this->migration->getDestinationPlugin();
|
Chris@0
|
304
|
Chris@0
|
305 // Loop through each row in the map, and try to roll it back.
|
Chris@18
|
306 $id_map->rewind();
|
Chris@18
|
307 while ($id_map->valid()) {
|
Chris@0
|
308 $destination_key = $id_map->currentDestination();
|
Chris@0
|
309 if ($destination_key) {
|
Chris@0
|
310 $map_row = $id_map->getRowByDestination($destination_key);
|
Chris@0
|
311 if ($map_row['rollback_action'] == MigrateIdMapInterface::ROLLBACK_DELETE) {
|
Chris@0
|
312 $this->getEventDispatcher()
|
Chris@0
|
313 ->dispatch(MigrateEvents::PRE_ROW_DELETE, new MigrateRowDeleteEvent($this->migration, $destination_key));
|
Chris@0
|
314 $destination->rollback($destination_key);
|
Chris@0
|
315 $this->getEventDispatcher()
|
Chris@0
|
316 ->dispatch(MigrateEvents::POST_ROW_DELETE, new MigrateRowDeleteEvent($this->migration, $destination_key));
|
Chris@0
|
317 }
|
Chris@0
|
318 // We're now done with this row, so remove it from the map.
|
Chris@0
|
319 $id_map->deleteDestination($destination_key);
|
Chris@0
|
320 }
|
Chris@0
|
321 else {
|
Chris@0
|
322 // If there is no destination key the import probably failed and we can
|
Chris@0
|
323 // remove the row without further action.
|
Chris@0
|
324 $source_key = $id_map->currentSource();
|
Chris@0
|
325 $id_map->delete($source_key);
|
Chris@0
|
326 }
|
Chris@18
|
327 $id_map->next();
|
Chris@0
|
328
|
Chris@0
|
329 // Check for memory exhaustion.
|
Chris@0
|
330 if (($return = $this->checkStatus()) != MigrationInterface::RESULT_COMPLETED) {
|
Chris@0
|
331 break;
|
Chris@0
|
332 }
|
Chris@0
|
333
|
Chris@0
|
334 // If anyone has requested we stop, return the requested result.
|
Chris@0
|
335 if ($this->migration->getStatus() == MigrationInterface::STATUS_STOPPING) {
|
Chris@0
|
336 $return = $this->migration->getInterruptionResult();
|
Chris@0
|
337 $this->migration->clearInterruptionResult();
|
Chris@0
|
338 break;
|
Chris@0
|
339 }
|
Chris@0
|
340 }
|
Chris@0
|
341
|
Chris@0
|
342 // Notify modules that rollback attempt was complete.
|
Chris@0
|
343 $this->getEventDispatcher()->dispatch(MigrateEvents::POST_ROLLBACK, new MigrateRollbackEvent($this->migration));
|
Chris@0
|
344 $this->migration->setStatus(MigrationInterface::STATUS_IDLE);
|
Chris@0
|
345
|
Chris@0
|
346 return $return;
|
Chris@0
|
347 }
|
Chris@0
|
348
|
Chris@0
|
349 /**
|
Chris@18
|
350 * Get the ID map from the current migration.
|
Chris@18
|
351 *
|
Chris@18
|
352 * @return \Drupal\migrate\Plugin\MigrateIdMapInterface
|
Chris@18
|
353 * The ID map.
|
Chris@18
|
354 */
|
Chris@18
|
355 protected function getIdMap() {
|
Chris@18
|
356 return $this->migration->getIdMap();
|
Chris@18
|
357 }
|
Chris@18
|
358
|
Chris@18
|
359 /**
|
Chris@0
|
360 * {@inheritdoc}
|
Chris@0
|
361 */
|
Chris@0
|
362 public function processRow(Row $row, array $process = NULL, $value = NULL) {
|
Chris@0
|
363 foreach ($this->migration->getProcessPlugins($process) as $destination => $plugins) {
|
Chris@0
|
364 $multiple = FALSE;
|
Chris@0
|
365 /** @var $plugin \Drupal\migrate\Plugin\MigrateProcessInterface */
|
Chris@0
|
366 foreach ($plugins as $plugin) {
|
Chris@0
|
367 $definition = $plugin->getPluginDefinition();
|
Chris@0
|
368 // Many plugins expect a scalar value but the current value of the
|
Chris@0
|
369 // pipeline might be multiple scalars (this is set by the previous
|
Chris@0
|
370 // plugin) and in this case the current value needs to be iterated
|
Chris@0
|
371 // and each scalar separately transformed.
|
Chris@0
|
372 if ($multiple && !$definition['handle_multiples']) {
|
Chris@0
|
373 $new_value = [];
|
Chris@0
|
374 if (!is_array($value)) {
|
Chris@0
|
375 throw new MigrateException(sprintf('Pipeline failed at %s plugin for destination %s: %s received instead of an array,', $plugin->getPluginId(), $destination, $value));
|
Chris@0
|
376 }
|
Chris@0
|
377 $break = FALSE;
|
Chris@0
|
378 foreach ($value as $scalar_value) {
|
Chris@0
|
379 try {
|
Chris@0
|
380 $new_value[] = $plugin->transform($scalar_value, $this, $row, $destination);
|
Chris@0
|
381 }
|
Chris@0
|
382 catch (MigrateSkipProcessException $e) {
|
Chris@0
|
383 $new_value[] = NULL;
|
Chris@0
|
384 $break = TRUE;
|
Chris@0
|
385 }
|
Chris@0
|
386 }
|
Chris@0
|
387 $value = $new_value;
|
Chris@0
|
388 if ($break) {
|
Chris@0
|
389 break;
|
Chris@0
|
390 }
|
Chris@0
|
391 }
|
Chris@0
|
392 else {
|
Chris@0
|
393 try {
|
Chris@0
|
394 $value = $plugin->transform($value, $this, $row, $destination);
|
Chris@0
|
395 }
|
Chris@0
|
396 catch (MigrateSkipProcessException $e) {
|
Chris@0
|
397 $value = NULL;
|
Chris@0
|
398 break;
|
Chris@0
|
399 }
|
Chris@0
|
400 $multiple = $plugin->multiple();
|
Chris@0
|
401 }
|
Chris@0
|
402 }
|
Chris@0
|
403 // Ensure all values, including nulls, are migrated.
|
Chris@0
|
404 if ($plugins) {
|
Chris@0
|
405 if (isset($value)) {
|
Chris@0
|
406 $row->setDestinationProperty($destination, $value);
|
Chris@0
|
407 }
|
Chris@0
|
408 else {
|
Chris@0
|
409 $row->setEmptyDestinationProperty($destination);
|
Chris@0
|
410 }
|
Chris@0
|
411 }
|
Chris@0
|
412 // Reset the value.
|
Chris@0
|
413 $value = NULL;
|
Chris@0
|
414 }
|
Chris@0
|
415 }
|
Chris@0
|
416
|
Chris@0
|
417 /**
|
Chris@0
|
418 * Fetches the key array for the current source record.
|
Chris@0
|
419 *
|
Chris@0
|
420 * @return array
|
Chris@0
|
421 * The current source IDs.
|
Chris@0
|
422 */
|
Chris@0
|
423 protected function currentSourceIds() {
|
Chris@0
|
424 return $this->getSource()->getCurrentIds();
|
Chris@0
|
425 }
|
Chris@0
|
426
|
Chris@0
|
427 /**
|
Chris@0
|
428 * {@inheritdoc}
|
Chris@0
|
429 */
|
Chris@0
|
430 public function saveMessage($message, $level = MigrationInterface::MESSAGE_ERROR) {
|
Chris@18
|
431 $this->getIdMap()->saveMessage($this->sourceIdValues, $message, $level);
|
Chris@0
|
432 }
|
Chris@0
|
433
|
Chris@0
|
434 /**
|
Chris@0
|
435 * Takes an Exception object and both saves and displays it.
|
Chris@0
|
436 *
|
Chris@0
|
437 * Pulls in additional information on the location triggering the exception.
|
Chris@0
|
438 *
|
Chris@0
|
439 * @param \Exception $exception
|
Chris@0
|
440 * Object representing the exception.
|
Chris@0
|
441 * @param bool $save
|
Chris@0
|
442 * (optional) Whether to save the message in the migration's mapping table.
|
Chris@0
|
443 * Set to FALSE in contexts where this doesn't make sense.
|
Chris@0
|
444 */
|
Chris@0
|
445 protected function handleException(\Exception $exception, $save = TRUE) {
|
Chris@0
|
446 $result = Error::decodeException($exception);
|
Chris@0
|
447 $message = $result['@message'] . ' (' . $result['%file'] . ':' . $result['%line'] . ')';
|
Chris@0
|
448 if ($save) {
|
Chris@0
|
449 $this->saveMessage($message);
|
Chris@0
|
450 }
|
Chris@0
|
451 $this->message->display($message, 'error');
|
Chris@0
|
452 }
|
Chris@0
|
453
|
Chris@0
|
454 /**
|
Chris@0
|
455 * Checks for exceptional conditions, and display feedback.
|
Chris@0
|
456 */
|
Chris@0
|
457 protected function checkStatus() {
|
Chris@0
|
458 if ($this->memoryExceeded()) {
|
Chris@0
|
459 return MigrationInterface::RESULT_INCOMPLETE;
|
Chris@0
|
460 }
|
Chris@0
|
461 return MigrationInterface::RESULT_COMPLETED;
|
Chris@0
|
462 }
|
Chris@0
|
463
|
Chris@0
|
464 /**
|
Chris@0
|
465 * Tests whether we've exceeded the desired memory threshold.
|
Chris@0
|
466 *
|
Chris@0
|
467 * If so, output a message.
|
Chris@0
|
468 *
|
Chris@0
|
469 * @return bool
|
Chris@0
|
470 * TRUE if the threshold is exceeded, otherwise FALSE.
|
Chris@0
|
471 */
|
Chris@0
|
472 protected function memoryExceeded() {
|
Chris@0
|
473 $usage = $this->getMemoryUsage();
|
Chris@0
|
474 $pct_memory = $usage / $this->memoryLimit;
|
Chris@0
|
475 if (!$threshold = $this->memoryThreshold) {
|
Chris@0
|
476 return FALSE;
|
Chris@0
|
477 }
|
Chris@0
|
478 if ($pct_memory > $threshold) {
|
Chris@0
|
479 $this->message->display(
|
Chris@0
|
480 $this->t(
|
Chris@0
|
481 'Memory usage is @usage (@pct% of limit @limit), reclaiming memory.',
|
Chris@0
|
482 [
|
Chris@0
|
483 '@pct' => round($pct_memory * 100),
|
Chris@0
|
484 '@usage' => $this->formatSize($usage),
|
Chris@0
|
485 '@limit' => $this->formatSize($this->memoryLimit),
|
Chris@0
|
486 ]
|
Chris@0
|
487 ),
|
Chris@0
|
488 'warning'
|
Chris@0
|
489 );
|
Chris@0
|
490 $usage = $this->attemptMemoryReclaim();
|
Chris@0
|
491 $pct_memory = $usage / $this->memoryLimit;
|
Chris@0
|
492 // Use a lower threshold - we don't want to be in a situation where we keep
|
Chris@0
|
493 // coming back here and trimming a tiny amount
|
Chris@0
|
494 if ($pct_memory > (0.90 * $threshold)) {
|
Chris@0
|
495 $this->message->display(
|
Chris@0
|
496 $this->t(
|
Chris@0
|
497 'Memory usage is now @usage (@pct% of limit @limit), not enough reclaimed, starting new batch',
|
Chris@0
|
498 [
|
Chris@0
|
499 '@pct' => round($pct_memory * 100),
|
Chris@0
|
500 '@usage' => $this->formatSize($usage),
|
Chris@0
|
501 '@limit' => $this->formatSize($this->memoryLimit),
|
Chris@0
|
502 ]
|
Chris@0
|
503 ),
|
Chris@0
|
504 'warning'
|
Chris@0
|
505 );
|
Chris@0
|
506 return TRUE;
|
Chris@0
|
507 }
|
Chris@0
|
508 else {
|
Chris@0
|
509 $this->message->display(
|
Chris@0
|
510 $this->t(
|
Chris@0
|
511 'Memory usage is now @usage (@pct% of limit @limit), reclaimed enough, continuing',
|
Chris@0
|
512 [
|
Chris@0
|
513 '@pct' => round($pct_memory * 100),
|
Chris@0
|
514 '@usage' => $this->formatSize($usage),
|
Chris@0
|
515 '@limit' => $this->formatSize($this->memoryLimit),
|
Chris@0
|
516 ]
|
Chris@0
|
517 ),
|
Chris@0
|
518 'warning');
|
Chris@0
|
519 return FALSE;
|
Chris@0
|
520 }
|
Chris@0
|
521 }
|
Chris@0
|
522 else {
|
Chris@0
|
523 return FALSE;
|
Chris@0
|
524 }
|
Chris@0
|
525 }
|
Chris@0
|
526
|
Chris@0
|
527 /**
|
Chris@0
|
528 * Returns the memory usage so far.
|
Chris@0
|
529 *
|
Chris@0
|
530 * @return int
|
Chris@0
|
531 * The memory usage.
|
Chris@0
|
532 */
|
Chris@0
|
533 protected function getMemoryUsage() {
|
Chris@0
|
534 return memory_get_usage();
|
Chris@0
|
535 }
|
Chris@0
|
536
|
Chris@0
|
537 /**
|
Chris@0
|
538 * Tries to reclaim memory.
|
Chris@0
|
539 *
|
Chris@0
|
540 * @return int
|
Chris@0
|
541 * The memory usage after reclaim.
|
Chris@0
|
542 */
|
Chris@0
|
543 protected function attemptMemoryReclaim() {
|
Chris@0
|
544 // First, try resetting Drupal's static storage - this frequently releases
|
Chris@0
|
545 // plenty of memory to continue.
|
Chris@0
|
546 drupal_static_reset();
|
Chris@0
|
547
|
Chris@0
|
548 // Entity storage can blow up with caches so clear them out.
|
Chris@0
|
549 $manager = \Drupal::entityManager();
|
Chris@0
|
550 foreach ($manager->getDefinitions() as $id => $definition) {
|
Chris@0
|
551 $manager->getStorage($id)->resetCache();
|
Chris@0
|
552 }
|
Chris@0
|
553
|
Chris@0
|
554 // @TODO: explore resetting the container.
|
Chris@0
|
555
|
Chris@0
|
556 // Run garbage collector to further reduce memory.
|
Chris@0
|
557 gc_collect_cycles();
|
Chris@0
|
558
|
Chris@0
|
559 return memory_get_usage();
|
Chris@0
|
560 }
|
Chris@0
|
561
|
Chris@0
|
562 /**
|
Chris@0
|
563 * Generates a string representation for the given byte count.
|
Chris@0
|
564 *
|
Chris@0
|
565 * @param int $size
|
Chris@0
|
566 * A size in bytes.
|
Chris@0
|
567 *
|
Chris@0
|
568 * @return string
|
Chris@0
|
569 * A translated string representation of the size.
|
Chris@0
|
570 */
|
Chris@0
|
571 protected function formatSize($size) {
|
Chris@0
|
572 return format_size($size);
|
Chris@0
|
573 }
|
Chris@0
|
574
|
Chris@0
|
575 }
|