Passed
Push — main ( c49c6c...49fc69 )
by Gaetano
08:47
created

RepositoryExecutor::resolveReferencesRecursively()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 6
c 1
b 0
f 0
nc 3
nop 1
dl 0
loc 9
ccs 0
cts 0
cp 0
crap 12
rs 10
1
<?php
2
3
namespace Kaliop\eZMigrationBundle\Core\Executor;
4
5
use eZ\Publish\API\Repository\Repository;
6
use eZ\Publish\Core\MVC\ConfigResolverInterface;
7
use Kaliop\eZMigrationBundle\API\Collection\AbstractCollection;
8
use Kaliop\eZMigrationBundle\API\Exception\InvalidStepDefinitionException;
9
use Kaliop\eZMigrationBundle\API\Exception\MigrationBundleException;
10
use Kaliop\eZMigrationBundle\API\ReferenceResolverBagInterface;
11
use Kaliop\eZMigrationBundle\API\Value\MigrationStep;
12
use Kaliop\eZMigrationBundle\Core\RepositoryUserSetterTrait;
13
14
/**
15
 * The core manager class that all migration action managers inherit from.
16
 * @property ReferenceResolverBagInterface $referenceResolver
17
 */
18
abstract class RepositoryExecutor extends AbstractExecutor
19
{
20
    use RepositoryUserSetterTrait;
0 ignored issues
show
introduced by
The trait Kaliop\eZMigrationBundle...positoryUserSetterTrait requires some properties which are not provided by Kaliop\eZMigrationBundle...utor\RepositoryExecutor: $id, $login
Loading history...
21
    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...utor\RepositoryExecutor.
Loading history...
22
    use ReferenceSetterTrait;
23
    use NonScalarReferenceSetterTrait;
0 ignored issues
show
Bug introduced by
The trait Kaliop\eZMigrationBundle...larReferenceSetterTrait requires the property $dsl which is not provided by Kaliop\eZMigrationBundle...utor\RepositoryExecutor.
Loading history...
24
25
    protected $scalarReferences = array('count');
26
27
    /**
28
     * Constant defining the default language code (used if not specified by the migration or on the command line)
29
     */
30
    const DEFAULT_LANGUAGE_CODE = 'eng-GB';
31
32
    /**
33
     * The default Admin user Id, used when no Admin user is specified
34
     */
35
    const ADMIN_USER_ID = 14;
36
37
    /** Used if not specified by the migration */
38
    const USER_CONTENT_TYPE = 'user';
39
    /** Used if not specified by the migration */
40
    const USERGROUP_CONTENT_TYPE = 'user_group';
41
42
    /**
43
     * The eZ Publish 5 API repository.
44
     *
45
     * @var \eZ\Publish\API\Repository\Repository
46
     */
47
    protected $repository;
48
49
    protected $configResolver;
50
51
    // to redefine in subclasses if they don't support all methods, or if they support more...
52
    protected $supportedActions = array(
53
        'create', 'update', 'delete'
54
    );
55
56 149
    public function setRepository(Repository $repository)
57
    {
58 149
        $this->repository = $repository;
59 149
    }
60
61 149
    public function setConfigResolver(ConfigResolverInterface $configResolver)
62
    {
63 149
        $this->configResolver = $configResolver;
64 149
    }
65
66 149
    public function setReferenceResolver(ReferenceResolverBagInterface $referenceResolver)
67
    {
68 149
        $this->referenceResolver = $referenceResolver;
69 149
    }
70
71 43
    public function execute(MigrationStep $step)
72
    {
73
        // base checks
74 43
        parent::execute($step);
75
76 43
        if (!isset($step->dsl['mode'])) {
77
            throw new InvalidStepDefinitionException("Invalid step definition: missing 'mode'");
78
        }
79
80
        // q: should we convert snake_case to camelCase ?
81 43
        $action = $step->dsl['mode'];
82
83 43
        if (!in_array($action, $this->supportedActions)) {
84
            throw new InvalidStepDefinitionException("Invalid step definition: value '$action' is not allowed for 'mode'");
85
        }
86
87 43
        if (!method_exists($this, $action)) {
88
            throw new InvalidStepDefinitionException("Invalid step definition: value '$action' is not a method of " . get_class($this));
89
        }
90
91 43
        $this->skipStepIfNeeded($step);
92
93 43
        $previousUserId = $this->loginUser($this->getAdminUserIdentifierFromContext($step->context));
94
95
        try {
96 42
            $output = $this->$action($step);
97 15
        } catch (\Exception $e) {
98 15
            $this->loginUser($previousUserId);
99 15
            throw $e;
100
        }
101
102
        // reset the environment as much as possible as we had found it before the migration
103 31
        $this->loginUser($previousUserId);
104
105 31
        return $output;
106
    }
107
108
    /**
109
     * Method that each executor (subclass) has to implement.
110
     *
111
     * It is used to get values for references based on the DSL instructions executed in the current step, for later steps to reuse.
112
     *
113
     * @throws \InvalidArgumentException when trying to set a reference to an unsupported attribute.
114
     * @param mixed $object a single element to extract reference values from
115
     * @param array $referencesDefinitions the definitions of the references to extract
116
     * @param MigrationStep $step
117
     * @return array key: the reference name (taken from $referencesDefinitions[n]['identifier'], value: the ref. value
118
     */
119
    abstract protected function getReferencesValues($object, array $referencesDefinitions, $step);
120
121
    /**
122
     * @param MigrationStep $step
123
     * @return string
124
     */
125 21
    protected function getLanguageCode($step)
126
    {
127 21
        return isset($step->dsl['lang']) ? $step->dsl['lang'] : $this->getLanguageCodeFromContext($step->context);
128
    }
129
130
    /**
131
     * @param array|null $context
132
     * @return string
133
     */
134 27
    protected function getLanguageCodeFromContext($context)
135
    {
136 27
        if (is_array($context) && isset($context['defaultLanguageCode'])) {
137 1
            return $context['defaultLanguageCode'];
138
        }
139
140 26
        if ($this->configResolver) {
141 26
            $locales = $this->configResolver->getParameter('languages');
142 26
            return reset($locales);
143
        }
144
145
        return self::DEFAULT_LANGUAGE_CODE;
146
    }
147
148
    /**
149
     * @param MigrationStep $step
150
     * @return string
151
     */
152 3
    protected function getUserContentType($step)
153
    {
154 3
        return isset($step->dsl['user_content_type']) ? $this->resolveReference($step->dsl['user_content_type']) : $this->getUserContentTypeFromContext($step->context);
155
    }
156
157
    /**
158
     * @param MigrationStep $step
159
     * @return string
160
     */
161 2
    protected function getUserGroupContentType($step)
162
    {
163 2
        return isset($step->dsl['usergroup_content_type']) ? $this->resolveReference($step->dsl['usergroup_content_type']) : $this->getUserGroupContentTypeFromContext($step->context);
164
    }
165
166
    /**
167
     * @param array $context
168
     * @return string
169
     */
170 3
    protected function getUserContentTypeFromContext($context)
171
    {
172 3
        return isset($context['userContentType']) ? $context['userContentType'] : self::USER_CONTENT_TYPE;
173
    }
174
175
    /**
176
     * @param array $context
177
     * @return string
178
     */
179 2
    protected function getUserGroupContentTypeFromContext($context)
180
    {
181 2
        return isset($context['userGroupContentType']) ? $context['userGroupContentType'] : self::USERGROUP_CONTENT_TYPE;
182
    }
183
184
    /**
185
     * @param array $context we have to return FALSE if that is set as adminUserLogin, whereas if NULL is set, we return the default admin
186
     * @return int|string|false
187
     */
188 77
    protected function getAdminUserIdentifierFromContext($context)
189
    {
190 77
        if (isset($context['adminUserLogin'])) {
191 1
            return $context['adminUserLogin'];
192
        }
193
194 76
        return self::ADMIN_USER_ID;
195
    }
196
197
    /**
198
     * Sets references to certain attributes of the items returned by steps.
199
     * Should be called after validateResultsCount in cases where one or many items could be used to get reference values from
200
     *
201
     * @param \Object|AbstractCollection $item
0 ignored issues
show
Bug introduced by
The type Object 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...
202
     * @param MigrationStep $step
203
     * @return boolean
204
     * @throws \InvalidArgumentException When trying to set a reference to an unsupported attribute
205
     * @todo should we allow to be passed in plain arrays, ArrayIterators and ObjectIterators as well as Collections?
206 31
     * @todo move to NonScalarReferenceSetterTrait?
207
     */
208 31
    protected function setReferences($item, $step)
209 26
    {
210
        if (!array_key_exists('references', $step->dsl) || !count($step->dsl['references'])) {
211
            return false;
212 24
        }
213
214
        $referencesDefs = $this->setScalarReferences($item, $step->dsl['references']);
215
216
        if (!count($referencesDefs)) {
217 24
            // Save some cpu and return early.
218
            // We return true because some scalar ref was set, or we would have exited at the top of the method
219 24
            return true;
220 17
        }
221
222 19
        // this check is now done immediately after matching
223
        //$this->insureResultsCountCompatibility($item, $referencesDefs, $step);
224
225 24
        // NB: there is no valid yml atm which could result in expectedResultsType returning 'unspecified' here, but
226 24
        // we should take care in case this changes in the future
227 23
        $multivalued = ($this->expectedResultsType($step) == self::$RESULT_TYPE_MULTIPLE);
228 23
229 20
        if ($item instanceof AbstractCollection || is_array($item)) {
230
            $items = $item;
231 8
        } else {
232 1
            $items = array($item);
233 1
        }
234
235 1
        $referencesValues = array();
236
        foreach ($items as $item) {
0 ignored issues
show
introduced by
$item is overwriting one of the parameters of this function.
Loading history...
237
            $itemReferencesValues = $this->getReferencesValues($item, $referencesDefs, $step);
238
            if (!$multivalued) {
239
                // $itemReferencesValues will be an array with key=refName
240
                $referencesValues = $itemReferencesValues;
241 24
            } else {
242 21
                foreach ($itemReferencesValues as $refName => $refValue) {
243 21
                    if (!isset($referencesValues[$refName])) {
244 21
                        $referencesValues[$refName] = array($refValue);
245 2
                    } else {
246
                        $referencesValues[$refName][] = $refValue;
247
                    }
248 21
                }
249
            }
250
        }
251 24
252
        foreach ($referencesDefs as $key => $reference) {
253
            $reference = $this->parseReferenceDefinition($key, $reference);
254
            $overwrite = false;
255
            if (isset($reference['overwrite'])) {
256
                $overwrite = $reference['overwrite'];
257
            }
258
            $referenceIdentifier = $reference['identifier'];
259
            if (!array_key_exists($referenceIdentifier, $referencesValues)) {
260 24
                if (count($items)) {
261
                    // internal error
262
                    throw new MigrationBundleException("Interanl error: getReferencesValues did not return reference '$referenceIdentifier' in class " . get_class($this));
263 24
                } else {
264 24
                    // we "know" this ia a request for multivalued refs. If we were expecting only one item, and got none,
265 24
                    // an exception would have been thrown before reaching here
266
                    $referencesValues[$referenceIdentifier] = array();
267 24
                }
268 10
            }
269 10
            $this->addReference($referenceIdentifier, $referencesValues[$referenceIdentifier], $overwrite);
270 10
        }
271 1
272
        return true;
273 10
    }
274 10
275 10
    /**
276
     * @param mixed $entity
277
     * @param array $referencesDefinition
278
     * @return array the same as $referencesDefinition, with the references already treated having been removed
279
     * @throws InvalidStepDefinitionException
280
     */
281
    protected function setScalarReferences($entity, $referencesDefinition)
282 24
    {
283
        // allow setting *some* refs even when we have 0 or N matches
284
        foreach ($referencesDefinition as $key => $reference) {
285
            $reference = $this->parseReferenceDefinition($key, $reference);
286
            switch ($reference['attribute']) {
287
288
                case 'count':
289
                    $value = count($entity);
290
                    $overwrite = false;
291
                    if (isset($reference['overwrite'])) {
292
                        $overwrite = $reference['overwrite'];
293
                    }
294
                    $this->addReference($reference['identifier'], $value, $overwrite);
295
                    unset($referencesDefinition[$key]);
296
                    break;
297
298
                default:
299
                    // do nothing
300
            }
301
        }
302
303
        return $referencesDefinition;
304
    }
305
306
    /**
307
     * Verifies compatibility between the definition of the references to be set and the data set to extract them from,
308
     * and returns a single entity
309
     *
310 36
     * @param AbstractCollection|mixed $entity
311
     * @param array $referencesDefinition
312 36
     * @param MigrationStep $step
313 36
     * @return AbstractCollection|mixed
314 36
     * @deprecated use validateResultsCount instead
315
     */
316 35
    /*protected function insureSingleEntity($entity, $referencesDefinition, $step)
317
    {
318 36
        $this->insureResultsCountCompatibility($entity, $referencesDefinition, $step);
319
320
        if ($entity instanceof AbstractCollection) {
321
            return $entity->reset();
322
        }
323
324
        return $entity;
325
    }*/
326 25
327
    /**
328 25
     * @param array $referenceDefinition
329
     * @return bool
330
     */
331
    protected function isScalarReference($referenceDefinition)
332
    {
333
        return in_array($referenceDefinition['attribute'], $this->scalarReferences);
334
    }
335
}
336