Passed
Push — main ( 2840e2...f5469f )
by Gaetano
07:36
created

MigrationDefinitionExecutor::run()   A

Complexity

Conditions 6
Paths 7

Size

Total Lines 29
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 6.9157

Importance

Changes 0
Metric Value
cc 6
eloc 13
nc 7
nop 2
dl 0
loc 29
ccs 12
cts 17
cp 0.7059
crap 6.9157
rs 9.2222
c 0
b 0
f 0
1
<?php
2
3
namespace Kaliop\eZMigrationBundle\Core\Executor;
4
5
use JmesPath\Env as JmesPath;
6
use Kaliop\eZMigrationBundle\API\Exception\InvalidStepDefinitionException;
7
use Kaliop\eZMigrationBundle\API\Exception\MigrationBundleException;
8
use Kaliop\eZMigrationBundle\API\MatcherInterface;
9
use Kaliop\eZMigrationBundle\API\MigrationGeneratorInterface;
10
use Kaliop\eZMigrationBundle\API\ReferenceResolverBagInterface;
11
use Kaliop\eZMigrationBundle\API\Value\MigrationStep;
12
use Symfony\Component\Yaml\Yaml;
13
14
/**
15
 * @property ReferenceResolverBagInterface $referenceResolver
16
 */
17
class MigrationDefinitionExecutor extends AbstractExecutor
18
{
19
    use IgnorableStepExecutorTrait;
0 ignored issues
show
Bug introduced by
The trait Kaliop\eZMigrationBundle...orableStepExecutorTrait requires the property $dsl which is not provided by Kaliop\eZMigrationBundle...ationDefinitionExecutor.
Loading history...
20
    use ReferenceSetterTrait;
21
22
    protected $supportedStepTypes = array('migration_definition');
23
    protected $supportedActions = array('generate', 'save', 'include');
24
25
    /** @var \Kaliop\eZMigrationBundle\Core\MigrationService $migrationService */
26 149
    protected $migrationService;
27
28 149
    public function __construct($migrationService, ReferenceResolverBagInterface $referenceResolver)
29 149
    {
30 149
        $this->migrationService = $migrationService;
31
        $this->referenceResolver = $referenceResolver;
32
    }
33
34
    /**
35
     * @param MigrationStep $step
36
     * @return mixed
37 3
     * @throws \Exception
38
     */
39 3
    public function execute(MigrationStep $step)
40
    {
41 3
        parent::execute($step);
42
43
        if (!isset($step->dsl['mode'])) {
44
            throw new InvalidStepDefinitionException("Invalid step definition: missing 'mode'");
45 3
        }
46
47 3
        $action = $step->dsl['mode'];
48
49
        if (!in_array($action, $this->supportedActions)) {
50
            throw new InvalidStepDefinitionException("Invalid step definition: value '$action' is not allowed for 'mode'");
51 3
        }
52
53 3
        $this->skipStepIfNeeded($step);
54
55
        if ($action === 'include') {
56
            // we can not use a keyword as method name
57
            $action = 'run';
58
        }
59
60
        return $this->$action($step->dsl, $step->context);
61
    }
62
63 3
    public function run($dsl, $context)
64
    {
65 3
        if (!isset($dsl['file'])) {
66
            throw new InvalidStepDefinitionException("Invalid step definition: miss 'file'");
67
        }
68 3
        $fileName = $this->resolveReference($dsl['file']);
69 3
70
        // default format: path is relative to the current mig dir
71
        $realFilePath = dirname($context['path']) . $fileName;
72 3
        // but we support as well absolute paths
73 3
        if (!is_file($realFilePath) && is_file($fileName)) {
74
            $realFilePath = $fileName;
75
        }
76 3
77
        $migrationDefinitions = $this->migrationService->getMigrationsDefinitions(array($realFilePath));
78 3
        if (!count($migrationDefinitions)) {
79 3
            throw new InvalidStepDefinitionException("Invalid step definition: '$fileName' is not a valid migration definition");
80
        }
81
82
        // avoid overwriting the 'current migration definition file path' for the included definition
83 3
        unset($context['path']);
84
85 3
        /// @todo can we could have the included migration's steps be printed as 1.1, 1.2 etc...
86
        /// @todo we could return the result of the included migration's last step
87
        foreach($migrationDefinitions as $migrationDefinition) {
88
            $this->migrationService->executeMigration($migrationDefinition, $context);
89
        }
90 3
91
        return true;
92 3
    }
93 3
94
    /**
95
     * @todo allow to save to disk
96
     * @param array $dsl
97 3
     * @param array $context
98
     * @return array
99 3
     * @throws \Exception
100
     */
101 3
    protected function generate($dsl, $context)
102
    {
103 3
        if (!isset($dsl['migration_type'])) {
104
            throw new InvalidStepDefinitionException("Invalid step definition: miss 'migration_type'");
105
        }
106 3
        $migrationType = $this->resolveReference($dsl['migration_type']);
107
        if (!isset($dsl['migration_mode'])) {
108 3
            throw new InvalidStepDefinitionException("Invalid step definition: miss 'migration_mode'");
109
        }
110
        $migrationMode = $this->resolveReference($dsl['migration_mode']);
111 1
        if (!isset($dsl['match']) || !is_array($dsl['match'])) {
112
            throw new InvalidStepDefinitionException("Invalid step definition: miss 'match' to determine what to generate migration definition for");
113 1
        }
114
        $match = $dsl['match'];
115
116 1
        $executors = $this->getGeneratingExecutors();
117
        if (!in_array($migrationType, $executors)) {
118
            throw new InvalidStepDefinitionException("It is not possible to generate a migration of type '$migrationType': executor not found or not a generator");
119
        }
120 1
        /** @var MigrationGeneratorInterface $executor */
121
        $executor = $this->migrationService->getExecutor($migrationType);
122
123 1
        if (isset($dsl['lang']) && $dsl['lang'] != '') {
124
            $context['defaultLanguageCode'] = $this->resolveReference($dsl['lang']);
125
        }
126 1
127
        // in case the executor does different things based on extra information present in the step definition
128 1
        $context['step'] = $dsl;
129
130 1
        // q: should we use resolveReferenceRecursively for $match['value']?
131
        $matchCondition = array($this->resolveReference($match['type']) => $this->resolveReference($match['value']));
132
        if (isset($match['except']) && $match['except']) {
133
            $matchCondition = array(MatcherInterface::MATCH_NOT => $matchCondition);
134
        }
135 1
136
        $result = $executor->generateMigration($matchCondition, $migrationMode, $context);
137
138 3
        if (isset($dsl['file'])) {
139
140 3
            $fileName = $this->resolveReference($dsl['file']);
141
142 3
            $this->saveDefinition($result, $fileName);
143 3
        }
144 3
145
        $this->setReferences($result, $dsl);
146
147
        return $result;
148 3
    }
149 3
150 3
    public function save($dsl, $context)
151
    {
152
        if (!isset($dsl['migration_steps'])) {
153
            throw new InvalidStepDefinitionException("Invalid step definition: miss 'migration_steps'");
154
        }
155 3
        if (!isset($dsl['file'])) {
156 3
            throw new InvalidStepDefinitionException("Invalid step definition: miss 'file'");
157
        }
158
159
        if (is_string($dsl['migration_steps'])) {
160 3
            $definition = $this->resolveReference($dsl['migration_steps']);
161
        } else {
162
            $definition = $dsl['migration_steps'];
163 3
        }
164
165 3
        /// @todo allow resolving references within texts, not only as full value
166
        $definition = $this->resolveReferencesRecursively($definition);
167 3
168 1
        $fileName = $this->resolveReference($dsl['file']);
169
170
        $this->saveDefinition($definition, $fileName);
171 2
172
        /// @todo what to allow setting refs to ?
173 2
174
        /// @todo what to return ?
175
    }
176 2
177 2
    /// @todo move to a Loader service
178 2
    protected function saveDefinition($definition, $fileName)
179
    {
180
        $ext = pathinfo(basename($fileName), PATHINFO_EXTENSION);
181
182 2
        switch ($ext) {
183
            case 'yml':
184
            case 'yaml':
185 2
                /// @todo use Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE option if it is supported
186 2
                $code = Yaml::dump($definition, 5);
187
                break;
188
            case 'json':
189
                $code = json_encode($definition, JSON_PRETTY_PRINT);
190 2
                break;
191
            default:
192 2
                throw new InvalidStepDefinitionException("Can not save migration definition to a file of type '$ext'");
193
        }
194
195
        $dir = dirname($fileName);
196
        if (!is_dir($dir)) {
197
            mkdir($dir, 0777, true);
198 3
        }
199
200 3
        if (!file_put_contents($fileName, $code)) {
201 3
            throw new MigrationBundleException("Failed saving migration definition to file '$fileName'");
202 3
        }
203 3
    }
204 3
205 3
    protected function setReferences($result, $dsl)
206
    {
207
        if (!array_key_exists('references', $dsl) || !count($dsl['references'])) {
208 3
            return false;
209
        }
210
211
        foreach ($dsl['references'] as $key => $reference) {
212
            // BC
213
            if (is_array($reference) && isset($reference['json_path']) && !isset($reference['attribute'] )) {
214
                $reference['attribute'] = $reference['json_path'];
215 1
            }
216
            $reference = $this->parseReferenceDefinition($key, $reference);
217 1
            switch ($reference['attribute']) {
218 1
                case 'definition':
219 1
                    $value = $result;
220
                    break;
221 1
                default:
222
                    $value = JmesPath::search($reference['attribute'], $result);
223 1
            }
224
225
            $overwrite = false;
226
            if (isset($reference['overwrite'])) {
227
                $overwrite = $reference['overwrite'];
228
            }
229
230
            $this->addReference($reference['identifier'], $value, $overwrite);
231
        }
232
233
        return true;
234
    }
235
236
    /**
237
     * @todo cache this for faster access
238
     * @return string[]
239
     */
240
    protected function getGeneratingExecutors()
241
    {
242
        $migrationService = $this->migrationService;
243
        $executors = $migrationService->listExecutors();
244
        foreach ($executors as $key => $name) {
245
            $executor = $migrationService->getExecutor($name);
246
            if (!$executor instanceof MigrationGeneratorInterface) {
247
                unset($executors[$key]);
248
            }
249
        }
250
        return $executors;
251
    }
252
}
253