Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like MigrationService often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use MigrationService, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
26 | class MigrationService implements ContextProviderInterface |
||
27 | { |
||
28 | use RepositoryUserSetterTrait; |
||
29 | |||
30 | /** |
||
31 | * The default Admin user Id, used when no Admin user is specified |
||
32 | */ |
||
33 | const ADMIN_USER_ID = 14; |
||
34 | |||
35 | /** |
||
36 | * @var LoaderInterface $loader |
||
37 | */ |
||
38 | protected $loader; |
||
39 | /** |
||
40 | * @var StorageHandlerInterface $storageHandler |
||
41 | */ |
||
42 | protected $storageHandler; |
||
43 | |||
44 | /** @var DefinitionParserInterface[] $DefinitionParsers */ |
||
45 | protected $DefinitionParsers = array(); |
||
46 | |||
47 | /** @var ExecutorInterface[] $executors */ |
||
48 | protected $executors = array(); |
||
49 | |||
50 | protected $repository; |
||
51 | |||
52 | protected $dispatcher; |
||
53 | |||
54 | /** |
||
55 | * @var ContextHandler $contextHandler |
||
56 | */ |
||
57 | protected $contextHandler; |
||
58 | |||
59 | protected $eventPrefix = 'ez_migration.'; |
||
60 | |||
61 | protected $eventEntity = 'migration'; |
||
62 | |||
63 | protected $migrationContext = array(); |
||
64 | |||
65 | 80 | public function __construct(LoaderInterface $loader, StorageHandlerInterface $storageHandler, Repository $repository, |
|
66 | EventDispatcherInterface $eventDispatcher, $contextHandler) |
||
67 | { |
||
68 | 80 | $this->loader = $loader; |
|
69 | 80 | $this->storageHandler = $storageHandler; |
|
70 | 80 | $this->repository = $repository; |
|
71 | 80 | $this->dispatcher = $eventDispatcher; |
|
72 | 80 | $this->contextHandler = $contextHandler; |
|
73 | 80 | } |
|
74 | |||
75 | 80 | public function addDefinitionParser(DefinitionParserInterface $DefinitionParser) |
|
76 | { |
||
77 | 80 | $this->DefinitionParsers[] = $DefinitionParser; |
|
78 | 80 | } |
|
79 | |||
80 | 80 | public function addExecutor(ExecutorInterface $executor) |
|
81 | { |
||
82 | 80 | foreach ($executor->supportedTypes() as $type) { |
|
83 | 80 | $this->executors[$type] = $executor; |
|
84 | } |
||
85 | 80 | } |
|
86 | |||
87 | /** |
||
88 | * @param string $type |
||
89 | * @return ExecutorInterface |
||
90 | * @throws \InvalidArgumentException If executor doesn't exist |
||
91 | */ |
||
92 | 26 | public function getExecutor($type) |
|
93 | { |
||
94 | 26 | if (!isset($this->executors[$type])) { |
|
95 | throw new \InvalidArgumentException("Executor with type '$type' doesn't exist"); |
||
96 | } |
||
97 | |||
98 | 26 | return $this->executors[$type]; |
|
99 | } |
||
100 | |||
101 | /** |
||
102 | * @return string[] |
||
103 | */ |
||
104 | 25 | public function listExecutors() |
|
105 | { |
||
106 | 25 | return array_keys($this->executors); |
|
107 | } |
||
108 | |||
109 | public function setLoader(LoaderInterface $loader) |
||
113 | |||
114 | /** |
||
115 | * NB: returns UNPARSED definitions |
||
116 | * |
||
117 | * @param string[] $paths |
||
118 | * @return MigrationDefinitionCollection key: migration name, value: migration definition as binary string |
||
119 | */ |
||
120 | 78 | public function getMigrationsDefinitions(array $paths = array()) |
|
121 | { |
||
122 | // we try to be flexible in file types we support, and the same time avoid loading all files in a directory |
||
123 | 78 | $handledDefinitions = array(); |
|
124 | 78 | foreach ($this->loader->listAvailableDefinitions($paths) as $migrationName => $definitionPath) { |
|
125 | 78 | foreach ($this->DefinitionParsers as $definitionParser) { |
|
126 | 78 | if ($definitionParser->supports($migrationName)) { |
|
127 | 78 | $handledDefinitions[] = $definitionPath; |
|
128 | } |
||
129 | } |
||
130 | } |
||
131 | |||
132 | // we can not call loadDefinitions with an empty array using the Filesystem loader, or it will start looking in bundles... |
||
133 | 78 | if (empty($handledDefinitions) && !empty($paths)) { |
|
134 | return new MigrationDefinitionCollection(); |
||
135 | } |
||
136 | |||
137 | 78 | return $this->loader->loadDefinitions($handledDefinitions); |
|
138 | } |
||
139 | |||
140 | /** |
||
141 | * Returns the list of all the migrations which where executed or attempted so far |
||
142 | * |
||
143 | * @param int $limit 0 or below will be treated as 'no limit' |
||
144 | * @param int $offset |
||
145 | * @return \Kaliop\eZMigrationBundle\API\Collection\MigrationCollection |
||
146 | */ |
||
147 | 79 | public function getMigrations($limit = null, $offset = null) |
|
148 | { |
||
149 | 79 | return $this->storageHandler->loadMigrations($limit, $offset); |
|
150 | } |
||
151 | |||
152 | /** |
||
153 | * Returns the list of all the migrations in a given status which where executed or attempted so far |
||
154 | * |
||
155 | * @param int $status |
||
156 | * @param int $limit 0 or below will be treated as 'no limit' |
||
157 | * @param int $offset |
||
158 | * @return \Kaliop\eZMigrationBundle\API\Collection\MigrationCollection |
||
159 | */ |
||
160 | 1 | public function getMigrationsByStatus($status, $limit = null, $offset = null) |
|
161 | { |
||
162 | 1 | return $this->storageHandler->loadMigrationsByStatus($status, $limit, $offset); |
|
163 | } |
||
164 | |||
165 | /** |
||
166 | * @param string $migrationName |
||
167 | * @return Migration|null |
||
168 | */ |
||
169 | 49 | public function getMigration($migrationName) |
|
170 | { |
||
171 | 49 | return $this->storageHandler->loadMigration($migrationName); |
|
172 | } |
||
173 | |||
174 | /** |
||
175 | * @param MigrationDefinition $migrationDefinition |
||
176 | * @return Migration |
||
177 | */ |
||
178 | 48 | public function addMigration(MigrationDefinition $migrationDefinition) |
|
179 | { |
||
180 | 48 | return $this->storageHandler->addMigration($migrationDefinition); |
|
181 | } |
||
182 | |||
183 | /** |
||
184 | * @param Migration $migration |
||
185 | */ |
||
186 | 46 | public function deleteMigration(Migration $migration) |
|
187 | { |
||
188 | 46 | return $this->storageHandler->deleteMigration($migration); |
|
189 | } |
||
190 | |||
191 | /** |
||
192 | * @param MigrationDefinition $migrationDefinition |
||
193 | * @return Migration |
||
194 | */ |
||
195 | public function skipMigration(MigrationDefinition $migrationDefinition) |
||
196 | { |
||
197 | return $this->storageHandler->skipMigration($migrationDefinition); |
||
198 | } |
||
199 | |||
200 | /** |
||
201 | * Not to be called by external users for normal use cases, you should use executeMigration() instead |
||
202 | * |
||
203 | * @param Migration $migration |
||
204 | */ |
||
205 | public function endMigration(Migration $migration) |
||
206 | { |
||
207 | return $this->storageHandler->endMigration($migration); |
||
208 | } |
||
209 | |||
210 | /** |
||
211 | * Parses a migration definition, return a parsed definition. |
||
212 | * If there is a parsing error, the definition status will be updated accordingly |
||
213 | * |
||
214 | * @param MigrationDefinition $migrationDefinition |
||
215 | * @return MigrationDefinition |
||
216 | * @throws \Exception if the migrationDefinition has no suitable parser for its source format |
||
217 | */ |
||
218 | 80 | public function parseMigrationDefinition(MigrationDefinition $migrationDefinition) |
|
219 | { |
||
220 | 80 | foreach ($this->DefinitionParsers as $definitionParser) { |
|
221 | 80 | if ($definitionParser->supports($migrationDefinition->name)) { |
|
222 | // parse the source file |
||
223 | 80 | $migrationDefinition = $definitionParser->parseMigrationDefinition($migrationDefinition); |
|
224 | |||
225 | // and make sure we know how to handle all steps |
||
226 | 80 | foreach ($migrationDefinition->steps as $step) { |
|
227 | 71 | if (!isset($this->executors[$step->type])) { |
|
228 | 2 | return new MigrationDefinition( |
|
229 | 2 | $migrationDefinition->name, |
|
230 | 2 | $migrationDefinition->path, |
|
231 | 2 | $migrationDefinition->rawDefinition, |
|
232 | 2 | MigrationDefinition::STATUS_INVALID, |
|
233 | 2 | array(), |
|
234 | 71 | "Can not handle migration step of type '{$step->type}'" |
|
235 | ); |
||
236 | } |
||
237 | } |
||
238 | |||
239 | 80 | return $migrationDefinition; |
|
240 | } |
||
241 | } |
||
242 | |||
243 | throw new \Exception("No parser available to parse migration definition '{$migrationDefinition->name}'"); |
||
244 | } |
||
245 | |||
246 | /** |
||
247 | * @param MigrationDefinition $migrationDefinition |
||
248 | * @param bool $useTransaction when set to false, no repo transaction will be used to wrap the migration |
||
249 | * @param string $defaultLanguageCode |
||
250 | * @param string|int|false|null $adminLogin when false, current user is used; when null, hardcoded admin account |
||
251 | * @throws \Exception |
||
252 | * |
||
253 | * @todo treating a null and false $adminLogin values differently is prone to hard-to-track errors. |
||
254 | * Shall we use instead -1 to indicate the desire to not-login-as-admin-user-at-all ? |
||
255 | */ |
||
256 | 40 | public function executeMigration(MigrationDefinition $migrationDefinition, $useTransaction = true, |
|
257 | $defaultLanguageCode = null, $adminLogin = null) |
||
258 | { |
||
259 | 40 | if ($migrationDefinition->status == MigrationDefinition::STATUS_TO_PARSE) { |
|
260 | 2 | $migrationDefinition = $this->parseMigrationDefinition($migrationDefinition); |
|
261 | } |
||
262 | |||
263 | 40 | View Code Duplication | if ($migrationDefinition->status == MigrationDefinition::STATUS_INVALID) { |
|
|||
264 | throw new \Exception("Can not execute " . $this->getEntityName($migrationDefinition). " '{$migrationDefinition->name}': {$migrationDefinition->parsingError}"); |
||
265 | } |
||
266 | |||
267 | /// @todo add support for setting in $migrationContext a userContentType ? |
||
268 | 40 | $migrationContext = $this->migrationContextFromParameters($defaultLanguageCode, $adminLogin); |
|
269 | |||
270 | // set migration as begun - has to be in own db transaction |
||
271 | 40 | $migration = $this->storageHandler->startMigration($migrationDefinition); |
|
272 | |||
273 | 40 | $this->executeMigrationInner($migration, $migrationDefinition, $migrationContext, 0, $useTransaction, $adminLogin); |
|
274 | 32 | } |
|
275 | |||
276 | /** |
||
277 | * @param Migration $migration |
||
278 | * @param MigrationDefinition $migrationDefinition |
||
279 | * @param array $migrationContext |
||
280 | * @param int $stepOffset |
||
281 | * @param bool $useTransaction when set to false, no repo transaction will be used to wrap the migration |
||
282 | * @param string|int|false|null $adminLogin used only for committing db transaction if needed. If false or null, hardcoded admin is used |
||
283 | * @throws \Exception |
||
284 | */ |
||
285 | 40 | protected function executeMigrationInner(Migration $migration, MigrationDefinition $migrationDefinition, |
|
286 | $migrationContext, $stepOffset = 0, $useTransaction = true, $adminLogin = null) |
||
287 | { |
||
288 | 40 | if ($useTransaction) { |
|
289 | 2 | $this->repository->beginTransaction(); |
|
290 | } |
||
291 | |||
292 | 40 | $this->migrationContext[$migration->name] = array('context' => $migrationContext); |
|
293 | 40 | $previousUserId = null; |
|
294 | 40 | $steps = array_slice($migrationDefinition->steps->getArrayCopy(), $stepOffset); |
|
295 | |||
296 | try { |
||
297 | |||
298 | 40 | $i = $stepOffset+1; |
|
299 | 40 | $finalStatus = Migration::STATUS_DONE; |
|
300 | 40 | $finalMessage = null; |
|
301 | |||
302 | try { |
||
303 | |||
304 | 40 | foreach ($steps as $step) { |
|
305 | // save enough data in the context to be able to successfully suspend/resume |
||
306 | 40 | $this->migrationContext[$migration->name]['step'] = $i; |
|
307 | |||
308 | 40 | $step = $this->injectContextIntoStep($step, $migrationContext); |
|
309 | |||
310 | // we validated the fact that we have a good executor at parsing time |
||
311 | 40 | $executor = $this->executors[$step->type]; |
|
312 | |||
313 | 40 | $beforeStepExecutionEvent = new BeforeStepExecutionEvent($step, $executor); |
|
314 | 40 | $this->dispatcher->dispatch($this->eventPrefix . 'before_execution', $beforeStepExecutionEvent); |
|
315 | // allow some sneaky trickery here: event listeners can manipulate 'live' the step definition and the executor |
||
316 | 40 | $executor = $beforeStepExecutionEvent->getExecutor(); |
|
317 | 40 | $step = $beforeStepExecutionEvent->getStep(); |
|
318 | |||
319 | try { |
||
320 | 40 | $result = $executor->execute($step); |
|
321 | |||
322 | 36 | $this->dispatcher->dispatch($this->eventPrefix . 'step_executed', new StepExecutedEvent($step, $result)); |
|
323 | 13 | } catch (MigrationStepSkippedException $e) { |
|
324 | 1 | continue; |
|
325 | } |
||
326 | |||
327 | 36 | $i++; |
|
328 | } |
||
329 | |||
330 | 12 | } catch (MigrationAbortedException $e) { |
|
331 | // allow a migration step (or events) to abort the migration via a specific exception |
||
332 | |||
333 | 3 | $this->dispatcher->dispatch($this->eventPrefix . $this->eventEntity . '_aborted', new MigrationAbortedEvent($step, $e)); |
|
334 | |||
335 | 3 | $finalStatus = $e->getCode(); |
|
336 | 3 | $finalMessage = "Abort in execution of step $i: " . $e->getMessage(); |
|
337 | 9 | } catch (MigrationSuspendedException $e) { |
|
338 | // allow a migration step (or events) to suspend the migration via a specific exception |
||
339 | |||
340 | 1 | $this->dispatcher->dispatch($this->eventPrefix . $this->eventEntity . '_suspended', new MigrationSuspendedEvent($step, $e)); |
|
341 | |||
342 | // let the context handler store our context, along with context data from any other (tagged) service which has some |
||
343 | 1 | $this->contextHandler->storeCurrentContext($migration->name); |
|
344 | |||
345 | 1 | $finalStatus = Migration::STATUS_SUSPENDED; |
|
346 | 1 | $finalMessage = "Suspended in execution of step $i: " . $e->getMessage(); |
|
347 | } |
||
348 | |||
349 | // in case we have an exception thrown in the commit phase after the last step, make sure we report the correct step |
||
350 | 32 | $i--; |
|
351 | |||
352 | // set migration as done |
||
353 | 32 | $this->storageHandler->endMigration(new Migration( |
|
354 | 32 | $migration->name, |
|
355 | 32 | $migration->md5, |
|
356 | 32 | $migration->path, |
|
357 | 32 | $migration->executionDate, |
|
358 | 32 | $finalStatus, |
|
359 | 32 | $finalMessage |
|
360 | )); |
||
361 | |||
362 | 32 | if ($useTransaction) { |
|
363 | // there might be workflows or other actions happening at commit time that fail if we are not admin |
||
364 | 2 | $previousUserId = $this->loginUser($this->getAdminUserIdentifier($adminLogin)); |
|
365 | |||
366 | 2 | $this->repository->commit(); |
|
367 | 32 | $this->loginUser($previousUserId); |
|
368 | } |
||
369 | |||
370 | 8 | } catch (\Exception $e) { |
|
371 | |||
372 | 8 | $errorMessage = $this->getFullExceptionMessage($e) . ' in file ' . $e->getFile() . ' line ' . $e->getLine(); |
|
373 | 8 | $finalStatus = Migration::STATUS_FAILED; |
|
374 | 8 | $exception = null; |
|
375 | |||
376 | 8 | if ($useTransaction) { |
|
377 | try { |
||
378 | // cater to the case where the $this->repository->commit() call above throws an exception |
||
379 | if ($previousUserId) { |
||
380 | $this->loginUser($previousUserId); |
||
381 | } |
||
382 | |||
383 | // there is no need to become admin here, at least in theory |
||
384 | $this->repository->rollBack(); |
||
385 | |||
386 | } catch (\Exception $e2) { |
||
387 | // This check is not rock-solid, but at the moment is all we can do to tell apart 2 cases of |
||
388 | // exceptions originating above: the case where the commit was successful but handling of a commit-queue |
||
389 | // signal failed, from the case where something failed beforehand. |
||
390 | // Known cases for signals failing at commit time include fe. https://jira.ez.no/browse/EZP-29333 |
||
391 | if ($previousUserId && $e2->getMessage() == 'There is no active transaction.') { |
||
392 | // since the migration succeeded and it was committed, no use to mark it as failed... |
||
393 | $finalStatus = Migration::STATUS_DONE; |
||
394 | $errorMessage = 'An exception was thrown after committing, in file ' . |
||
395 | $e->getFile() . ' line ' . $e->getLine() . ': ' . $this->getFullExceptionMessage($e); |
||
396 | $exception = new AfterMigrationExecutionException($errorMessage, $i, $e); |
||
397 | } else { |
||
398 | $errorMessage .= '. In addition, an exception was thrown while rolling back, in file ' . |
||
399 | $e2->getFile() . ' line ' . $e2->getLine() . ': ' . $this->getFullExceptionMessage($e2); |
||
400 | } |
||
401 | } |
||
402 | } |
||
403 | |||
404 | // set migration as failed |
||
405 | // NB: we use the 'force' flag here because we might be catching an exception happened during the call to |
||
406 | // $this->repository->commit() above, in which case the Migration might already be in the DB with a status 'done' |
||
407 | 8 | $this->storageHandler->endMigration( |
|
408 | 8 | new Migration( |
|
409 | 8 | $migration->name, |
|
410 | 8 | $migration->md5, |
|
411 | 8 | $migration->path, |
|
412 | 8 | $migration->executionDate, |
|
413 | 8 | $finalStatus, |
|
414 | 8 | $errorMessage |
|
415 | ), |
||
416 | 8 | true |
|
417 | ); |
||
418 | |||
419 | 8 | throw $exception ? $exception : new MigrationStepExecutionException($errorMessage, $i, $e); |
|
420 | } |
||
421 | 32 | } |
|
422 | |||
423 | /** |
||
424 | * @param Migration $migration |
||
425 | * @param bool $useTransaction |
||
426 | * @throws \Exception |
||
427 | * |
||
428 | * @todo add support for adminLogin ? |
||
429 | */ |
||
430 | public function resumeMigration(Migration $migration, $useTransaction = true) |
||
431 | { |
||
432 | if ($migration->status != Migration::STATUS_SUSPENDED) { |
||
433 | throw new \Exception("Can not resume ".$this->getEntityName($migration)." '{$migration->name}': it is not in suspended status"); |
||
434 | } |
||
435 | |||
436 | $migrationDefinitions = $this->getMigrationsDefinitions(array($migration->path)); |
||
437 | if (!count($migrationDefinitions)) { |
||
438 | throw new \Exception("Can not resume ".$this->getEntityName($migration)." '{$migration->name}': its definition is missing"); |
||
439 | } |
||
440 | |||
441 | $defs = $migrationDefinitions->getArrayCopy(); |
||
442 | $migrationDefinition = reset($defs); |
||
443 | |||
444 | $migrationDefinition = $this->parseMigrationDefinition($migrationDefinition); |
||
445 | View Code Duplication | if ($migrationDefinition->status == MigrationDefinition::STATUS_INVALID) { |
|
446 | throw new \Exception("Can not resume ".$this->getEntityName($migration)." '{$migration->name}': {$migrationDefinition->parsingError}"); |
||
447 | } |
||
448 | |||
449 | // restore context |
||
450 | $this->contextHandler->restoreCurrentContext($migration->name); |
||
451 | |||
452 | if (!isset($this->migrationContext[$migration->name])) { |
||
453 | throw new \Exception("Can not resume ".$this->getEntityName($migration)." '{$migration->name}': the stored context is missing"); |
||
454 | } |
||
455 | $restoredContext = $this->migrationContext[$migration->name]; |
||
456 | if (!is_array($restoredContext) || !isset($restoredContext['context']) || !isset($restoredContext['step'] )) { |
||
457 | throw new \Exception("Can not resume ".$this->getEntityName($migration)." '{$migration->name}': the stored context is invalid"); |
||
458 | } |
||
459 | |||
460 | // update migration status |
||
461 | $migration = $this->storageHandler->resumeMigration($migration); |
||
462 | |||
463 | // clean up restored context - ideally it should be in the same db transaction as the line above |
||
464 | $this->contextHandler->deleteContext($migration->name); |
||
465 | |||
466 | // and go |
||
467 | // note: we store the current step counting starting at 1, but use offset starting at 0, hence the -1 here |
||
468 | $this->executeMigrationInner($migration, $migrationDefinition, $restoredContext['context'], |
||
469 | $restoredContext['step'] - 1, $useTransaction); |
||
470 | } |
||
471 | |||
472 | /** |
||
473 | * @param string $defaultLanguageCode |
||
474 | * @param string|int|false $adminLogin |
||
475 | * @return array |
||
476 | */ |
||
477 | 40 | protected function migrationContextFromParameters($defaultLanguageCode = null, $adminLogin = null) |
|
478 | { |
||
479 | 40 | $properties = array(); |
|
480 | |||
481 | 40 | if ($defaultLanguageCode != null) { |
|
482 | 1 | $properties['defaultLanguageCode'] = $defaultLanguageCode; |
|
483 | } |
||
484 | // nb: other parts of the codebase treat differently a false and null values for $properties['adminUserLogin'] |
||
485 | 40 | if ($adminLogin !== null) { |
|
486 | $properties['adminUserLogin'] = $adminLogin; |
||
487 | } |
||
488 | |||
489 | 40 | return $properties; |
|
490 | } |
||
491 | |||
492 | 40 | protected function injectContextIntoStep(MigrationStep $step, array $context) |
|
493 | { |
||
494 | 40 | return new MigrationStep( |
|
495 | 40 | $step->type, |
|
496 | 40 | $step->dsl, |
|
497 | 40 | array_merge($step->context, $context) |
|
498 | ); |
||
499 | } |
||
500 | |||
501 | /** |
||
502 | * @param string $adminLogin |
||
503 | * @return int|string |
||
504 | */ |
||
505 | 2 | protected function getAdminUserIdentifier($adminLogin) |
|
506 | { |
||
507 | 2 | if ($adminLogin != null) { |
|
508 | return $adminLogin; |
||
509 | } |
||
510 | |||
511 | 2 | return self::ADMIN_USER_ID; |
|
512 | } |
||
513 | |||
514 | /** |
||
515 | * Turns eZPublish cryptic exceptions into something more palatable for random devs |
||
516 | * @todo should this be moved to a lower layer ? |
||
517 | * |
||
518 | * @param \Exception $e |
||
519 | * @return string |
||
520 | */ |
||
521 | 8 | protected function getFullExceptionMessage(\Exception $e) |
|
522 | { |
||
523 | 8 | $message = $e->getMessage(); |
|
524 | 8 | if (is_a($e, '\eZ\Publish\API\Repository\Exceptions\ContentTypeFieldDefinitionValidationException') || |
|
525 | 8 | is_a($e, '\eZ\Publish\API\Repository\Exceptions\LimitationValidationException') || |
|
526 | 8 | is_a($e, '\eZ\Publish\Core\Base\Exceptions\ContentFieldValidationException') |
|
527 | ) { |
||
528 | if (is_a($e, '\eZ\Publish\API\Repository\Exceptions\LimitationValidationException')) { |
||
529 | $errorsArray = $e->getLimitationErrors(); |
||
530 | if ($errorsArray == null) { |
||
531 | return $message; |
||
532 | } |
||
533 | } else if (is_a($e, '\eZ\Publish\Core\Base\Exceptions\ContentFieldValidationException')) { |
||
534 | $errorsArray = array(); |
||
535 | foreach ($e->getFieldErrors() as $limitationError) { |
||
536 | // we get the 1st language |
||
537 | $errorsArray[] = reset($limitationError); |
||
538 | } |
||
539 | } else { |
||
540 | $errorsArray = $e->getFieldErrors(); |
||
541 | } |
||
542 | |||
543 | foreach ($errorsArray as $errors) { |
||
544 | // sometimes error arrays are 2-level deep, sometimes 1... |
||
545 | if (!is_array($errors)) { |
||
546 | $errors = array($errors); |
||
547 | } |
||
548 | foreach ($errors as $error) { |
||
549 | /// @todo find out what is the proper eZ way of getting a translated message for these errors |
||
550 | $translatableMessage = $error->getTranslatableMessage(); |
||
551 | if (is_a($translatableMessage, '\eZ\Publish\API\Repository\Values\Translation\Plural')) { |
||
552 | $msgText = $translatableMessage->plural; |
||
553 | } else { |
||
554 | $msgText = $translatableMessage->message; |
||
555 | } |
||
556 | |||
557 | $message .= "\n" . $msgText . " - " . var_export($translatableMessage->values, true); |
||
558 | } |
||
559 | } |
||
560 | } |
||
561 | |||
562 | 8 | while (($e = $e->getPrevious()) != null) { |
|
563 | 1 | $message .= "\n" . $e->getMessage(); |
|
564 | } |
||
565 | |||
566 | 8 | return $message; |
|
567 | } |
||
568 | |||
569 | /** |
||
570 | * @param string $migrationName |
||
571 | * @return array |
||
572 | */ |
||
573 | 1 | public function getCurrentContext($migrationName) |
|
574 | { |
||
575 | 1 | return isset($this->migrationContext[$migrationName]) ? $this->migrationContext[$migrationName] : null; |
|
576 | } |
||
577 | |||
578 | /** |
||
579 | * @param string $migrationName |
||
580 | * @param array $context |
||
581 | */ |
||
582 | public function restoreContext($migrationName, array $context) |
||
586 | |||
587 | protected function getEntityName($migration) |
||
588 | { |
||
589 | $array = explode('\\', get_class($migration)); |
||
590 | return strtolower(preg_replace('/Definition$/', '', end($array))); |
||
591 | } |
||
592 | } |
||
593 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.