Passed
Push — main ( 2840e2...f5469f )
by Gaetano
07:36
created

MigrationService::executeMigration()   B

Complexity

Conditions 10
Paths 20

Size

Total Lines 31
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 10.1

Importance

Changes 0
Metric Value
cc 10
eloc 17
nc 20
nop 6
dl 0
loc 31
ccs 9
cts 10
cp 0.9
crap 10.1
rs 7.6666
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Kaliop\eZMigrationBundle\Core;
4
5
use eZ\Publish\API\Repository\Repository;
6
use Kaliop\eZMigrationBundle\API\ReferenceBagInterface;
7
use Kaliop\eZMigrationBundle\API\Value\MigrationStep;
8
use Kaliop\eZMigrationBundle\API\Collection\MigrationDefinitionCollection;
9
use Kaliop\eZMigrationBundle\API\StorageHandlerInterface;
10
use Kaliop\eZMigrationBundle\API\LoaderInterface;
11
use Kaliop\eZMigrationBundle\API\DefinitionParserInterface;
12
use Kaliop\eZMigrationBundle\API\ExecutorInterface;
13
use Kaliop\eZMigrationBundle\API\ContextProviderInterface;
14
use Kaliop\eZMigrationBundle\API\Value\Migration;
15
use Kaliop\eZMigrationBundle\API\Value\MigrationDefinition;
16
use Kaliop\eZMigrationBundle\API\Exception\MigrationStepExecutionException;
17
use Kaliop\eZMigrationBundle\API\Exception\MigrationAbortedException;
18
use Kaliop\eZMigrationBundle\API\Exception\MigrationBundleException;
19
use Kaliop\eZMigrationBundle\API\Exception\MigrationSuspendedException;
20
use Kaliop\eZMigrationBundle\API\Exception\MigrationStepSkippedException;
21
use Kaliop\eZMigrationBundle\API\Exception\AfterMigrationExecutionException;
22
use Kaliop\eZMigrationBundle\API\Event\BeforeStepExecutionEvent;
23
use Kaliop\eZMigrationBundle\API\Event\StepExecutedEvent;
24
use Kaliop\eZMigrationBundle\API\Event\MigrationAbortedEvent;
25
use Kaliop\eZMigrationBundle\API\Event\MigrationSuspendedEvent;
26
use Symfony\Component\Console\Output\OutputInterface;
27
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
28
29
class MigrationService implements ContextProviderInterface
30
{
31
    use RepositoryUserSetterTrait;
0 ignored issues
show
introduced by
The trait Kaliop\eZMigrationBundle...positoryUserSetterTrait requires some properties which are not provided by Kaliop\eZMigrationBundle\Core\MigrationService: $id, $login
Loading history...
32
33
    /**
34
     * The default Admin user Id, used when no Admin user is specified
35
     */
36
    const ADMIN_USER_ID = 14;
37
38
    /**
39
     * @var LoaderInterface $loader
40
     */
41
    protected $loader;
42
    /**
43
     * @var StorageHandlerInterface $storageHandler
44
     */
45
    protected $storageHandler;
46
47
    /** @var DefinitionParserInterface[] $DefinitionParsers */
48
    protected $DefinitionParsers = array();
49
50
    /** @var ExecutorInterface[] $executors */
51
    protected $executors = array();
52
53
    protected $repository;
54
55
    protected $dispatcher;
56
57
    /**
58
     * @var ContextHandler $contextHandler
59
     */
60
    protected $contextHandler;
61
62
    protected $eventPrefix = 'ez_migration.';
63
64
    protected $eventEntity = 'migration';
65
66
    protected $migrationContext = array();
67
68
    /** @var  OutputInterface $output */
69
    protected $output;
70
71
    /** @var ReferenceBagInterface */
72
    protected $referenceResolver;
73
74 149
    public function __construct(LoaderInterface $loader, StorageHandlerInterface $storageHandler, Repository $repository,
75
        EventDispatcherInterface $eventDispatcher, $contextHandler, $referenceResolver)
76
    {
77 149
        $this->loader = $loader;
78 149
        $this->storageHandler = $storageHandler;
79 149
        $this->repository = $repository;
80 149
        $this->dispatcher = $eventDispatcher;
81 149
        $this->contextHandler = $contextHandler;
82 149
        $this->referenceResolver = $referenceResolver;
83 149
    }
84
85 149
    public function addDefinitionParser(DefinitionParserInterface $DefinitionParser)
86
    {
87 149
        $this->DefinitionParsers[] = $DefinitionParser;
88 149
    }
89
90 149
    public function addExecutor(ExecutorInterface $executor)
91
    {
92 149
        foreach ($executor->supportedTypes() as $type) {
93 149
            $this->executors[$type] = $executor;
94
        }
95 149
    }
96
97
    /**
98
     * @param string $type
99
     * @return ExecutorInterface
100
     * @throws \InvalidArgumentException If executor doesn't exist
101
     */
102 38
    public function getExecutor($type)
103
    {
104 38
        if (!isset($this->executors[$type])) {
105
            throw new \InvalidArgumentException("Executor with type '$type' doesn't exist");
106
        }
107
108 38
        return $this->executors[$type];
109
    }
110
111
    /**
112
     * @return string[]
113
     */
114 37
    public function listExecutors()
115
    {
116 37
        return array_keys($this->executors);
117
    }
118
119
    public function setLoader(LoaderInterface $loader)
120
    {
121
        $this->loader = $loader;
122
    }
123
124
    /**
125
     * @todo we could get rid of this by getting $output passed as argument to self::executeMigration. We are not doing
126
     *       that for BC for the moment (self::executeMigration api should be redone, but it is used in WorkfloBundle too)
127
     */
128 98
    public function setOutput(OutputInterface $output)
129
    {
130 98
        $this->output = $output;
131 98
    }
132
133
    /**
134
     * NB: returns UNPARSED definitions
135
     *
136
     * @param string[] $paths
137
     * @return MigrationDefinitionCollection key: migration name, value: migration definition as binary string
138
     * @throws \Exception
139
     */
140 145
    public function getMigrationsDefinitions(array $paths = array())
141
    {
142
        // we try to be flexible in file types we support, and the same time avoid loading all files in a directory
143 145
        $handledDefinitions = array();
144 145
        foreach ($this->loader->listAvailableDefinitions($paths) as $migrationName => $definitionPath) {
145 145
            foreach ($this->DefinitionParsers as $definitionParser) {
146 145
                if ($definitionParser->supports($migrationName)) {
147 145
                    $handledDefinitions[] = $definitionPath;
148
                }
149
            }
150
        }
151
152
        // we can not call loadDefinitions with an empty array using the Filesystem loader, or it will start looking in bundles...
153 145
        if (empty($handledDefinitions) && !empty($paths)) {
154
            return new MigrationDefinitionCollection();
155
        }
156
157 145
        return $this->loader->loadDefinitions($handledDefinitions);
158
    }
159
160
    /**
161
     * Returns the list of all the migrations which where executed or attempted so far
162
     *
163
     * @param int $limit 0 or below will be treated as 'no limit'
164
     * @param int $offset
165
     * @return \Kaliop\eZMigrationBundle\API\Collection\MigrationCollection
166
     */
167 99
    public function getMigrations($limit = null, $offset = null)
168
    {
169 99
        return $this->storageHandler->loadMigrations($limit, $offset);
170
    }
171
172
    /**
173
     * Returns the list of all the migrations in a given status which where executed or attempted so far
174
     *
175
     * @param int $status
176
     * @param int $limit 0 or below will be treated as 'no limit'
177
     * @param int $offset
178
     * @return \Kaliop\eZMigrationBundle\API\Collection\MigrationCollection
179
     */
180 2
    public function getMigrationsByStatus($status, $limit = null, $offset = null)
181
    {
182 2
        return $this->storageHandler->loadMigrationsByStatus($status, $limit, $offset);
183
    }
184
185 47
    public function getMigrationsByPaths(array $paths, $limit = null, $offset = null)
186
    {
187 47
        return $this->storageHandler->loadMigrationsByPaths($paths, $limit, $offset);
0 ignored issues
show
Bug introduced by
The method loadMigrationsByPaths() does not exist on Kaliop\eZMigrationBundle...StorageHandlerInterface. Did you maybe mean loadMigration()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

187
        return $this->storageHandler->/** @scrutinizer ignore-call */ loadMigrationsByPaths($paths, $limit, $offset);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
188
    }
189
190
    /**
191
     * @param string $migrationName
192
     * @return Migration|null
193
     */
194 108
    public function getMigration($migrationName)
195
    {
196 108
        return $this->storageHandler->loadMigration($migrationName);
197
    }
198
199
    /**
200
     * @param MigrationDefinition $migrationDefinition
201
     * @return Migration
202
     */
203 85
    public function addMigration(MigrationDefinition $migrationDefinition)
204
    {
205 85
        return $this->storageHandler->addMigration($migrationDefinition);
206
    }
207
208
    /**
209
     * @param Migration $migration
210
     */
211 108
    public function deleteMigration(Migration $migration)
212
    {
213 108
        return $this->storageHandler->deleteMigration($migration);
214
    }
215
216
    /**
217
     * @param MigrationDefinition $migrationDefinition
218
     * @return Migration
219
     */
220 1
    public function skipMigration(MigrationDefinition $migrationDefinition)
221
    {
222 1
        return $this->storageHandler->skipMigration($migrationDefinition);
223
    }
224
225
    /**
226
     * Not to be called by external users for normal use cases, you should use executeMigration() instead
227
     *
228
     * @param Migration $migration
229
     */
230
    public function endMigration(Migration $migration)
231
    {
232
        return $this->storageHandler->endMigration($migration);
233
    }
234
235
    /**
236
     * Not to be called by external users for normal use cases, you should use executeMigration() instead.
237
     * NB: will act regardless of current migration status.
238
     *
239
     * @param Migration $migration
240
     */
241
    public function failMigration(Migration $migration, $errorMessage)
242
    {
243
        return $this->storageHandler->endMigration(
244
            new Migration(
245
                $migration->name,
246
                $migration->md5,
247
                $migration->path,
248
                $migration->executionDate,
249
                Migration::STATUS_FAILED,
250
                $errorMessage
251
            ),
252
            true
253
        );
254
    }
255
256
    /**
257
     * Parses a migration definition, return a parsed definition.
258
     * If there is a parsing error, the definition status will be updated accordingly
259
     *
260
     * @param MigrationDefinition $migrationDefinition
261
     * @return MigrationDefinition
262
     * @throws \Exception if the migrationDefinition has no suitable parser for its source format
263
     */
264 148
    public function parseMigrationDefinition(MigrationDefinition $migrationDefinition)
265
    {
266 148
        foreach ($this->DefinitionParsers as $definitionParser) {
267 148
            if ($definitionParser->supports($migrationDefinition->name)) {
268
                // parse the source file
269 148
                $migrationDefinition = $definitionParser->parseMigrationDefinition($migrationDefinition);
270
271
                // and make sure we know how to handle all steps
272 148
                foreach ($migrationDefinition->steps as $step) {
273 141
                    if (!isset($this->executors[$step->type])) {
274 2
                        return new MigrationDefinition(
275 2
                            $migrationDefinition->name,
276 2
                            $migrationDefinition->path,
277 2
                            $migrationDefinition->rawDefinition,
278 2
                            MigrationDefinition::STATUS_INVALID,
279 2
                            array(),
280 2
                            "Can not handle migration step of type '{$step->type}'"
281
                        );
282
                    }
283
                }
284
285 148
                return $migrationDefinition;
286
            }
287
        }
288
289
        throw new MigrationBundleException("No parser available to parse migration definition '{$migrationDefinition->name}'");
290
    }
291
292
    /**
293
     * Note: previous API is kept for BC (subclasses reimplementing this method).
294
     * @param MigrationDefinition $migrationDefinition
295
     * @param array $migrationContext Supported array keys are: adminUserLogin, defaultLanguageCode,
296
     *                                forcedReferences, forceExecution, forceSigchildEnabled, userContentType, userGroupContentType,
297
     *                                useTransaction.
298
     *                                Bool usage is deprecated. It was: when set to false, no repo transaction will be used to wrap the migration
299
     * @param string $defaultLanguageCode Deprecated - use $migrationContext['defaultLanguageCode']
300
     * @param string|int|false|null $adminLogin Deprecated - use $migrationContext['adminLogin']; when false, current user is used; when null, hardcoded admin account
301
     * @param bool $force Deprecated - use $migrationContext['forceExecution']; when true, execute a migration if it was already in status DONE or SKIPPED (would throw by default)
302
     * @param bool|null $forceSigchildEnabled Deprecated
303
     * @throws \Exception
304
     *
305 89
     * @todo add support for setting in $migrationContext a userContentType, userGroupContentType ?
306
     * @todo treating a null and false $adminLogin values differently is prone to hard-to-track errors.
307
     *       Shall we use instead -1 to indicate the desire to not-login-as-admin-user-at-all ?
308 89
     */
309 4
    public function executeMigration(MigrationDefinition $migrationDefinition, $migrationContext = true,
310
        $defaultLanguageCode = null, $adminLogin = null, $force = false, $forceSigchildEnabled = null)
311
    {
312 89
        if ($migrationDefinition->status == MigrationDefinition::STATUS_TO_PARSE) {
313
            $migrationDefinition = $this->parseMigrationDefinition($migrationDefinition);
314
        }
315
316
        if ($migrationDefinition->status == MigrationDefinition::STATUS_INVALID) {
317 89
            throw new MigrationBundleException("Can not execute " . $this->getEntityName($migrationDefinition). " '{$migrationDefinition->name}': {$migrationDefinition->parsingError}");
318
        }
319
320 89
        // BC: handling of legacy method call signature
321
        if (!is_array($migrationContext)) {
322 89
            $useTransaction = $migrationContext;
323 60
            $migrationContext = $this->migrationContextFromParameters($defaultLanguageCode, $adminLogin, $forceSigchildEnabled);
0 ignored issues
show
Deprecated Code introduced by
The function Kaliop\eZMigrationBundle...ContextFromParameters() has been deprecated: kept for BC ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

323
            $migrationContext = /** @scrutinizer ignore-deprecated */ $this->migrationContextFromParameters($defaultLanguageCode, $adminLogin, $forceSigchildEnabled);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
324
            $migrationContext['useTransaction'] = $useTransaction;
325
            $migrationContext['forceExecution'] = $force;
326
        } else {
327
            if ($defaultLanguageCode !== null || $adminLogin !== null || $force !== false || $forceSigchildEnabled !== null) {
328
                throw new MigrationBundleException("Invalid call to executeMigration: argument types mismatch");
329
            }
330
        }
331
        if ($this->output) {
332
            $migrationContext['output'] = $this->output;
333
        }
334 89
        $forceExecution = array_key_exists('forceExecution', $migrationContext) ? $migrationContext['forceExecution'] : false;
335
336
        // set migration as begun - has to be in own db transaction
337 89
        $migration = $this->storageHandler->startMigration($migrationDefinition, $forceExecution);
338 15
339
        $this->executeMigrationInner($migration, $migrationDefinition, $migrationContext);
340
    }
341 89
342 89
    /**
343 89
     * Note: previous API is kept for BC (subclasses reimplementing this method).
344
     * @param Migration $migration
345
     * @param MigrationDefinition $migrationDefinition
346
     * @param array $migrationContext
347 89
     * @param int $stepOffset
348 89
     * @param bool $useTransaction Deprecated - replaced by $migrationContext['useTransaction']. When set to false, no repo transaction will be used to wrap the migration
349 89
     * @param string|int|false|null $adminLogin Deprecated - $migrationContext['adminLogin']. Used only for committing db transaction if needed. If false or null, hardcoded admin is used
350
     * @throws \Exception
351
     */
352
    protected function executeMigrationInner(Migration $migration, MigrationDefinition $migrationDefinition,
353 89
        $migrationContext, $stepOffset = 0, $useTransaction = true, $adminLogin = null)
354
    {
355 89
        // BC: handling of legacy method call signature
356
        $useTransaction = array_key_exists('useTransaction', $migrationContext) ? $migrationContext['useTransaction'] : $useTransaction;
357 89
        $adminLogin = array_key_exists('adminLogin', $migrationContext) ? $migrationContext['adminLogin'] : $adminLogin;
358
359
        /// @todo cane we make this validation smarter / move it somewhere else?
360 89
        if (array_key_exists('path', $migrationContext) || array_key_exists('contentTypeIdentifier', $migrationContext) ||
361
            array_key_exists('fieldIdentifier', $migrationContext)) {
362 89
            throw new MigrationBundleException("Invalid call to executeMigrationInner: forbidden elements in migrationContext");
363 89
        }
364
365 89
        if (isset($migrationContext['forcedReferences'])) {
366 89
            foreach ($migrationContext['forcedReferences'] as $name => $value) {
367
                $this->referenceResolver->addReference($name, $value, true);
368
            }
369 89
        }
370
371 63
        if ($useTransaction) {
372 36
            $this->repository->beginTransaction();
373 2
        }
374
375
        $this->migrationContext[$migration->name] = array('context' => $migrationContext);
376 63
        $previousUserId = null;
377
        $steps = array_slice($migrationDefinition->steps->getArrayCopy(), $stepOffset);
378
379 34
        try {
380
381
            $i = $stepOffset+1;
382 4
            $finalStatus = Migration::STATUS_DONE;
383
            $finalMessage = null;
384 4
385 4
            try {
386 30
387
                foreach ($steps as $step) {
388
                    // save enough data in the context to be able to successfully suspend/resume
389 1
                    $this->migrationContext[$migration->name]['step'] = $i;
390
391
                    $step = $this->injectContextIntoStep($step, array_merge($migrationContext, array('step' => $i)));
392 1
393
                    // we validated the fact that we have a good executor at parsing time
394 1
                    $executor = $this->executors[$step->type];
395 1
396
                    $beforeStepExecutionEvent = new BeforeStepExecutionEvent($step, $executor);
397
                    $this->dispatcher->dispatch($this->eventPrefix . 'before_execution', $beforeStepExecutionEvent);
398
                    // allow some sneaky trickery here: event listeners can manipulate 'live' the step definition and the executor
399 60
                    $executor = $beforeStepExecutionEvent->getExecutor();
400
                    $step = $beforeStepExecutionEvent->getStep();
401
402 60
                    try {
403 60
                        $result = $executor->execute($step);
404 60
405 60
                        $this->dispatcher->dispatch($this->eventPrefix . 'step_executed', new StepExecutedEvent($step, $result));
406 60
                    } catch (MigrationStepSkippedException $e) {
407
                        continue;
408
                    }
409
410
                    $i++;
411 60
                }
412
413 14
            } catch (MigrationAbortedException $e) {
414
                // allow a migration step (or events) to abort the migration via a specific exception
415 14
416 60
                $this->dispatcher->dispatch($this->eventPrefix . $this->eventEntity . '_aborted', new MigrationAbortedEvent($step, $e));
417
418
                $finalStatus = $e->getCode();
419 29
                $finalMessage = "Abort in execution of step $i: " . $e->getMessage();
420
            } catch (MigrationSuspendedException $e) {
421
                // allow a migration step (or events) to suspend the migration via a specific exception
422 29
423 29
                $this->dispatcher->dispatch($this->eventPrefix . $this->eventEntity . '_suspended', new MigrationSuspendedEvent($step, $e));
424 29
425
                // let the context handler store our context, along with context data from any other (tagged) service which has some
426 29
                $this->contextHandler->storeCurrentContext($migration->name);
427
428
                $finalStatus = Migration::STATUS_SUSPENDED;
429 1
                $finalMessage = "Suspended in execution of step $i: " . $e->getMessage();
430
            }
431
432
            // in case we have an exception thrown in the commit phase after the last step, make sure we report the correct step
433
            $i--;
434 1
435
            // set migration as done
436
            $this->storageHandler->endMigration(new Migration(
437
                $migration->name,
438
                $migration->md5,
439
                $migration->path,
440
                $migration->executionDate,
441
                $finalStatus,
442
                $finalMessage
443
            ));
444
445
            if ($useTransaction) {
446
                // there might be workflows or other actions happening at commit time that fail if we are not admin
447
                $previousUserId = $this->loginUser($this->getAdminUserIdentifier($adminLogin));
0 ignored issues
show
Bug introduced by
It seems like $adminLogin can also be of type false; however, parameter $adminLogin of Kaliop\eZMigrationBundle...etAdminUserIdentifier() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

447
                $previousUserId = $this->loginUser($this->getAdminUserIdentifier(/** @scrutinizer ignore-type */ $adminLogin));
Loading history...
448
449
                $this->repository->commit();
450
                $this->loginUser($previousUserId);
451
            }
452
453
        } catch (\Exception $e) {
454
            /// @todo shall we emit a signal as well?
455
456
            $errorMessage = $this->getFullExceptionMessage($e) . ' in file ' . $e->getFile() . ' line ' . $e->getLine();
457 29
            $finalStatus = Migration::STATUS_FAILED;
458 29
            $exception = null;
459 29
460 29
            if ($useTransaction) {
461 29
                try {
462 29
                    // cater to the case where the $this->repository->commit() call above throws an exception
463
                    if ($previousUserId) {
464
                        $this->loginUser($previousUserId);
465
                    }
466 29
467
                    // there is no need to become admin here, at least in theory
468
                    $this->repository->rollBack();
469 29
470
                } catch (\Exception $e2) {
471 60
                    // This check is not rock-solid, but at the moment is all we can do to tell apart 2 cases of
472
                    // exceptions originating above: the case where the commit was successful but handling of a commit-queue
473
                    // signal failed, from the case where something failed beforehand.
474
                    // Known cases for signals failing at commit time include fe. https://jira.ez.no/browse/EZP-29333
475
                    if ($previousUserId && $e2->getMessage() == 'There is no active transaction.') {
476
                        // since the migration succeeded and it was committed, no use to mark it as failed...
477
                        $finalStatus = Migration::STATUS_DONE;
478
                        $errorMessage = 'An exception was thrown after committing, in file ' .
479
                            $e->getFile() . ' line ' . $e->getLine() . ': ' . $this->getFullExceptionMessage($e);
480
                        $exception = new AfterMigrationExecutionException($errorMessage, $i, $e);
481 1
                    } else {
482
                        $errorMessage .= '. In addition, an exception was thrown while rolling back, in file ' .
483 1
                            $e2->getFile() . ' line ' . $e2->getLine() . ': ' . $this->getFullExceptionMessage($e2);
484
                    }
485
                }
486
            }
487 1
488 1
            // set migration as failed
489
            // NB: we use the 'force' flag here because we might be catching an exception happened during the call to
490
            // $this->repository->commit() above, in which case the Migration might already be in the DB with a status 'done'
491
            $this->storageHandler->endMigration(
492 1
                new Migration(
493 1
                    $migration->name,
494
                    $migration->md5,
495 1
                    $migration->path,
496 1
                    $migration->executionDate,
497
                    $finalStatus,
498
                    $errorMessage
499
                ),
500
                true
501 1
            );
502
503 1
            throw ($exception ? $exception : new MigrationStepExecutionException($errorMessage, $i, $e));
504 1
        }
505 1
    }
506
507
    /**
508
     * Note: previous API is kept for BC (subclasses reimplementing this method).
509 1
     * @param Migration $migration
510
     * @param array $migrationContext see executeMigration
511
     * @param array $forcedReferences Deprecated - use $migrationContext['forcedReferences']
512 1
     * @throws \Exception
513 1
     */
514
    public function resumeMigration(Migration $migration, $migrationContext = true, array $forcedReferences = array())
515
    {
516
        // BC: handling of legacy method call signature
517
        if (!is_array($migrationContext)) {
518 1
            $migrationContext = array(
519
                'useTransaction' => $migrationContext,
520
                'forcedReferences' => $forcedReferences,
521 1
            );
522
        } else {
523
            if (!is_array($forcedReferences) || count($forcedReferences)) {
0 ignored issues
show
introduced by
The condition is_array($forcedReferences) is always true.
Loading history...
524
                throw new MigrationBundleException("Invalid call to resumeMigration: argument types mismatch");
525 1
            }
526 1
        }
527 1
528
        if ($migration->status != Migration::STATUS_SUSPENDED) {
529
            throw new MigrationBundleException("Can not resume ".$this->getEntityName($migration)." '{$migration->name}': it is not in suspended status");
530
        }
531
532
        $migrationDefinitions = $this->getMigrationsDefinitions(array($migration->path));
533
        if (!count($migrationDefinitions)) {
534
            throw new MigrationBundleException("Can not resume ".$this->getEntityName($migration)." '{$migration->name}': its definition is missing");
535 89
        }
536
537 89
        $defs = $migrationDefinitions->getArrayCopy();
538
        $migrationDefinition = reset($defs);
539 89
540 1
        $migrationDefinition = $this->parseMigrationDefinition($migrationDefinition);
541
        if ($migrationDefinition->status == MigrationDefinition::STATUS_INVALID) {
542
            throw new MigrationBundleException("Can not resume ".$this->getEntityName($migration)." '{$migration->name}': {$migrationDefinition->parsingError}");
543 89
        }
544 1
545
        // restore context
546 89
        $this->contextHandler->restoreCurrentContext($migration->name);
547
        if (!isset($this->migrationContext[$migration->name])) {
548
            throw new MigrationBundleException("Can not resume ".$this->getEntityName($migration)." '{$migration->name}': the stored context is missing");
549
        }
550
        $restoredContext = $this->migrationContext[$migration->name];
551 89
        if (!is_array($restoredContext) || !isset($restoredContext['context']) || !isset($restoredContext['step'])) {
552 85
            throw new MigrationBundleException("Can not resume ".$this->getEntityName($migration)." '{$migration->name}': the stored context is invalid");
553
        }
554
555 89
        // update migration status
556
        $migration = $this->storageHandler->resumeMigration($migration);
557
558 89
        // clean up restored context - ideally it should be in the same db transaction as the line above
559
        $this->contextHandler->deleteContext($migration->name);
560 89
561 89
        // and go
562 89
        // note: we store the current step counting starting at 1, but use offset starting at 0, hence the -1 here
563 89
        $this->executeMigrationInner($migration, $migrationDefinition, array_merge($restoredContext['context'], $migrationContext),
564
            $restoredContext['step'] - 1);
565
    }
566
567
    /**
568
     * @param string $defaultLanguageCode
569
     * @param string|int|false $adminLogin
570
     * @param bool|null $forceSigchildEnabled Doubly Deprecated!
571 14
     * @return array
572
     * @deprecated kept for BC
573 14
     */
574
    protected function migrationContextFromParameters($defaultLanguageCode = null, $adminLogin = null, $forceSigchildEnabled = null)
0 ignored issues
show
Unused Code introduced by
The parameter $forceSigchildEnabled is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

574
    protected function migrationContextFromParameters($defaultLanguageCode = null, $adminLogin = null, /** @scrutinizer ignore-unused */ $forceSigchildEnabled = null)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
575
    {
576
        $properties = array();
577 14
578
        if ($defaultLanguageCode != null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $defaultLanguageCode of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
579
            $properties['defaultLanguageCode'] = $defaultLanguageCode;
580
        }
581
        // nb: other parts of the codebase treat differently a false and null values for $properties['adminUserLogin']
582
        if ($adminLogin !== null) {
583
            $properties['adminUserLogin'] = $adminLogin;
584
        }
585
        //if ($forceSigchildEnabled !== null)
586
        //{
587 29
        //    $properties['forceSigchildEnabled'] = $forceSigchildEnabled;
588
        //}
589 29
590 29
        return $properties;
591 29
    }
592 29
593
    protected function injectContextIntoStep(MigrationStep $step, array $context)
594
    {
595
        return new MigrationStep(
596
            $step->type,
597
            $step->dsl,
598
            array_merge($step->context, $context)
599
        );
600
    }
601
602
    /**
603
     * @param string $adminLogin
604
     * @return int|string
605
     */
606
    protected function getAdminUserIdentifier($adminLogin)
607
    {
608
        if ($adminLogin != null) {
609
            return $adminLogin;
610
        }
611
612
        return self::ADMIN_USER_ID;
613
    }
614
615
    /**
616
     * Turns eZPublish cryptic exceptions into something more palatable for random devs
617
     * @todo should this be moved to a lower layer ?
618
     *
619
     * @param \Exception $e
620
     * @return string
621
     */
622
    protected function getFullExceptionMessage(\Exception $e)
623
    {
624
        $message = $e->getMessage();
625
        if (is_a($e, '\eZ\Publish\API\Repository\Exceptions\ContentTypeFieldDefinitionValidationException') ||
626
            is_a($e, '\eZ\Publish\API\Repository\Exceptions\LimitationValidationException') ||
627
            is_a($e, '\eZ\Publish\Core\Base\Exceptions\ContentFieldValidationException')
628 29
        ) {
629 2
            if (is_a($e, '\eZ\Publish\API\Repository\Exceptions\LimitationValidationException')) {
630
                $errorsArray = $e->getLimitationErrors();
0 ignored issues
show
Bug introduced by
The method getLimitationErrors() does not exist on Exception. It seems like you code against a sub-type of Exception such as eZ\Publish\API\Repositor...tionValidationException. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

630
                /** @scrutinizer ignore-call */ 
631
                $errorsArray = $e->getLimitationErrors();
Loading history...
631
                if ($errorsArray == null) {
632 29
                    return $message;
633
                }
634
            } else if (is_a($e, '\eZ\Publish\Core\Base\Exceptions\ContentFieldValidationException')) {
635
                $errorsArray = array();
636
                foreach ($e->getFieldErrors() as $limitationError) {
0 ignored issues
show
Bug introduced by
The method getFieldErrors() does not exist on Exception. It seems like you code against a sub-type of Exception such as eZ\Publish\API\Repositor...tionValidationException or eZ\Publish\API\Repositor...ieldValidationException. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

636
                foreach ($e->/** @scrutinizer ignore-call */ getFieldErrors() as $limitationError) {
Loading history...
637
                    // we get the 1st language
638
                    $errorsArray[] = reset($limitationError);
639 1
                }
640
            } else {
641 1
                $errorsArray = $e->getFieldErrors();
642
            }
643 1
644
            foreach ($errorsArray as $errors) {
645 1
                // sometimes error arrays are 2-level deep, sometimes 1...
646
                if (!is_array($errors)) {
647
                    $errors = array($errors);
648 1
                }
649
                foreach ($errors as $error) {
650
                    /// @todo find out what is the proper eZ way of getting a translated message for these errors
651
                    $translatableMessage = $error->getTranslatableMessage();
652
                    if (is_a($translatableMessage, '\eZ\Publish\API\Repository\Values\Translation\Plural')) {
653
                        $msgText = $translatableMessage->plural;
654
                    } else {
655 1
                        $msgText = $translatableMessage->message;
656
                    }
657 1
658 1
                    $message .= "\n" . $msgText . " - " . var_export($translatableMessage->values, true);
659 1
                }
660
            }
661 1
        }
662
663
        while (($e = $e->getPrevious()) != null) {
664
            $message .= "\n" . $e->getMessage();
665
        }
666
667
        return $message;
668
    }
669
670
    /**
671
     * @param string $migrationName
672
     * @return array
673
     */
674
    public function getCurrentContext($migrationName)
675
    {
676
        if (!isset($this->migrationContext[$migrationName]))
677
            return null;
678
        $context = $this->migrationContext[$migrationName];
679
        // avoid attempting to store the current outputInterface when saving the context
680
        if (isset($context['output'])) {
681
            unset($context['output']);
682
        }
683
        return $context;
684
    }
685
686
    /**
687
     * This gets called when we call $this->contextHandler->restoreCurrentContext().
688
     * @param string $migrationName
689
     * @param array $context
690
     */
691
    public function restoreContext($migrationName, array $context)
692
    {
693
        $this->migrationContext[$migrationName] = $context;
694
        if ($this->output) {
695
            $this->migrationContext['output'] = $this->output;
696
        }
697
    }
698
699
    protected function getEntityName($migration)
700
    {
701
        $array = explode('\\', get_class($migration));
702
        return strtolower(preg_replace('/Definition$/', '', end($array)));
703
    }
704
}
705