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

MigrationService::resumeMigration()   B

Complexity

Conditions 8
Paths 6

Size

Total Lines 40
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 36.6188

Importance

Changes 3
Bugs 1 Features 0
Metric Value
cc 8
eloc 20
c 3
b 1
f 0
nc 6
nop 2
dl 0
loc 40
ccs 4
cts 17
cp 0.2353
crap 36.6188
rs 8.4444
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