Passed
Push — master ( 03dfdf...d55943 )
by Gaetano
10:43
created

MigrationService::executeMigrationInner()   F

Complexity

Conditions 14
Paths 2806

Size

Total Lines 136
Code Lines 73

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 52
CRAP Score 15.2919

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 14
eloc 73
c 4
b 0
f 0
nc 2806
nop 6
dl 0
loc 136
ccs 52
cts 64
cp 0.8125
crap 15.2919
rs 2.269

How to fix   Long Method    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 Kaliop\eZMigrationBundle\API\Value\MigrationStep;
6
use Symfony\Component\Console\Output\OutputInterface;
7
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
8
use eZ\Publish\API\Repository\Repository;
9
use Kaliop\eZMigrationBundle\API\Collection\MigrationDefinitionCollection;
10
use Kaliop\eZMigrationBundle\API\StorageHandlerInterface;
11
use Kaliop\eZMigrationBundle\API\LoaderInterface;
12
use Kaliop\eZMigrationBundle\API\DefinitionParserInterface;
13
use Kaliop\eZMigrationBundle\API\ExecutorInterface;
14
use Kaliop\eZMigrationBundle\API\ContextProviderInterface;
15
use Kaliop\eZMigrationBundle\API\Value\Migration;
16
use Kaliop\eZMigrationBundle\API\Value\MigrationDefinition;
17
use Kaliop\eZMigrationBundle\API\Exception\MigrationStepExecutionException;
18
use Kaliop\eZMigrationBundle\API\Exception\MigrationAbortedException;
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
27
class MigrationService implements ContextProviderInterface
28
{
29
    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...
30
31
    /**
32
     * The default Admin user Id, used when no Admin user is specified
33
     */
34
    const ADMIN_USER_ID = 14;
35
36
    /**
37
     * @var LoaderInterface $loader
38
     */
39
    protected $loader;
40
    /**
41
     * @var StorageHandlerInterface $storageHandler
42
     */
43
    protected $storageHandler;
44
45
    /** @var DefinitionParserInterface[] $DefinitionParsers */
46
    protected $DefinitionParsers = array();
47
48
    /** @var ExecutorInterface[] $executors */
49
    protected $executors = array();
50
51
    protected $repository;
52
53
    protected $dispatcher;
54
55
    /**
56
     * @var ContextHandler $contextHandler
57
     */
58
    protected $contextHandler;
59
60
    protected $eventPrefix = 'ez_migration.';
61
62
    protected $eventEntity = 'migration';
63
64
    protected $migrationContext = array();
65 96
66
    /** @var  OutputInterface $output */
67
    protected $output;
68 96
69 96
    public function __construct(LoaderInterface $loader, StorageHandlerInterface $storageHandler, Repository $repository,
70 96
        EventDispatcherInterface $eventDispatcher, $contextHandler)
71 96
    {
72 96
        $this->loader = $loader;
73 96
        $this->storageHandler = $storageHandler;
74
        $this->repository = $repository;
75 96
        $this->dispatcher = $eventDispatcher;
76
        $this->contextHandler = $contextHandler;
77 96
    }
78 96
79
    public function addDefinitionParser(DefinitionParserInterface $DefinitionParser)
80 96
    {
81
        $this->DefinitionParsers[] = $DefinitionParser;
82 96
    }
83 96
84
    public function addExecutor(ExecutorInterface $executor)
85 96
    {
86
        foreach ($executor->supportedTypes() as $type) {
87
            $this->executors[$type] = $executor;
88
        }
89
    }
90
91
    /**
92 29
     * @param string $type
93
     * @return ExecutorInterface
94 29
     * @throws \InvalidArgumentException If executor doesn't exist
95
     */
96
    public function getExecutor($type)
97
    {
98 29
        if (!isset($this->executors[$type])) {
99
            throw new \InvalidArgumentException("Executor with type '$type' doesn't exist");
100
        }
101
102
        return $this->executors[$type];
103
    }
104 28
105
    /**
106 28
     * @return string[]
107
     */
108
    public function listExecutors()
109
    {
110
        return array_keys($this->executors);
111
    }
112
113
    public function setLoader(LoaderInterface $loader)
114
    {
115
        $this->loader = $loader;
116
    }
117
118
    /**
119
     * @todo we could get rid of this by getting $output passed as argument to self::executeMigration. We are not doing
120 94
     *       that for BC for the moment (self::executeMigration api should be redone, but it is used in WorkfloBundle too)
121
     */
122
    public function setOutput(OutputInterface $output)
123 94
    {
124 94
        $this->output = $output;
125 94
    }
126 94
127 94
    /**
128
     * NB: returns UNPARSED definitions
129
     *
130
     * @param string[] $paths
131
     * @return MigrationDefinitionCollection key: migration name, value: migration definition as binary string
132
     * @throws \Exception
133 94
     */
134
    public function getMigrationsDefinitions(array $paths = array())
135
    {
136
        // we try to be flexible in file types we support, and the same time avoid loading all files in a directory
137 94
        $handledDefinitions = array();
138
        foreach ($this->loader->listAvailableDefinitions($paths) as $migrationName => $definitionPath) {
139
            foreach ($this->DefinitionParsers as $definitionParser) {
140
                if ($definitionParser->supports($migrationName)) {
141
                    $handledDefinitions[] = $definitionPath;
142
                }
143
            }
144
        }
145
146
        // we can not call loadDefinitions with an empty array using the Filesystem loader, or it will start looking in bundles...
147 93
        if (empty($handledDefinitions) && !empty($paths)) {
148
            return new MigrationDefinitionCollection();
149 93
        }
150
151
        return $this->loader->loadDefinitions($handledDefinitions);
152
    }
153
154
    /**
155
     * Returns the list of all the migrations which where executed or attempted so far
156
     *
157
     * @param int $limit 0 or below will be treated as 'no limit'
158
     * @param int $offset
159
     * @return \Kaliop\eZMigrationBundle\API\Collection\MigrationCollection
160 1
     */
161
    public function getMigrations($limit = null, $offset = null)
162 1
    {
163
        return $this->storageHandler->loadMigrations($limit, $offset);
164
    }
165
166
    /**
167
     * Returns the list of all the migrations in a given status which where executed or attempted so far
168
     *
169 62
     * @param int $status
170
     * @param int $limit 0 or below will be treated as 'no limit'
171 62
     * @param int $offset
172
     * @return \Kaliop\eZMigrationBundle\API\Collection\MigrationCollection
173
     */
174
    public function getMigrationsByStatus($status, $limit = null, $offset = null)
175
    {
176
        return $this->storageHandler->loadMigrationsByStatus($status, $limit, $offset);
177
    }
178 61
179
    /**
180 61
     * @param string $migrationName
181
     * @return Migration|null
182
     */
183
    public function getMigration($migrationName)
184
    {
185
        return $this->storageHandler->loadMigration($migrationName);
186 58
    }
187
188 58
    /**
189
     * @param MigrationDefinition $migrationDefinition
190
     * @return Migration
191
     */
192
    public function addMigration(MigrationDefinition $migrationDefinition)
193
    {
194
        return $this->storageHandler->addMigration($migrationDefinition);
195
    }
196
197
    /**
198
     * @param Migration $migration
199
     */
200
    public function deleteMigration(Migration $migration)
201
    {
202
        return $this->storageHandler->deleteMigration($migration);
203
    }
204
205
    /**
206
     * @param MigrationDefinition $migrationDefinition
207
     * @return Migration
208
     */
209
    public function skipMigration(MigrationDefinition $migrationDefinition)
210
    {
211
        return $this->storageHandler->skipMigration($migrationDefinition);
212
    }
213
214
    /**
215
     * Not to be called by external users for normal use cases, you should use executeMigration() instead
216
     *
217
     * @param Migration $migration
218 94
     */
219
    public function endMigration(Migration $migration)
220 94
    {
221 94
        return $this->storageHandler->endMigration($migration);
222
    }
223 94
224
    /**
225
     * Parses a migration definition, return a parsed definition.
226 94
     * If there is a parsing error, the definition status will be updated accordingly
227 85
     *
228 2
     * @param MigrationDefinition $migrationDefinition
229 2
     * @return MigrationDefinition
230 2
     * @throws \Exception if the migrationDefinition has no suitable parser for its source format
231 2
     */
232 2
    public function parseMigrationDefinition(MigrationDefinition $migrationDefinition)
233 2
    {
234 85
        foreach ($this->DefinitionParsers as $definitionParser) {
235
            if ($definitionParser->supports($migrationDefinition->name)) {
236
                // parse the source file
237
                $migrationDefinition = $definitionParser->parseMigrationDefinition($migrationDefinition);
238
239 94
                // and make sure we know how to handle all steps
240
                foreach ($migrationDefinition->steps as $step) {
241
                    if (!isset($this->executors[$step->type])) {
242
                        return new MigrationDefinition(
243
                            $migrationDefinition->name,
244
                            $migrationDefinition->path,
245
                            $migrationDefinition->rawDefinition,
246
                            MigrationDefinition::STATUS_INVALID,
247
                            array(),
248
                            "Can not handle migration step of type '{$step->type}'"
249
                        );
250
                    }
251
                }
252
253
                return $migrationDefinition;
254
            }
255
        }
256
257
        throw new \Exception("No parser available to parse migration definition '{$migrationDefinition->name}'");
258
    }
259 49
260
    /**
261
     * @param MigrationDefinition $migrationDefinition
262 49
     * @param bool $useTransaction when set to false, no repo transaction will be used to wrap the migration
263 2
     * @param string $defaultLanguageCode
264
     * @param string|int|false|null $adminLogin when false, current user is used; when null, hardcoded admin account
265
     * @param bool $force when true, execute a migration if it was already in status DONE or SKIPPED (would throw by default)
266 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...
267
     * @throws \Exception
268
     *
269
     * @todo treating a null and false $adminLogin values differently is prone to hard-to-track errors.
270
     *       Shall we use instead -1 to indicate the desire to not-login-as-admin-user-at-all ?
271 49
     * @todo refactor. There are too many parameters here to add more. Move to a single parameter: array of options or value-object
272
     */
273
    public function executeMigration(MigrationDefinition $migrationDefinition, $useTransaction = true,
274 49
        $defaultLanguageCode = null, $adminLogin = null, $force = false, $forceSigchildEnabled = null)
275
    {
276 49
        if ($migrationDefinition->status == MigrationDefinition::STATUS_TO_PARSE) {
277 41
            $migrationDefinition = $this->parseMigrationDefinition($migrationDefinition);
278
        }
279
280
        if ($migrationDefinition->status == MigrationDefinition::STATUS_INVALID) {
281
            throw new \Exception("Can not execute " . $this->getEntityName($migrationDefinition). " '{$migrationDefinition->name}': {$migrationDefinition->parsingError}");
282
        }
283
284
        /// @todo add support for setting in $migrationContext a userContentType, userGroupContentType ?
285
        $migrationContext = $this->migrationContextFromParameters($defaultLanguageCode, $adminLogin, $forceSigchildEnabled);
286
287
        // set migration as begun - has to be in own db transaction
288 49
        $migration = $this->storageHandler->startMigration($migrationDefinition, $force);
289
290
        $this->executeMigrationInner($migration, $migrationDefinition, $migrationContext, 0, $useTransaction, $adminLogin);
291 49
    }
292 9
293
    /**
294
     * @param Migration $migration
295 49
     * @param MigrationDefinition $migrationDefinition
296 49
     * @param array $migrationContext
297 49
     * @param int $stepOffset
298
     * @param bool $useTransaction when set to false, no repo transaction will be used to wrap the migration
299
     * @param string|int|false|null $adminLogin used only for committing db transaction if needed. If false or null, hardcoded admin is used
300
     * @throws \Exception
301 49
     */
302 49
    protected function executeMigrationInner(Migration $migration, MigrationDefinition $migrationDefinition,
303 49
        $migrationContext, $stepOffset = 0, $useTransaction = true, $adminLogin = null)
304
    {
305
        if ($useTransaction) {
306
            $this->repository->beginTransaction();
307 49
        }
308
309 49
        $this->migrationContext[$migration->name] = array('context' => $migrationContext);
310
        $previousUserId = null;
311 49
        $steps = array_slice($migrationDefinition->steps->getArrayCopy(), $stepOffset);
312
313
        try {
314 49
315
            $i = $stepOffset+1;
316 49
            $finalStatus = Migration::STATUS_DONE;
317 49
            $finalMessage = null;
318
319 49
            try {
320 49
321
                foreach ($steps as $step) {
322
                    // save enough data in the context to be able to successfully suspend/resume
323 49
                    $this->migrationContext[$migration->name]['step'] = $i;
324
325 45
                    $step = $this->injectContextIntoStep($step, array_merge($migrationContext, array('step' => $i)));
326 13
327 1
                    // we validated the fact that we have a good executor at parsing time
328
                    $executor = $this->executors[$step->type];
329
330 45
                    $beforeStepExecutionEvent = new BeforeStepExecutionEvent($step, $executor);
331
                    $this->dispatcher->dispatch($this->eventPrefix . 'before_execution', $beforeStepExecutionEvent);
332
                    // allow some sneaky trickery here: event listeners can manipulate 'live' the step definition and the executor
333 12
                    $executor = $beforeStepExecutionEvent->getExecutor();
334
                    $step = $beforeStepExecutionEvent->getStep();
335
336 3
                    try {
337
                        $result = $executor->execute($step);
338 3
339 3
                        $this->dispatcher->dispatch($this->eventPrefix . 'step_executed', new StepExecutedEvent($step, $result));
340 9
                    } catch (MigrationStepSkippedException $e) {
341
                        continue;
342
                    }
343 1
344
                    $i++;
345
                }
346 1
347
            } catch (MigrationAbortedException $e) {
348 1
                // allow a migration step (or events) to abort the migration via a specific exception
349 1
350
                $this->dispatcher->dispatch($this->eventPrefix . $this->eventEntity . '_aborted', new MigrationAbortedEvent($step, $e));
351
352
                $finalStatus = $e->getCode();
353 41
                $finalMessage = "Abort in execution of step $i: " . $e->getMessage();
354
            } catch (MigrationSuspendedException $e) {
355
                // allow a migration step (or events) to suspend the migration via a specific exception
356 41
357 41
                $this->dispatcher->dispatch($this->eventPrefix . $this->eventEntity . '_suspended', new MigrationSuspendedEvent($step, $e));
358 41
359 41
                // let the context handler store our context, along with context data from any other (tagged) service which has some
360 41
                $this->contextHandler->storeCurrentContext($migration->name);
361 41
362 41
                $finalStatus = Migration::STATUS_SUSPENDED;
363
                $finalMessage = "Suspended in execution of step $i: " . $e->getMessage();
364
            }
365 41
366
            // in case we have an exception thrown in the commit phase after the last step, make sure we report the correct step
367 9
            $i--;
368
369 9
            // set migration as done
370 41
            $this->storageHandler->endMigration(new Migration(
371
                $migration->name,
372
                $migration->md5,
373 8
                $migration->path,
374
                $migration->executionDate,
375 8
                $finalStatus,
376 8
                $finalMessage
377 8
            ));
378
379 8
            if ($useTransaction) {
380
                // there might be workflows or other actions happening at commit time that fail if we are not admin
381
                $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

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

556
                /** @scrutinizer ignore-call */ 
557
                $errorsArray = $e->getLimitationErrors();
Loading history...
557
                if ($errorsArray == null) {
558
                    return $message;
559
                }
560
            } else if (is_a($e, '\eZ\Publish\Core\Base\Exceptions\ContentFieldValidationException')) {
561
                $errorsArray = array();
562
                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

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