Completed
Push — master ( 405448...a9f96c )
by Gaetano
05:21
created

MigrationService::addDefinitionParser()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 3
ccs 1
cts 1
cp 1
crap 1
rs 10
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 96
    protected $migrationContext = array();
66
67
    /** @var  OutputInterface $output */
68 96
    protected $output;
69 96
70 96
    /** @var ReferenceBagInterface */
71 96
    protected $referenceResolver;
72 96
73 96
    public function __construct(LoaderInterface $loader, StorageHandlerInterface $storageHandler, Repository $repository,
74
        EventDispatcherInterface $eventDispatcher, $contextHandler, $referenceResolver)
75 96
    {
76
        $this->loader = $loader;
77 96
        $this->storageHandler = $storageHandler;
78 96
        $this->repository = $repository;
79
        $this->dispatcher = $eventDispatcher;
80 96
        $this->contextHandler = $contextHandler;
81
        $this->referenceResolver = $referenceResolver;
82 96
    }
83 96
84
    public function addDefinitionParser(DefinitionParserInterface $DefinitionParser)
85 96
    {
86
        $this->DefinitionParsers[] = $DefinitionParser;
87
    }
88
89
    public function addExecutor(ExecutorInterface $executor)
90
    {
91
        foreach ($executor->supportedTypes() as $type) {
92 29
            $this->executors[$type] = $executor;
93
        }
94 29
    }
95
96
    /**
97
     * @param string $type
98 29
     * @return ExecutorInterface
99
     * @throws \InvalidArgumentException If executor doesn't exist
100
     */
101
    public function getExecutor($type)
102
    {
103
        if (!isset($this->executors[$type])) {
104 28
            throw new \InvalidArgumentException("Executor with type '$type' doesn't exist");
105
        }
106 28
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 94
        $this->loader = $loader;
121
    }
122
123 94
    /**
124 94
     * @todo we could get rid of this by getting $output passed as argument to self::executeMigration. We are not doing
125 94
     *       that for BC for the moment (self::executeMigration api should be redone, but it is used in WorkfloBundle too)
126 94
     */
127 94
    public function setOutput(OutputInterface $output)
128
    {
129
        $this->output = $output;
130
    }
131
132
    /**
133 94
     * NB: returns UNPARSED definitions
134
     *
135
     * @param string[] $paths
136
     * @return MigrationDefinitionCollection key: migration name, value: migration definition as binary string
137 94
     * @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 93
                }
148
            }
149 93
        }
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 1
     * Returns the list of all the migrations which where executed or attempted so far
161
     *
162 1
     * @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 62
    }
170
171 62
    /**
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 61
     */
179
    public function getMigrationsByStatus($status, $limit = null, $offset = null)
180 61
    {
181
        return $this->storageHandler->loadMigrationsByStatus($status, $limit, $offset);
182
    }
183
184
    public function getMigrationsByPaths(array $paths, $limit = null, $offset = null)
185
    {
186 58
        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 58
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 94
     */
219
    public function skipMigration(MigrationDefinition $migrationDefinition)
220 94
    {
221 94
        return $this->storageHandler->skipMigration($migrationDefinition);
222
    }
223 94
224
    /**
225
     * Not to be called by external users for normal use cases, you should use executeMigration() instead
226 94
     *
227 85
     * @param Migration $migration
228 2
     */
229 2
    public function endMigration(Migration $migration)
230 2
    {
231 2
        return $this->storageHandler->endMigration($migration);
232 2
    }
233 2
234 85
    /**
235
     * Parses a migration definition, return a parsed definition.
236
     * If there is a parsing error, the definition status will be updated accordingly
237
     *
238
     * @param MigrationDefinition $migrationDefinition
239 94
     * @return MigrationDefinition
240
     * @throws \Exception if the migrationDefinition has no suitable parser for its source format
241
     */
242
    public function parseMigrationDefinition(MigrationDefinition $migrationDefinition)
243
    {
244
        foreach ($this->DefinitionParsers as $definitionParser) {
245
            if ($definitionParser->supports($migrationDefinition->name)) {
246
                // parse the source file
247
                $migrationDefinition = $definitionParser->parseMigrationDefinition($migrationDefinition);
248
249
                // and make sure we know how to handle all steps
250
                foreach ($migrationDefinition->steps as $step) {
251
                    if (!isset($this->executors[$step->type])) {
252
                        return new MigrationDefinition(
253
                            $migrationDefinition->name,
254
                            $migrationDefinition->path,
255
                            $migrationDefinition->rawDefinition,
256
                            MigrationDefinition::STATUS_INVALID,
257
                            array(),
258
                            "Can not handle migration step of type '{$step->type}'"
259 49
                        );
260
                    }
261
                }
262 49
263 2
                return $migrationDefinition;
264
            }
265
        }
266 49
267
        throw new \Exception("No parser available to parse migration definition '{$migrationDefinition->name}'");
268
    }
269
270
    /**
271 49
     * @param MigrationDefinition $migrationDefinition
272
     * @param bool $useTransaction when set to false, no repo transaction will be used to wrap the migration
273
     * @param string $defaultLanguageCode
274 49
     * @param string|int|false|null $adminLogin when false, current user is used; when null, hardcoded admin account
275
     * @param bool $force when true, execute a migration if it was already in status DONE or SKIPPED (would throw by default)
276 49
     * @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...
277 41
     * @throws \Exception
278
     *
279
     * @todo treating a null and false $adminLogin values differently is prone to hard-to-track errors.
280
     *       Shall we use instead -1 to indicate the desire to not-login-as-admin-user-at-all ?
281
     * @todo refactor. There are too many parameters here to add more. Move to a single parameter: array of options or value-object
282
     */
283
    public function executeMigration(MigrationDefinition $migrationDefinition, $useTransaction = true,
284
        $defaultLanguageCode = null, $adminLogin = null, $force = false, $forceSigchildEnabled = null)
285
    {
286
        if ($migrationDefinition->status == MigrationDefinition::STATUS_TO_PARSE) {
287
            $migrationDefinition = $this->parseMigrationDefinition($migrationDefinition);
288 49
        }
289
290
        if ($migrationDefinition->status == MigrationDefinition::STATUS_INVALID) {
291 49
            throw new \Exception("Can not execute " . $this->getEntityName($migrationDefinition). " '{$migrationDefinition->name}': {$migrationDefinition->parsingError}");
292 9
        }
293
294
        /// @todo add support for setting in $migrationContext a userContentType, userGroupContentType ?
295 49
        $migrationContext = $this->migrationContextFromParameters($defaultLanguageCode, $adminLogin, $forceSigchildEnabled);
296 49
297 49
        // set migration as begun - has to be in own db transaction
298
        $migration = $this->storageHandler->startMigration($migrationDefinition, $force);
299
300
        $this->executeMigrationInner($migration, $migrationDefinition, $migrationContext, 0, $useTransaction, $adminLogin);
301 49
    }
302 49
303 49
    /**
304
     * @param Migration $migration
305
     * @param MigrationDefinition $migrationDefinition
306
     * @param array $migrationContext
307 49
     * @param int $stepOffset
308
     * @param bool $useTransaction when set to false, no repo transaction will be used to wrap the migration
309 49
     * @param string|int|false|null $adminLogin used only for committing db transaction if needed. If false or null, hardcoded admin is used
310
     * @throws \Exception
311 49
     */
312
    protected function executeMigrationInner(Migration $migration, MigrationDefinition $migrationDefinition,
313
        $migrationContext, $stepOffset = 0, $useTransaction = true, $adminLogin = null)
314 49
    {
315
        if ($useTransaction) {
316 49
            $this->repository->beginTransaction();
317 49
        }
318
319 49
        $this->migrationContext[$migration->name] = array('context' => $migrationContext);
320 49
        $previousUserId = null;
321
        $steps = array_slice($migrationDefinition->steps->getArrayCopy(), $stepOffset);
322
323 49
        try {
324
325 45
            $i = $stepOffset+1;
326 13
            $finalStatus = Migration::STATUS_DONE;
327 1
            $finalMessage = null;
328
329
            try {
330 45
331
                foreach ($steps as $step) {
332
                    // save enough data in the context to be able to successfully suspend/resume
333 12
                    $this->migrationContext[$migration->name]['step'] = $i;
334
335
                    $step = $this->injectContextIntoStep($step, array_merge($migrationContext, array('step' => $i)));
336 3
337
                    // we validated the fact that we have a good executor at parsing time
338 3
                    $executor = $this->executors[$step->type];
339 3
340 9
                    $beforeStepExecutionEvent = new BeforeStepExecutionEvent($step, $executor);
341
                    $this->dispatcher->dispatch($this->eventPrefix . 'before_execution', $beforeStepExecutionEvent);
342
                    // allow some sneaky trickery here: event listeners can manipulate 'live' the step definition and the executor
343 1
                    $executor = $beforeStepExecutionEvent->getExecutor();
344
                    $step = $beforeStepExecutionEvent->getStep();
345
346 1
                    try {
347
                        $result = $executor->execute($step);
348 1
349 1
                        $this->dispatcher->dispatch($this->eventPrefix . 'step_executed', new StepExecutedEvent($step, $result));
350
                    } catch (MigrationStepSkippedException $e) {
351
                        continue;
352
                    }
353 41
354
                    $i++;
355
                }
356 41
357 41
            } catch (MigrationAbortedException $e) {
358 41
                // allow a migration step (or events) to abort the migration via a specific exception
359 41
360 41
                $this->dispatcher->dispatch($this->eventPrefix . $this->eventEntity . '_aborted', new MigrationAbortedEvent($step, $e));
361 41
362 41
                $finalStatus = $e->getCode();
363
                $finalMessage = "Abort in execution of step $i: " . $e->getMessage();
364
            } catch (MigrationSuspendedException $e) {
365 41
                // allow a migration step (or events) to suspend the migration via a specific exception
366
367 9
                $this->dispatcher->dispatch($this->eventPrefix . $this->eventEntity . '_suspended', new MigrationSuspendedEvent($step, $e));
368
369 9
                // let the context handler store our context, along with context data from any other (tagged) service which has some
370 41
                $this->contextHandler->storeCurrentContext($migration->name);
371
372
                $finalStatus = Migration::STATUS_SUSPENDED;
373 8
                $finalMessage = "Suspended in execution of step $i: " . $e->getMessage();
374
            }
375 8
376 8
            // in case we have an exception thrown in the commit phase after the last step, make sure we report the correct step
377 8
            $i--;
378
379 8
            // set migration as done
380
            $this->storageHandler->endMigration(new Migration(
381
                $migration->name,
382
                $migration->md5,
383
                $migration->path,
384
                $migration->executionDate,
385
                $finalStatus,
386
                $finalMessage
387
            ));
388
389
            if ($useTransaction) {
390
                // there might be workflows or other actions happening at commit time that fail if we are not admin
391
                $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

391
                $previousUserId = $this->loginUser($this->getAdminUserIdentifier(/** @scrutinizer ignore-type */ $adminLogin));
Loading history...
392
393
                $this->repository->commit();
394
                $this->loginUser($previousUserId);
395
            }
396
397
        } catch (\Exception $e) {
398
            /// @todo shall we emit a signal as well?
399
400
            $errorMessage = $this->getFullExceptionMessage($e) . ' in file ' . $e->getFile() . ' line ' . $e->getLine();
401
            $finalStatus = Migration::STATUS_FAILED;
402
            $exception = null;
403
404
            if ($useTransaction) {
405
                try {
406
                    // cater to the case where the $this->repository->commit() call above throws an exception
407
                    if ($previousUserId) {
408
                        $this->loginUser($previousUserId);
409
                    }
410 8
411 8
                    // there is no need to become admin here, at least in theory
412 8
                    $this->repository->rollBack();
413 8
414 8
                } catch (\Exception $e2) {
415 8
                    // This check is not rock-solid, but at the moment is all we can do to tell apart 2 cases of
416 8
                    // exceptions originating above: the case where the commit was successful but handling of a commit-queue
417 8
                    // signal failed, from the case where something failed beforehand.
418
                    // Known cases for signals failing at commit time include fe. https://jira.ez.no/browse/EZP-29333
419 8
                    if ($previousUserId && $e2->getMessage() == 'There is no active transaction.') {
420
                        // since the migration succeeded and it was committed, no use to mark it as failed...
421
                        $finalStatus = Migration::STATUS_DONE;
422 8
                        $errorMessage = 'An exception was thrown after committing, in file ' .
423
                            $e->getFile() . ' line ' . $e->getLine() . ': ' . $this->getFullExceptionMessage($e);
424 41
                        $exception = new AfterMigrationExecutionException($errorMessage, $i, $e);
425
                    } else {
426
                        $errorMessage .= '. In addition, an exception was thrown while rolling back, in file ' .
427
                            $e2->getFile() . ' line ' . $e2->getLine() . ': ' . $this->getFullExceptionMessage($e2);
428
                    }
429
                }
430
            }
431
432
            // set migration as failed
433
            // NB: we use the 'force' flag here because we might be catching an exception happened during the call to
434
            // $this->repository->commit() above, in which case the Migration might already be in the DB with a status 'done'
435
            $this->storageHandler->endMigration(
436
                new Migration(
437
                    $migration->name,
438
                    $migration->md5,
439
                    $migration->path,
440
                    $migration->executionDate,
441
                    $finalStatus,
442
                    $errorMessage
443
                ),
444
                true
445
            );
446
447
            throw $exception ? $exception : new MigrationStepExecutionException($errorMessage, $i, $e);
448
        }
449
    }
450
451
    /**
452
     * @param Migration $migration
453
     * @param bool $useTransaction
454
     * @param array $forcedReferences
455
     * @throws \Exception
456
     *
457
     * @todo add support for adminLogin ?
458
     */
459
    public function resumeMigration(Migration $migration, $useTransaction = true, array $forcedReferences = array())
460
    {
461
        if ($migration->status != Migration::STATUS_SUSPENDED) {
462
            throw new \Exception("Can not resume ".$this->getEntityName($migration)." '{$migration->name}': it is not in suspended status");
463
        }
464
465
        $migrationDefinitions = $this->getMigrationsDefinitions(array($migration->path));
466
        if (!count($migrationDefinitions)) {
467
            throw new \Exception("Can not resume ".$this->getEntityName($migration)." '{$migration->name}': its definition is missing");
468
        }
469
470
        $defs = $migrationDefinitions->getArrayCopy();
471
        $migrationDefinition = reset($defs);
472
473
        $migrationDefinition = $this->parseMigrationDefinition($migrationDefinition);
474
        if ($migrationDefinition->status == MigrationDefinition::STATUS_INVALID) {
475
            throw new \Exception("Can not resume ".$this->getEntityName($migration)." '{$migration->name}': {$migrationDefinition->parsingError}");
476
        }
477
478
        // restore context
479
        $this->contextHandler->restoreCurrentContext($migration->name);
480
481 49
        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...
482
            foreach($forcedReferences as $name => $value) {
483 49
                $this->referenceResolver->addReference($name, $value, true);
484
            }
485 49
        }
486 1
487
        if (!isset($this->migrationContext[$migration->name])) {
488
            throw new \Exception("Can not resume ".$this->getEntityName($migration)." '{$migration->name}': the stored context is missing");
489 49
        }
490
        $restoredContext = $this->migrationContext[$migration->name];
491
        if (!is_array($restoredContext) || !isset($restoredContext['context']) || !isset($restoredContext['step'] )) {
492 49
            throw new \Exception("Can not resume ".$this->getEntityName($migration)." '{$migration->name}': the stored context is invalid");
493
        }
494
495
        // update migration status
496
        $migration = $this->storageHandler->resumeMigration($migration);
497 49
498
        // clean up restored context - ideally it should be in the same db transaction as the line above
499
        $this->contextHandler->deleteContext($migration->name);
500 49
501
        // and go
502 49
        // note: we store the current step counting starting at 1, but use offset starting at 0, hence the -1 here
503 49
        $this->executeMigrationInner($migration, $migrationDefinition, $restoredContext['context'],
504 49
            $restoredContext['step'] - 1, $useTransaction);
505 49
    }
506
507
    /**
508
     * @param string $defaultLanguageCode
509
     * @param string|int|false $adminLogin
510
     * @param bool|null $forceSigchildEnabled
511
     * @return array
512
     */
513 9
    protected function migrationContextFromParameters($defaultLanguageCode = null, $adminLogin = null, $forceSigchildEnabled = null )
514
    {
515 9
        $properties = array();
516
517
        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...
518
            $properties['defaultLanguageCode'] = $defaultLanguageCode;
519 9
        }
520
        // nb: other parts of the codebase treat differently a false and null values for $properties['adminUserLogin']
521
        if ($adminLogin !== null) {
522
            $properties['adminUserLogin'] = $adminLogin;
523
        }
524
        if ($forceSigchildEnabled !== null)
525
        {
526
            $properties['forceSigchildEnabled'] = $forceSigchildEnabled;
527
        }
528
529 8
        if ($this->output) {
530
            $properties['output'] = $this->output;
531 8
        }
532 8
533 8
        return $properties;
534 8
    }
535
536
    protected function injectContextIntoStep(MigrationStep $step, array $context)
537
    {
538
        return new MigrationStep(
539
            $step->type,
540
            $step->dsl,
541
            array_merge($step->context, $context)
542
        );
543
    }
544
545
    /**
546
     * @param string $adminLogin
547
     * @return int|string
548
     */
549
    protected function getAdminUserIdentifier($adminLogin)
550
    {
551
        if ($adminLogin != null) {
552
            return $adminLogin;
553
        }
554
555
        return self::ADMIN_USER_ID;
556
    }
557
558
    /**
559
     * Turns eZPublish cryptic exceptions into something more palatable for random devs
560
     * @todo should this be moved to a lower layer ?
561
     *
562
     * @param \Exception $e
563
     * @return string
564
     */
565
    protected function getFullExceptionMessage(\Exception $e)
566
    {
567
        $message = $e->getMessage();
568
        if (is_a($e, '\eZ\Publish\API\Repository\Exceptions\ContentTypeFieldDefinitionValidationException') ||
569
            is_a($e, '\eZ\Publish\API\Repository\Exceptions\LimitationValidationException') ||
570 8
            is_a($e, '\eZ\Publish\Core\Base\Exceptions\ContentFieldValidationException')
571 1
        ) {
572
            if (is_a($e, '\eZ\Publish\API\Repository\Exceptions\LimitationValidationException')) {
573
                $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

573
                /** @scrutinizer ignore-call */ 
574
                $errorsArray = $e->getLimitationErrors();
Loading history...
574 8
                if ($errorsArray == null) {
575
                    return $message;
576
                }
577
            } else if (is_a($e, '\eZ\Publish\Core\Base\Exceptions\ContentFieldValidationException')) {
578
                $errorsArray = array();
579
                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

579
                foreach ($e->/** @scrutinizer ignore-call */ getFieldErrors() as $limitationError) {
Loading history...
580
                    // we get the 1st language
581 1
                    $errorsArray[] = reset($limitationError);
582
                }
583 1
            } else {
584
                $errorsArray = $e->getFieldErrors();
585
            }
586
587
            foreach ($errorsArray as $errors) {
588
                // sometimes error arrays are 2-level deep, sometimes 1...
589
                if (!is_array($errors)) {
590
                    $errors = array($errors);
591
                }
592
                foreach ($errors as $error) {
593
                    /// @todo find out what is the proper eZ way of getting a translated message for these errors
594
                    $translatableMessage = $error->getTranslatableMessage();
595
                    if (is_a($translatableMessage, '\eZ\Publish\API\Repository\Values\Translation\Plural')) {
596
                        $msgText = $translatableMessage->plural;
597
                    } else {
598
                        $msgText = $translatableMessage->message;
599
                    }
600
601
                    $message .= "\n" . $msgText . " - " . var_export($translatableMessage->values, true);
602
                }
603
            }
604
        }
605
606
        while (($e = $e->getPrevious()) != null) {
607
            $message .= "\n" . $e->getMessage();
608
        }
609
610
        return $message;
611
    }
612
613
    /**
614
     * @param string $migrationName
615
     * @return array
616
     */
617
    public function getCurrentContext($migrationName)
618
    {
619
        if (!isset($this->migrationContext[$migrationName]))
620
            return null;
621
        $context = $this->migrationContext[$migrationName];
622
        // avoid attempting to store the current outputInterface when saving the context
623
        if (isset($context['output'])) {
624
            unset($context['output']);
625
        }
626
        return $context;
627
    }
628
629
    /**
630
     * @param string $migrationName
631
     * @param array $context
632
     */
633
    public function restoreContext($migrationName, array $context)
634
    {
635
        $this->migrationContext[$migrationName] = $context;
636
        if ($this->output) {
637
            $this->migrationContext['output'] = $this->output;
638
        }
639
    }
640
641
    protected function getEntityName($migration)
642
    {
643
        $array = explode('\\', get_class($migration));
644
        return strtolower(preg_replace('/Definition$/', '', end($array)));
645
    }
646
}
647