diff --git a/Core/Executor/LoopExecutor.php b/Core/Executor/LoopExecutor.php new file mode 100644 index 00000000..9b77c36a --- /dev/null +++ b/Core/Executor/LoopExecutor.php @@ -0,0 +1,84 @@ +migrationService = $migrationService; + $this->loopResolver = $loopResolver; + } + + /** + * @param MigrationStep $step + * @return mixed + * @throws \Exception + */ + public function execute(MigrationStep $step) + { + parent::execute($step); + + if (!isset($step->dsl['repeat']) || $step->dsl['repeat'] < 0) { + throw new \Exception("Invalid step definition: missing 'repeat' or not a positive integer"); + } + + if (!isset($step->dsl['steps']) || !is_array($step->dsl['steps'])) { + throw new \Exception("Invalid step definition: missing 'steps' or not an array"); + } + + // no need for a 'mode' for now + /*$action = $step->dsl['mode']; + + if (!in_array($action, $this->supportedActions)) { + throw new \Exception("Invalid step definition: value '$action' is not allowed for 'mode'"); + }*/ + + $this->skipStepIfNeeded($step); + + // before engaging in the loop, check that all steps are valid + $stepExecutors = array(); + foreach ($step->dsl['steps'] as $i => $stepDef) { + $type = $stepDef['type']; + try { + $stepExecutors[$i] = $this->migrationService->getExecutor($type); + } catch (\InvalidArgumentException $e) { + throw new \InvalidArgumentException($e->getMessage() . " in sub-step of a loop step"); + } + } + + $this->loopResolver->beginLoop(); + $result = null; + + // NB: we are *not* firing events for each pass of the loop... it might be worth making that optionally happen ? + for ($i = 0; $i < $step->dsl['repeat']; $i++) { + + $this->loopResolver->loopStep(); + + foreach ($step->dsl['steps'] as $j => $stepDef) { + $type = $stepDef['type']; + unset($stepDef['type']); + $subStep = new MigrationStep($type, $stepDef, array_merge($step->context, array())); + $result = $stepExecutors[$j]->execute($subStep); + } + } + + $this->loopResolver->endLoop(); + return $result; + } + +} diff --git a/Core/ReferenceResolver/LoopResolver.php b/Core/ReferenceResolver/LoopResolver.php new file mode 100644 index 00000000..a811659e --- /dev/null +++ b/Core/ReferenceResolver/LoopResolver.php @@ -0,0 +1,44 @@ +stack[] = 0; + } + + public function endLoop() + { + array_pop($this->stack); + } + + public function loopStep() + { + $idx = count($this->stack) - 1; + $this->stack[$idx] = $this->stack[$idx] + 1; + } + + /** + * @param string $identifier format: 'loop:index', 'loop:depth' + * @return int + * @throws \Exception When trying to retrieve anything else but index and depth + */ + public function getReferenceValue($identifier) + { + switch(substr($identifier, 5)) { + case 'iteration': + return end($this->stack); + case 'depth': + return count($this->stack); + default: + throw new \Exception("Can not resolve loop value '$identifier'"); + } + } + +} \ No newline at end of file diff --git a/Resources/config/services.yml b/Resources/config/services.yml index dea8c23f..db282849 100644 --- a/Resources/config/services.yml +++ b/Resources/config/services.yml @@ -47,6 +47,7 @@ parameters: ez_migration_bundle.executor.file.class: Kaliop\eZMigrationBundle\Core\Executor\FileExecutor ez_migration_bundle.executor.http.class: Kaliop\eZMigrationBundle\Core\Executor\HTTPExecutor + ez_migration_bundle.executor.loop.class: Kaliop\eZMigrationBundle\Core\Executor\LoopExecutor ez_migration_bundle.executor.mail.class: Kaliop\eZMigrationBundle\Core\Executor\MailExecutor ez_migration_bundle.executor.migration.class: Kaliop\eZMigrationBundle\Core\Executor\MigrationExecutor ez_migration_bundle.executor.migration_definition.class: Kaliop\eZMigrationBundle\Core\Executor\MigrationDefinitionExecutor @@ -107,6 +108,7 @@ parameters: ez_migration_bundle.reference_resolver.customreference.class: Kaliop\eZMigrationBundle\Core\ReferenceResolver\CustomReferenceResolver ez_migration_bundle.reference_resolver.content.class: Kaliop\eZMigrationBundle\Core\ReferenceResolver\ContentResolver ez_migration_bundle.reference_resolver.location.class: Kaliop\eZMigrationBundle\Core\ReferenceResolver\LocationResolver + ez_migration_bundle.reference_resolver.loop.class: Kaliop\eZMigrationBundle\Core\ReferenceResolver\LoopResolver ez_migration_bundle.reference_resolver.tag.class: Kaliop\eZMigrationBundle\Core\ReferenceResolver\TagResolver ez_migration_bundle.helper.limitation_converter.class: Kaliop\eZMigrationBundle\Core\Helper\LimitationConverter @@ -216,6 +218,16 @@ services: tags: - { name: ez_migration_bundle.executor } + ez_migration_bundle.executor.loop: + class: '%ez_migration_bundle.executor.loop.class%' + arguments: + - '@ez_migration_bundle.migration_service' + - '@ez_migration_bundle.reference_resolver.loop' + calls: + - ['setReferenceMatcher', ['@ez_migration_bundle.reference_matcher']] + tags: + - { name: ez_migration_bundle.executor } + ez_migration_bundle.executor.mail: class: '%ez_migration_bundle.executor.mail.class%' arguments: @@ -664,6 +676,9 @@ services: arguments: - '@ez_migration_bundle.location_matcher' + ez_migration_bundle.reference_resolver.loop: + class: '%ez_migration_bundle.reference_resolver.loop.class%' + ez_migration_bundle.reference_resolver.tag: class: '%ez_migration_bundle.reference_resolver.tag.class%' arguments: @@ -683,7 +698,7 @@ services: ez_migration_bundle.reference_resolver.customreference.flexible: class: '%ez_migration_bundle.reference_resolver.chain_prefix.class%' arguments: - - [ '@ez_migration_bundle.reference_resolver.customreference.base' ] + - [ '@ez_migration_bundle.reference_resolver.customreference.base', '@ez_migration_bundle.reference_resolver.loop' ] ### misc diff --git a/Resources/doc/DSL/Loops.yml b/Resources/doc/DSL/Loops.yml new file mode 100644 index 00000000..869c6b2f --- /dev/null +++ b/Resources/doc/DSL/Loops.yml @@ -0,0 +1,51 @@ +# Loop steps allow to execute maultiple times in a row (a sequence of) other steps. +# Nested loops are supported. +# NB: step execution events are triggered only for the outhermost loop step + +- + type: loop + repeat: int # number of times to repeat execution of the sequence of sub-steps + steps: # the migration steps that you want to be executed repeatedly + - + type: ... + mode: ... + etc: ... # New references that can be resolved anywhere inside the nested step definitions are `loop:index` + # and `loop:depth`. + # loop:iteration is the counter of the current loop iteration, starting at 1 + # loop:depth is used to tell apart nested loops. I starts at depth 1 + # f.e. you could use the following to create contents with different names: "Article [loop:depth].[loop:iteration]" + - + type: ... + mode: ... + etc: ... + + if: # Optional. If set, the loop will be skipped unless the condition is matched + "reference:_ref_name": # name of a reference to be used for the test + _operator_: value # allowed operators: eq, gt, gte, lt, lte, ne, count, length, regexp + +# Example: how to create a reference that holds a composite index of the current step in nested loops: + +- + type: loop + repeat: 2 + steps: + - + type: reference + mode: set + identifier: loopindex1 + value: "[loop:iteration]" + overwrite: true + - + type: loop + repeat: 2 + steps: + - + type: reference + mode: set + identifier: loopindex2 + value: "[reference:loopindex1].[loop:iteration]" + overwrite: true + - + type: reference + mode: dump + identifier: reference:loopindex2 diff --git a/WHATSNEW.md b/WHATSNEW.md index 37296d4f..1f157066 100644 --- a/WHATSNEW.md +++ b/WHATSNEW.md @@ -28,7 +28,7 @@ Version 4.8 type: reference mode: set identifier: three - value: "[reference:one].[loop:two]" + value: "both [reference:one] and [reference:two]" ``` @@ -44,6 +44,8 @@ Version 4.8 ... ``` +* New: added migration step `loop`. More details in [Resources/doc/DSL/Loops.yml](Resources/doc/DSL/Loops.yml) + Version 4.7 =========== @@ -230,7 +232,7 @@ Version 4.0 RC-3 * New: allow to run migrations without changing into an admin user (only for developers, not yet from the command line) * New: it is now possible to execute external processes as migration steps. - More details in [Resources/doc/DSL/Processes.yml](Resources/doc/DSL/Mails.yml) + More details in [Resources/doc/DSL/Processes.yml](Resources/doc/DSL/Processes.yml) Version 4.0 RC-2