Passed
Push — master ( d6ce84...9c42e9 )
by Gaetano
10:07
created

MigrationDefinitionExecutor::setReferences()   B

Complexity

Conditions 7
Paths 10

Size

Total Lines 26
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

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