Passed
Push — master ( 563df6...5f9c61 )
by Gaetano
04:05
created

MigrationService::failMigration()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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

186
        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...
187
    }
188
189
    /**
190
     * @param string $migrationName
191
     * @return Migration|null
192
     */
193
    public function getMigration($migrationName)
194
    {
195
        return $this->storageHandler->loadMigration($migrationName);
196
    }
197
198
    /**
199
     * @param MigrationDefinition $migrationDefinition
200
     * @return Migration
201
     */
202
    public function addMigration(MigrationDefinition $migrationDefinition)
203
    {
204
        return $this->storageHandler->addMigration($migrationDefinition);
205
    }
206
207
    /**
208
     * @param Migration $migration
209
     */
210
    public function deleteMigration(Migration $migration)
211
    {
212
        return $this->storageHandler->deleteMigration($migration);
213
    }
214
215
    /**
216
     * @param MigrationDefinition $migrationDefinition
217
     * @return Migration
218
     */
219
    public function skipMigration(MigrationDefinition $migrationDefinition)
220
    {
221
        return $this->storageHandler->skipMigration($migrationDefinition);
222
    }
223
224
    /**
225
     * Not to be called by external users for normal use cases, you should use executeMigration() instead
226
     *
227
     * @param Migration $migration
228
     */
229
    public function endMigration(Migration $migration)
230
    {
231
        return $this->storageHandler->endMigration($migration);
232
    }
233
234
    /**
235
     * Not to be called by external users for normal use cases, you should use executeMigration() instead.
236
     * NB: will act regardless of current migration status.
237
     *
238
     * @param Migration $migration
239
     */
240
    public function failMigration(Migration $migration, $errorMessage)
241
    {
242
        return $this->storageHandler->endMigration(
243
            new Migration(
244
                $migration->name,
245
                $migration->md5,
246
                $migration->path,
247
                $migration->executionDate,
248
                Migration::STATUS_FAILED,
249
                $errorMessage
250
            ),
251
            true
252
        );
253
    }
254
255
    /**
256
     * Parses a migration definition, return a parsed definition.
257
     * If there is a parsing error, the definition status will be updated accordingly
258
     *
259
     * @param MigrationDefinition $migrationDefinition
260
     * @return MigrationDefinition
261
     * @throws \Exception if the migrationDefinition has no suitable parser for its source format
262
     */
263
    public function parseMigrationDefinition(MigrationDefinition $migrationDefinition)
264
    {
265
        foreach ($this->DefinitionParsers as $definitionParser) {
266
            if ($definitionParser->supports($migrationDefinition->name)) {
267
                // parse the source file
268
                $migrationDefinition = $definitionParser->parseMigrationDefinition($migrationDefinition);
269
270
                // and make sure we know how to handle all steps
271
                foreach ($migrationDefinition->steps as $step) {
272
                    if (!isset($this->executors[$step->type])) {
273
                        return new MigrationDefinition(
274
                            $migrationDefinition->name,
275
                            $migrationDefinition->path,
276
                            $migrationDefinition->rawDefinition,
277
                            MigrationDefinition::STATUS_INVALID,
278
                            array(),
279
                            "Can not handle migration step of type '{$step->type}'"
280
                        );
281
                    }
282
                }
283
284
                return $migrationDefinition;
285
            }
286
        }
287
288
        throw new \Exception("No parser available to parse migration definition '{$migrationDefinition->name}'");
289
    }
290
291
    /**
292
     * @param MigrationDefinition $migrationDefinition
293
     * @param bool $useTransaction when set to false, no repo transaction will be used to wrap the migration
294
     * @param string $defaultLanguageCode
295
     * @param string|int|false|null $adminLogin when false, current user is used; when null, hardcoded admin account
296
     * @param bool $force when true, execute a migration if it was already in status DONE or SKIPPED (would throw by default)
297
     * @param bool|null forceSigchildEnabled
0 ignored issues
show
Bug introduced by
The type Kaliop\eZMigrationBundle\Core\forceSigchildEnabled was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
298
     * @throws \Exception
299
     *
300
     * @todo treating a null and false $adminLogin values differently is prone to hard-to-track errors.
301
     *       Shall we use instead -1 to indicate the desire to not-login-as-admin-user-at-all ?
302
     * @todo refactor. There are too many parameters here to add more. Move to a single parameter: array of options or value-object
303
     */
304
    public function executeMigration(MigrationDefinition $migrationDefinition, $useTransaction = true,
305
        $defaultLanguageCode = null, $adminLogin = null, $force = false, $forceSigchildEnabled = null)
306
    {
307
        if ($migrationDefinition->status == MigrationDefinition::STATUS_TO_PARSE) {
308
            $migrationDefinition = $this->parseMigrationDefinition($migrationDefinition);
309
        }
310
311
        if ($migrationDefinition->status == MigrationDefinition::STATUS_INVALID) {
312
            throw new \Exception("Can not execute " . $this->getEntityName($migrationDefinition). " '{$migrationDefinition->name}': {$migrationDefinition->parsingError}");
313
        }
314
315
        /// @todo add support for setting in $migrationContext a userContentType, userGroupContentType ?
316
        $migrationContext = $this->migrationContextFromParameters($defaultLanguageCode, $adminLogin, $forceSigchildEnabled);
317
318
        // set migration as begun - has to be in own db transaction
319
        $migration = $this->storageHandler->startMigration($migrationDefinition, $force);
320
321
        $this->executeMigrationInner($migration, $migrationDefinition, $migrationContext, 0, $useTransaction, $adminLogin);
322
    }
323
324
    /**
325
     * @param Migration $migration
326
     * @param MigrationDefinition $migrationDefinition
327
     * @param array $migrationContext
328
     * @param int $stepOffset
329
     * @param bool $useTransaction when set to false, no repo transaction will be used to wrap the migration
330
     * @param string|int|false|null $adminLogin used only for committing db transaction if needed. If false or null, hardcoded admin is used
331
     * @throws \Exception
332
     */
333
    protected function executeMigrationInner(Migration $migration, MigrationDefinition $migrationDefinition,
334
        $migrationContext, $stepOffset = 0, $useTransaction = true, $adminLogin = null)
335
    {
336
        if ($useTransaction) {
337
            $this->repository->beginTransaction();
338
        }
339
340
        $this->migrationContext[$migration->name] = array('context' => $migrationContext);
341
        $previousUserId = null;
342
        $steps = array_slice($migrationDefinition->steps->getArrayCopy(), $stepOffset);
343
344
        try {
345
346
            $i = $stepOffset+1;
347
            $finalStatus = Migration::STATUS_DONE;
348
            $finalMessage = null;
349
350
            try {
351
352
                foreach ($steps as $step) {
353
                    // save enough data in the context to be able to successfully suspend/resume
354
                    $this->migrationContext[$migration->name]['step'] = $i;
355
356
                    $step = $this->injectContextIntoStep($step, array_merge($migrationContext, array('step' => $i)));
357
358
                    // we validated the fact that we have a good executor at parsing time
359
                    $executor = $this->executors[$step->type];
360
361
                    $beforeStepExecutionEvent = new BeforeStepExecutionEvent($step, $executor);
362
                    $this->dispatcher->dispatch($this->eventPrefix . 'before_execution', $beforeStepExecutionEvent);
363
                    // allow some sneaky trickery here: event listeners can manipulate 'live' the step definition and the executor
364
                    $executor = $beforeStepExecutionEvent->getExecutor();
365
                    $step = $beforeStepExecutionEvent->getStep();
366
367
                    try {
368
                        $result = $executor->execute($step);
369
370
                        $this->dispatcher->dispatch($this->eventPrefix . 'step_executed', new StepExecutedEvent($step, $result));
371
                    } catch (MigrationStepSkippedException $e) {
372
                        continue;
373
                    }
374
375
                    $i++;
376
                }
377
378
            } catch (MigrationAbortedException $e) {
379
                // allow a migration step (or events) to abort the migration via a specific exception
380
381
                $this->dispatcher->dispatch($this->eventPrefix . $this->eventEntity . '_aborted', new MigrationAbortedEvent($step, $e));
382
383
                $finalStatus = $e->getCode();
384
                $finalMessage = "Abort in execution of step $i: " . $e->getMessage();
385
            } catch (MigrationSuspendedException $e) {
386
                // allow a migration step (or events) to suspend the migration via a specific exception
387
388
                $this->dispatcher->dispatch($this->eventPrefix . $this->eventEntity . '_suspended', new MigrationSuspendedEvent($step, $e));
389
390
                // let the context handler store our context, along with context data from any other (tagged) service which has some
391
                $this->contextHandler->storeCurrentContext($migration->name);
392
393
                $finalStatus = Migration::STATUS_SUSPENDED;
394
                $finalMessage = "Suspended in execution of step $i: " . $e->getMessage();
395
            }
396
397
            // in case we have an exception thrown in the commit phase after the last step, make sure we report the correct step
398
            $i--;
399
400
            // set migration as done
401
            $this->storageHandler->endMigration(new Migration(
402
                $migration->name,
403
                $migration->md5,
404
                $migration->path,
405
                $migration->executionDate,
406
                $finalStatus,
407
                $finalMessage
408
            ));
409
410
            if ($useTransaction) {
411
                // there might be workflows or other actions happening at commit time that fail if we are not admin
412
                $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

412
                $previousUserId = $this->loginUser($this->getAdminUserIdentifier(/** @scrutinizer ignore-type */ $adminLogin));
Loading history...
413
414
                $this->repository->commit();
415
                $this->loginUser($previousUserId);
416
            }
417
418
        } catch (\Exception $e) {
419
            /// @todo shall we emit a signal as well?
420
421
            $errorMessage = $this->getFullExceptionMessage($e) . ' in file ' . $e->getFile() . ' line ' . $e->getLine();
422
            $finalStatus = Migration::STATUS_FAILED;
423
            $exception = null;
424
425
            if ($useTransaction) {
426
                try {
427
                    // cater to the case where the $this->repository->commit() call above throws an exception
428
                    if ($previousUserId) {
429
                        $this->loginUser($previousUserId);
430
                    }
431
432
                    // there is no need to become admin here, at least in theory
433
                    $this->repository->rollBack();
434
435
                } catch (\Exception $e2) {
436
                    // This check is not rock-solid, but at the moment is all we can do to tell apart 2 cases of
437
                    // exceptions originating above: the case where the commit was successful but handling of a commit-queue
438
                    // signal failed, from the case where something failed beforehand.
439
                    // Known cases for signals failing at commit time include fe. https://jira.ez.no/browse/EZP-29333
440
                    if ($previousUserId && $e2->getMessage() == 'There is no active transaction.') {
441
                        // since the migration succeeded and it was committed, no use to mark it as failed...
442
                        $finalStatus = Migration::STATUS_DONE;
443
                        $errorMessage = 'An exception was thrown after committing, in file ' .
444
                            $e->getFile() . ' line ' . $e->getLine() . ': ' . $this->getFullExceptionMessage($e);
445
                        $exception = new AfterMigrationExecutionException($errorMessage, $i, $e);
446
                    } else {
447
                        $errorMessage .= '. In addition, an exception was thrown while rolling back, in file ' .
448
                            $e2->getFile() . ' line ' . $e2->getLine() . ': ' . $this->getFullExceptionMessage($e2);
449
                    }
450
                }
451
            }
452
453
            // set migration as failed
454
            // NB: we use the 'force' flag here because we might be catching an exception happened during the call to
455
            // $this->repository->commit() above, in which case the Migration might already be in the DB with a status 'done'
456
            $this->storageHandler->endMigration(
457
                new Migration(
458
                    $migration->name,
459
                    $migration->md5,
460
                    $migration->path,
461
                    $migration->executionDate,
462
                    $finalStatus,
463
                    $errorMessage
464
                ),
465
                true
466
            );
467
468
            throw $exception ? $exception : new MigrationStepExecutionException($errorMessage, $i, $e);
469
        }
470
    }
471
472
    /**
473
     * @param Migration $migration
474
     * @param bool $useTransaction
475
     * @param array $forcedReferences
476
     * @throws \Exception
477
     *
478
     * @todo add support for adminLogin ?
479
     */
480
    public function resumeMigration(Migration $migration, $useTransaction = true, array $forcedReferences = array())
481
    {
482
        if ($migration->status != Migration::STATUS_SUSPENDED) {
483
            throw new \Exception("Can not resume ".$this->getEntityName($migration)." '{$migration->name}': it is not in suspended status");
484
        }
485
486
        $migrationDefinitions = $this->getMigrationsDefinitions(array($migration->path));
487
        if (!count($migrationDefinitions)) {
488
            throw new \Exception("Can not resume ".$this->getEntityName($migration)." '{$migration->name}': its definition is missing");
489
        }
490
491
        $defs = $migrationDefinitions->getArrayCopy();
492
        $migrationDefinition = reset($defs);
493
494
        $migrationDefinition = $this->parseMigrationDefinition($migrationDefinition);
495
        if ($migrationDefinition->status == MigrationDefinition::STATUS_INVALID) {
496
            throw new \Exception("Can not resume ".$this->getEntityName($migration)." '{$migration->name}': {$migrationDefinition->parsingError}");
497
        }
498
499
        // restore context
500
        $this->contextHandler->restoreCurrentContext($migration->name);
501
502
        if ($forcedReferences) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $forcedReferences of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
503
            foreach($forcedReferences as $name => $value) {
504
                $this->referenceResolver->addReference($name, $value, true);
505
            }
506
        }
507
508
        if (!isset($this->migrationContext[$migration->name])) {
509
            throw new \Exception("Can not resume ".$this->getEntityName($migration)." '{$migration->name}': the stored context is missing");
510
        }
511
        $restoredContext = $this->migrationContext[$migration->name];
512
        if (!is_array($restoredContext) || !isset($restoredContext['context']) || !isset($restoredContext['step'] )) {
513
            throw new \Exception("Can not resume ".$this->getEntityName($migration)." '{$migration->name}': the stored context is invalid");
514
        }
515
516
        // update migration status
517
        $migration = $this->storageHandler->resumeMigration($migration);
518
519
        // clean up restored context - ideally it should be in the same db transaction as the line above
520
        $this->contextHandler->deleteContext($migration->name);
521
522
        // and go
523
        // note: we store the current step counting starting at 1, but use offset starting at 0, hence the -1 here
524
        $this->executeMigrationInner($migration, $migrationDefinition, $restoredContext['context'],
525
            $restoredContext['step'] - 1, $useTransaction);
526
    }
527
528
    /**
529
     * @param string $defaultLanguageCode
530
     * @param string|int|false $adminLogin
531
     * @param bool|null $forceSigchildEnabled
532
     * @return array
533
     */
534
    protected function migrationContextFromParameters($defaultLanguageCode = null, $adminLogin = null, $forceSigchildEnabled = null )
535
    {
536
        $properties = array();
537
538
        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...
539
            $properties['defaultLanguageCode'] = $defaultLanguageCode;
540
        }
541
        // nb: other parts of the codebase treat differently a false and null values for $properties['adminUserLogin']
542
        if ($adminLogin !== null) {
543
            $properties['adminUserLogin'] = $adminLogin;
544
        }
545
        if ($forceSigchildEnabled !== null)
546
        {
547
            $properties['forceSigchildEnabled'] = $forceSigchildEnabled;
548
        }
549
550
        if ($this->output) {
551
            $properties['output'] = $this->output;
552
        }
553
554
        return $properties;
555
    }
556
557
    protected function injectContextIntoStep(MigrationStep $step, array $context)
558
    {
559
        return new MigrationStep(
560
            $step->type,
561
            $step->dsl,
562
            array_merge($step->context, $context)
563
        );
564
    }
565
566
    /**
567
     * @param string $adminLogin
568
     * @return int|string
569
     */
570
    protected function getAdminUserIdentifier($adminLogin)
571
    {
572
        if ($adminLogin != null) {
573
            return $adminLogin;
574
        }
575
576
        return self::ADMIN_USER_ID;
577
    }
578
579
    /**
580
     * Turns eZPublish cryptic exceptions into something more palatable for random devs
581
     * @todo should this be moved to a lower layer ?
582
     *
583
     * @param \Exception $e
584
     * @return string
585
     */
586
    protected function getFullExceptionMessage(\Exception $e)
587
    {
588
        $message = $e->getMessage();
589
        if (is_a($e, '\eZ\Publish\API\Repository\Exceptions\ContentTypeFieldDefinitionValidationException') ||
590
            is_a($e, '\eZ\Publish\API\Repository\Exceptions\LimitationValidationException') ||
591
            is_a($e, '\eZ\Publish\Core\Base\Exceptions\ContentFieldValidationException')
592
        ) {
593
            if (is_a($e, '\eZ\Publish\API\Repository\Exceptions\LimitationValidationException')) {
594
                $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

594
                /** @scrutinizer ignore-call */ 
595
                $errorsArray = $e->getLimitationErrors();
Loading history...
595
                if ($errorsArray == null) {
596
                    return $message;
597
                }
598
            } else if (is_a($e, '\eZ\Publish\Core\Base\Exceptions\ContentFieldValidationException')) {
599
                $errorsArray = array();
600
                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

600
                foreach ($e->/** @scrutinizer ignore-call */ getFieldErrors() as $limitationError) {
Loading history...
601
                    // we get the 1st language
602
                    $errorsArray[] = reset($limitationError);
603
                }
604
            } else {
605
                $errorsArray = $e->getFieldErrors();
606
            }
607
608
            foreach ($errorsArray as $errors) {
609
                // sometimes error arrays are 2-level deep, sometimes 1...
610
                if (!is_array($errors)) {
611
                    $errors = array($errors);
612
                }
613
                foreach ($errors as $error) {
614
                    /// @todo find out what is the proper eZ way of getting a translated message for these errors
615
                    $translatableMessage = $error->getTranslatableMessage();
616
                    if (is_a($translatableMessage, '\eZ\Publish\API\Repository\Values\Translation\Plural')) {
617
                        $msgText = $translatableMessage->plural;
618
                    } else {
619
                        $msgText = $translatableMessage->message;
620
                    }
621
622
                    $message .= "\n" . $msgText . " - " . var_export($translatableMessage->values, true);
623
                }
624
            }
625
        }
626
627
        while (($e = $e->getPrevious()) != null) {
628
            $message .= "\n" . $e->getMessage();
629
        }
630
631
        return $message;
632
    }
633
634
    /**
635
     * @param string $migrationName
636
     * @return array
637
     */
638
    public function getCurrentContext($migrationName)
639
    {
640
        if (!isset($this->migrationContext[$migrationName]))
641
            return null;
642
        $context = $this->migrationContext[$migrationName];
643
        // avoid attempting to store the current outputInterface when saving the context
644
        if (isset($context['output'])) {
645
            unset($context['output']);
646
        }
647
        return $context;
648
    }
649
650
    /**
651
     * @param string $migrationName
652
     * @param array $context
653
     */
654
    public function restoreContext($migrationName, array $context)
655
    {
656
        $this->migrationContext[$migrationName] = $context;
657
        if ($this->output) {
658
            $this->migrationContext['output'] = $this->output;
659
        }
660
    }
661
662
    protected function getEntityName($migration)
663
    {
664
        $array = explode('\\', get_class($migration));
665
        return strtolower(preg_replace('/Definition$/', '', end($array)));
666
    }
667
}
668